深入理解Object 之 对象的创建

工厂模式

用一个“生产”对象的函数(工厂),该函数接收属性的值作为参数,生成一个对象(产品)并返回。

function createPerson(name, age, job){
    let o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
        console.log("My name is ", this.name);
    };
    return o;
}
let person1 = createPerson("Jack", 18, 'Student');
let person2 = createPerson("Ann", 28, 'Doctor');

这样生产的所有对象都是object类型

 

构造函数模式

function Person(name) {
    this.name = name;
    this.sayName = function(){
        console.log(name);
    }
};

let person1 = new Person("Jack");

使用new实例化一个对象时,发生了以下事件:

  1. 内存中新建一个person1对象
  2. 新对象的 [[prototype]] 指向构造函数的prototype
  3. 构造函数中的this指向新对象
  4. 构造函数被执行
  5. 返回新建的对象

注:如果不使用new直接调用构造函数,this会指向全局作用域:

Person("Ann");
window.sayName(); //Ann

 

这样使用构造函数的一个弊端是,对于每一个函数成员每新建一个实例,就会有一个新的函数生成,上述Person构造函数代码等价于:

function Person(name) {
    this.name = name;
    this.sayName = new Function("console.log(this.name)");
};

假如实例化100个person类型的对象,就会有100个功能完全一样的sayName函数被创建,解决这个问题的一个方法是将sayName函数的内容定义在外部:

function Person(name) {
    this.name = name;
    this.sayName = sayName;
};
function sayName(){
    console.log(this.name);
}

这样所有实例都指向了同一个函数,但如果所有类的方法都定义在全局作用域,会造成代码逻辑的混乱,带来许多安全隐患,更安全的方式是使用原型模式。

 

原型模式

1. 原型的工作原理

当一个函数被定义时,他的prototype属性也会自动生成,prototype也是一个对象。

prototype默认有属性constructor和__proto__,prototype.constructor指向函数本身,prototype.__proto__指向父类型的prototype

function Person() {};

//函数被创建后,自动生成了prototype属性
console.log(typeof Person.prototype);//object
console.log(Person.prototype);
//{
//     constructor: f Person(),
//     __proto__: Object
// }


//prototype的constructor属性指向函数本身
console.log(Person.prototype.constructor === Person); //true
//prototype的__proto__属性指向父级类型的prototype
console.log(Person.prototype.__proto__ === Object.prototype); //true
//Object.prototype的constructor指向Object构造函数本身
console.log(Person.prototype.__proto__.constructor === Object); //true
//Object.prototype的__proto__是null
console.log(Person.prototype.__proto__.__proto__ === null); //true

 

当通过new实例化一个对象后,实例的[[Prototype]](通过__proto__访问)指向构造函数的Prototype

let person1 = new Person();
console.log(person1.__proto__ === Person.prototype);//true
console.log(person1.__proto__.constructor === Person);//true

 

图示:

 

 

2. isPrototypeOf 和 getPrototypeOf

console.log(Object.getPrototypeOf(person1) === Person.prototype);//true

console.log(Person.prototype.isPrototypeOf(person1)) ;//true

 

3. Object的原型

使用Object.setPrototypeOf(child, parant)可以使第一个对象继承第二个对象的所有属性,并设置其prototype属性

let person = {
    name: "Jack",
    age: 18
}
let student = {
    marks: 90
}

Object.setPrototypeOf(student, person);
console.log(student.age);
console.log(person.isPrototypeOf(student)); //true

 

使用Object.create()可以新建一个对象并规定其原型:

let person = {
    name: "Jack",
    age: 18
}
let student = Object.create(person);
student.marks = 90;

console.log(student.age);
console.log(student.__proto__);//{name: "Jack", age: 18}
console.log(person.isPrototypeOf(student)); //true

 

4. 原型模式

将属性定义在原型上,构造函数为空,所有实例共用一套属性。

let Person = function(){};

Person.prototype.name = "Jackson";
Person.prototype.age = 8;
Person.prototype.sayName = function(){
    console.log(this.name);
}

let person1 = new Person();
let person2 = new Person();

person1.sayName();  //Jackson
person2.sayName();  //Jackson

person1和person2的所有属性全部都可以用===判断为真。需要注意的是,所有属性都属于prototype,而不是对象本身和构造函数。如图所示:

访问顺序:

 当访问person1.name时,首先在person1自身的属性列表中查找name,查找不到,继续在其__proto__指向的Prototype对象中查找是否有name属性,查找到name的值为“Nicholas”。注意Person Prototype也是一个object,在其中查找name的过程和在person1中是一样的,查不到会继续在object中查找,直到查到prototype为null。

需要注意的是,如果我们再对person1和person2添加和原型上属性同名的属性name,原型上的name属性并不会被重写,只是被同名的对象属性隐藏了。在通过person1.name访问时只能访问到person1上的name,但是通过delete person1.name 删除以后,原型上的name仍然可以被访问到。delete只能删除当下对象上的属性,不会在原型链上继续查找。

 

in VS hasOwnProperty VS for...in :

  in hasOwnProperty for...in
参数 "propertyName" in obj obj.hasOwnProperty("propertyName") for(let propertyName in obj){...}
返回值

true or false

true or false 遍历obj及其prototype中所有可枚举的的属性
(enumerated 为 true)
查找范围

instance -> prototype

instance instance -> prototype

 

动态添加原型属性

由于对对象的属性的访问是一个链式查找过程,所以即使在创建对象后再修改原型,这些改动也能在实例上体现出来。但如果在对象创建后,直接将原型赋值为另一个对象,则改动不能在实例上体现出来,因为实例的[[Prototype]]指向地址A在创建时就已经确定,将原型赋值为另一个对象后,A仍然会指向原来的那个地址。参考以下例子:

情况1: 创建对象后改变原型,但不使其指向新的地址:

let Person = function(){};

let person1 = new Person();

Person.prototype.name = "Jackson";
Person.prototype.age = 8;
Person.prototype.sayName = function(){
    console.log(this.name);
}

person1.sayName();  //Jackson

 

情况2: 创建对象后改变原型,使其指向新的对象:

let Person = function(){};

let person1 = new Person();

Person.prototype = {
    name: "Jack",
    Age: 8,
    sayName(){
        console.log(this.name);
    }
}

person1.sayName();  
//Uncaught TypeError: person1.sayName is not a function

 

posted @ 2020-07-04 09:19  studystudyxinxin  阅读(273)  评论(0编辑  收藏  举报