深入理解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实例化一个对象时,发生了以下事件:
- 内存中新建一个person1对象
- 新对象的 [[prototype]] 指向构造函数的prototype
- 构造函数中的this指向新对象
- 构造函数被执行
- 返回新建的对象
注:如果不使用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