本文主要对JS中的object和function这两种数据类型的基础上对原型原型链及构造函数做一个详解。
注意:由于浏览器的解析问题文中所有出现的proto 就是 __proto__。
原型图.png
构造函数
ECMAScript中的构造函数是用于创建特定类型对象的,像Object和Array这样的原生构造函数。当然也可以自定义构造函数,以函数的形式为自己的对象类型定义属性和方法。比如
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
console.log(this.name);
}
}
let person1 = new Person('zj', 30, 'web开发');
person1.sayName(); // zj
注意:构造函数名称的首字母都是要大写的,非构造函数名则以小写字母开头
创建Person实例,应使用new操作符。以这种方式调用构造函数会执行如下操作:
(1)在内存中创建一个新的对象。
(2)在这新的对象内部的proto特性被赋值为构造函数的prototype。
(3)执行构造函数内部的this被赋值为这个新对象(即this指向的对象)。
(4)执行构造函数内部的代码(给新对象添加属性)。
(5)如果构造函数返回非空对象,则返回该对象;否则返回刚创建的新对象。
personWeb是Person的实例,这个实例对象上有一个constructor属性,这个属性指向Person这个构造函数。
constructor是用来标识对象类型的。不过,一般认为instanceof操作符是确定对象类型更可靠的方式,在上边的例子中每一个对象都是Object的实例。所以
console.log(person1 instanceof Person); // true
console.log(person1 instanceof Object); // true
注:构造函数也是函数
原型
每一个函数都会创建一个prototype属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法。实际上,这个对象就是通过构造函数创建的对象的原型,使用原型的好处就是,在它上面定义的属性和方法可以被对象实例共享。比如
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
console.log(this.name);
}
}
let person2 = new Person('zj', 30, 'web开发');
person2.sayName(); // zj
无论何时,只要创建一个函数,就会按照特定的规则为这个函数创建一个prototype属性(指向原型对象),默认的情况下,所有的原型对象自动获得一个名为constructor的属性,指回与之关联的构造函数。
在自定义构造函数时,原型对象默认只会获得constructor属性,其它所有的方法都继承自Object,每次调用构造函数创建一个新的实例,这个实例的内部proto指针就会被赋值为构造函数的原型对象。
关于proto有的浏览器没有暴露出来 但是可以使用isPrototypeOf()方法确定两个对象的关系,本质上,isPrototypeOf()会在传入参数的proto指向调用它的对象时返回true,比如:
console.log(Person.prototype.isPrototypeOf(person2)); // true
ECMAScript的Object类型有一个方法叫Object.getPrototypeOf(),返回proto的值,比如:
console.log(Object.getPrototypeOf(person2) === Person.prototype); //true
Object类型还有一个setPrototypeOf()方法,可以向实例的私有特性proto写入一个新值,这样就可以重写一个对象的原型继承关系:
let biped = {
numLegs: 2
};
let person = {
name: 'zj'
};
Object.setPrototypeOf(person, biped);
console.log(person.name); // 'zj'
console.log(person.numLegs); // 2
console.log(Object.getPrototypeOf(person) === biped); // true
警告: 在所有的浏览器中修改继承关系的影响都是微妙且深远的,这种影响并不仅是执行Object.setPrototypeOf()语句那么简单,而是会涉及所有访问了那些修改过proto的对象的代码,Object.setPrototypeOf()可能会严重的影响代码的性能。
为了避免使用Object.setPrototypeOf()可能造成的后果,可以通过Object.create()来创建一个新的对象,同时为其指定原型为所传入的参数:
let biped = {
numLegs: 2
};
let person = Object.create(biped);
person.name = 'zj';
Object.setPrototypeOf(person, biped);
console.log(person.name); // 'zj'
console.log(person.numLegs); // 2
console.log(Object.getPrototypeOf(person) === biped); // true
原型层级
在通过对象访问属性时,会按照这个属性的名称开始搜索。搜索开始于对象实例本身,如果这个实例上发现了给定的名称,则返回该名称对应的值。如果没有找到这个属性,则搜索会沿着指针进入原型对象,然后在原型对象上找到属性后返回对应的值。
只要给对象实例添加一个属性,这个属性就会屏蔽原型对象上的同名的属性,也就是不会修改它,但是会屏蔽对它的访问,即使把实例上的这个属性设置为null也是这样的,但是使用delete操作符就可以继续搜索原型对象。
注:前面及原型图中提到的constructor属性只存在于原型对象,因此通过实例对象也可以访问到,比如:
function Person(name){
this.name = name;
}
let person = new Person('zj');
let obj = {};
console.log(person.constructor === Person); //true
console.log(Person.constructor === Function); // true
console.log(obj.constructor === Object); // true
关于实例及原型对象的属性的访问:
1、通过实例访问hasOwnProperty()能够清楚的知道访问的是实例属性还是原型属性;
2、in 操作符 不管该值是存在于原型上还是实例上都会返回true;
3、for in循环中使用in操作符时,可以通过对象访问且可以被枚举的属性都会返回,包括实例和原型上的属性;
4、Object.keys() 获得实例上所有可枚举的属性;
5、Object.getOwnPropertyNames 获得实例上所有的可枚举及不可枚举的属性;
注意:Object.getOwnPropertyDescriptor()方法只对实例属性有效。要想取得原型属性上的描述符,就必须在原型对象上调用。
那么Object和Function是什么关系呢?
console.log(Object.constructor === Function); // true
// 如最上边的原型图中
console.log(Object.__proto__ === Function.prototype); // true
console.log(Function.__proto__ === Function.prototype); // true
console.log(Function.constructor === Function); // true
关于proto有定义是每一对象上都会暴露这个属性,Function上有这个属性,所以Function也是对象,由最上边的原型图及上边代码 Object也是一个Function的实例。此外定义一个对象不仅可以使用字面量的形式还可以使用构造函数的形式。如:
let obj = {};
let newObj = new Object();
原型链
每一个构造函数都有一个原型(prototype)对象,原型有一个属性(constructor)指回构造函数,而实例内部有一个指针(prpto)指向原型,那么如果原型是另一个类型的实例,那就意味着原型内有一个指针(prpto)指向另一个原型,相应的另一个原型也有一个属性(constructor)指向另一个构造函数(如文章开始的第一张图),这样就在实例和原型之间构造了一条原型链。
原型和实例之间的关系确定方式:
1、instanceof 如果一个实例的原型链中出现过相应的构造函数,就会返回true,如下:
console.log(Object instanceof Function); // true
console.log(Function instanceof Object); // true
console.log(Function instanceof Function); // true
console.log(Person.prototype instanceof Object); // true
console.log(Function.prototype instanceof Object); // true
2、isPrototypeOf()方法 每一个原型都可以调用这个方法,只要原型链中包含这个原型就返回true
console.log(Function.prototype.isPrototypeOf(Object)); // true
网友评论