JavaScript系列—原型和原型链

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

上面代码中,xy都是实例对象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指向的是原型对象。上面代码中,p1p2都是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,子类Bprototype属性的__proto__属性指向父类Aprototype属性。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

看完了,现在开始回答下面的问题

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

解析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值