1.理解原型对象
理解原型对象之前,先看一下使用原型模式编写的代码 。
function Person() {
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function () {
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"
var person2 = new Person();
person2.sayName(); //"Nicholas"
alert(person1.sayName == person2.sayName); //true
一、基本原理
a.只要创建了一个新函数(构造函数Person),就会根据一组特定的规则为该函数创建一个 prototype属性,这个属性指向函数的原型对象(Person Prototype)。
b.在默认情况下,所有原型对象都会自动获得一个 constructor(构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针。(Person.prototype. constructor 指向 Person 。而通过这个构造函数,我们还可继续为原型对象添加其他属性和方法)。
c.创建了自定义的构造函数之后,其原型对象默认只会取得 constructor 属性;至于其他方法,则都是从 Object 继承而来的。
d.当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。即 [[Prototype]] ,这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。
[[Prototype]] 其实就是_proto_
一句话概括就是每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。如下图所示
2.理解原型链
现在我们引入原型链的概念?
假如我们让原型对象等于另一个类型的实例,结果会怎么样呢?显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。
看个例子
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
};
function SubType() {
this.subproperty = false;
}
//继承了 SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function() {
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); //true
简言之: SubType 继承了 SuperType ,而继承是通过创建 SuperType 的实例,并将该实例赋给SubType.prototype 实现的。
这个例子中的实例以及构造函数和原型之间的关系如图所示
做一下注解
最终结果就是这样的:
1)instance 指向 SubType的 原 型 , SubType 的 原 型 又 指 向 SuperType 的 原 型 。
2)getSuperValue() 方 法 仍 然 还 在SuperType.prototype 中,但 property 则位于 SubType.prototype 中。这是因为 property 是一个实例属性,而 getSuperValue() 则是一个原型方法。既然 SubType.prototype 现在是 SuperType 的实例,那么 property 当然就位于该实例中了。
3)instance.constructor 现在指向的是 SuperType ,这是因为原来 SubType.prototype 中的 constructor 被重写了的缘故。
总结:调用instance.getSuperValue() 会经历三个搜索步骤:
1)搜索实例;
2)搜索 SubType.prototype ;
3)搜索 SuperType.prototype ,
最后一步才会找到该方法。在找不到属性或方法的情况下,搜索过程总是要一环一环地前行到原型链末端才会停下来。
1. 别忘记默认的原型
所有引用类型默认都继承了 Object。所有函数的默认原型都是 Object 的实例,因此默认原型都会包含一个内部指针,指向 Object.prototype 。上面例子中完整的原型链应该如下所示。
一句话, SubType 继承了 SuperType ,而 SuperType 继承了 Object 。当调用 instance.toString()时,实际上调用的是保存在 Object.prototype 中的那个方法。
2. 确定原型和实例的关系
instanceof操作符。测试实例与原型链中出现过的构造函数,结果就会返回 true 。
alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true
isPrototypeOf() 方法。只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型,因此 isPrototypeOf() 方法也会返回 true ,
alert(Object.prototype.isPrototypeOf(instance)); //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true
3.ES6中class的原型链
1) 简介
在类的实例上面调用方法,其实就是调用原型上的方法。
class B {}
let b = new B();
b.constructor === B.prototype.constructor // true
因为实例b和B.prototype指向同一个地方也就是B的原型,所以上面也就不难理解了。
2)链式
由于类的方法都定义在
prototype
对象上面,所以类的新方法可以添加在prototype
对象上面。Object.assign
方法可以很方便地一次向类添加多个方法。
class Point {
constructor(){
// ...
}
}
Object.assign(Point.prototype, {
toString(){},
toValue(){}
});
上面的意思是这样子的
prototype
对象的constructor
属性,直接指向“类”的本身,这与 ES5 的行为是一致的。
Point.prototype.constructor === Point // true
3)_proto_
//定义类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
var point = new Point(2, 3);
point.toString() // (2, 3)
point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false
point.__proto__.hasOwnProperty('toString') // true
上面代码中,x
和y
都是实例对象point
自身的属性(因为定义在this
变量上),所以hasOwnProperty
方法返回true
,而toString
是原型对象的属性(因为定义在Point
类上),所以hasOwnProperty
方法返回false
。这些都与 ES5 的行为保持一致。
关于这个point.__proto__.hasOwnProperty('toString') // true。首先看下面的表。这里有一句是这样写的
person1._prop_ === Person.prototype 结果为true。
类比class, point._proto === Point.prototype(原型对象) 结果为true。这样就好理解咯
与 ES5 一样,类的所有实例共享一个原型对象。
var p1 = new Point(2,3);
var p2 = new Point(3,2);
p1.__proto__ === p2.__proto__
//true
p1._proto指向的是原型对象。上面代码中,p1
和p2
都是Point
的实例,它们的原型都是Point.prototype
,所以__proto__
属性是相等的。这也意味着,可以通过实例的__proto__
属性为“类”添加方法。但是
__proto__
并不是语言本身的特性,这是各大厂商具体实现时添加的私有属性,虽然目前很多现代浏览器的 JS 引擎中都提供了这个私有属性,但依旧不建议在生产中使用该属性,避免对环境产生依赖。生产环境中,我们可以用 Object.getPrototypeOf
方法来获取实例对象的原型,然后再来为原型添加方法/属性。
4)类的 prototype 属性和__proto__属性
先回忆一下上面的几张图
大多数浏览器的 ES5 实现之中,每一个对象都有__proto__
属性,指向对应的构造函数的prototype
属性。Class 作为构造函数的语法糖,同时有prototype
属性和__proto__
属性,因此同时存在两条继承链
(1)子类的
__proto__
属性,表示构造函数的继承,总是指向父类。(2)子类
prototype
属性的__proto__
属性,表示方法的继承,总是指向父类的prototype
属性。
class A {
}
class B extends A {
}
B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true
上面代码中,子类B
的__proto__
属性指向父类A
,子类B
的prototype
属性的__proto__
属性指向父类A
的prototype
属性。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
看完了,现在开始回答下面的问题
1.介绍下原型链,解决的是继承问题吗
2.如何继承
1)测试题
选A
解析
es5的写发
都是打印
继续看
2)测试题
选A
3)再看一道稍微难的题
解析
f.x=>f._proto_.x == F.prototype.x => obj.x =>1(注意 F.prototype = obj 是题中已有的,不要想复杂了)
f.y=>f._proto_.y==F.prototype.y=>obj.y=>obj._proto_.y == Object.prototype.y =>100
这块是个陷阱,第二句把第一句覆盖了,第一句没用了
4)
理解一下
打印
5)测试题
解析
6)测试题
2,4,1
解析