JavaScript原型继承工作原理
当查找一个对象的属性的时候,JavaScript会往上遍历原型链,直到找到指定的属性名为止,如果没有找到则为undefined。
大多数的js的实现用_proto_属性来表示一个对象的原型链,以下代码展示了js引擎如何查找属性。
function getProperty(obj, prop){
if(obj.hasOwnProperty(prop)){
return obj[prop]
}else if(obj.__proto__ !== null){
return getProperty(obj.__proto__, prop)
}else{
return undefined;
}
}
现在我们创建一个对象point,具有x、y、z和print属性,为了能创建一个新的三维坐标点,我们需要创建一个新的对象,使得它的_proto_指向point,并继承point。类似于C++中的OOP,例如以point为基类,创建新的point对象为原先point的子类。
var Point = {
x: 1,
y: 1,
z: 1,
print: function(){
console.log(this.x, this.y, this.z)
}
}
;
var p = {
x: 10,
y: 20,
z: 30
,
__proto__: Point
};
p.print()
// 10 20 30
但是在实际开发中,我们是不会这样实现原型继承的,写法如下:
function Point(x, y){
this.x = x;
this.y = y;
this.z = z;
}
Point.prototype = {
print: function(){console.log(this.x, this.y, this.z);}
}
var p = new Point(20, 30, 40);
p.print();
//20 30 40
这里涉及到了new运算符的工作原理
new后面跟的不是类,而是构造函数,用new构造函数来生成实例对象,有一个很明显的缺点,就是每一个实例无法共享同一个属性和方法。在C++中,如果一个类定义了一个static成员,那么所有该类的实例都可以共享该成员。而在JavaScript中每一个实例的对象都有自己属性的副本,这样就导致比较浪费空间,并且无法实现数据的共享。
基于以上new构造函数的缺陷,js创始人为构造函数添加了prototype属性
prototype属性的引入
js规定每一个构造函数都有prototype属性,该属性指向另一个对象,另一个对象中的所有属性和方法都会被构造函数的实例引用。
这个属性包含一个对象,所有需要共享的属性和方法放入到这个对象中,而不需要共享的属性和方法放入到构造函数中。实例对象一旦创建成功,就会自动引用prototype对象中的方法和属性,即实例对象的方法和属性分为两种,一种是本地的,即放入构造函数中的属性和方法,另一种是引用的,即放入prototype对象。例如以下代码:
function DOG(name){
this.name = name;
}
DOG.prototype = {species: '犬科'};
var dogA = new DOG('大毛');
var dogB = new DOG('二毛');
console.log(dogA.species)
// 犬科
console.log(dogB.species)
// 犬科
在这个例子中,species属性放入prototype对象中,实例dogA和实例dogB共用species属性。只要其中一个实例的species属性发生变化,另一个实例的species属性也会发生改变。
Prototype模式的验证方法
①.isPrototypeOf
这个方法用来判断prototype对象和某个实例之间的关系,例如:
console.log(DOG.prototype.isPrototypeOf(dogA));
//true
②.hasOwnProperty
每一个实例对象都有一个方法,用来判断该实例中的某个属性是来自本地属性还是继承自原型对象属性。例如:
console.log(dogA.hasOwnProperty(name));
// true
③. in运算符
in运算符可以判断某个属性是否属于实例对象,不管是本地属性还是继承自原型对象属性。如:
console.log('name' in dogA);
// true
console.log('species' in dogA);
// true
in运算符还可以遍历某个对象中的所有属性,如:
for(var prop in dogA){
console.log(prop)
}