【笔记12】Javascript - 原型链

原型链

原型链的构成

先来看一段代码:

Grand.prototype.company = "Apple";    // grand 也有原型
function Grand() {
this.lastName ="Jobs";
}
var grand = new Grand();

Father.prototype = grand; // father 的原型上面还有原型
function Father() {
this.name = "Steve";
}
var father = new Father();

Son.prototype = father; // son 的原型上面有原型
function Son() {
this.hobbit = "smoke";
}
var son = new Son();

现在我们在控制台输出:

Javascript(笔记12) - 原型链_原型链

hobbit 是 son 自己的属性,name 是 son 的原型(father)的属性,lastName 是 son 原型的原型(Grand)的属性,最后 company 是 grand 的原型的属性,一层层都可以调用。

这样就把访问原型的顺序连成链,就叫做原型链。

那么 Grand.prototype 就是已知最上层的原型了么? 不。

Grand.prototype.__proto__ == Object.prototype   // true

看控制台输出:

Javascript(笔记12) - 原型链_构造函数_02

可以看到,整条原型链的最上层是 Object.prototype ,再往上就是 null 。

属性的修改

原型链跟原型一样,自己没有的属性就一直向上查。

对象自己可以 增加修改删除 自己原型的属性,但不能修改上层原型的属性。

示例1:尝试修改属性

我们给 Father 构造函数加一个引用值的属性:

Grand.prototype.company = "Apple";   
function Grand() {
this.lastName = "Jobs";
}
var grand = new Grand();

Father.prototype = grand;
function Father() {
this.name = "Steve";
this.fortune = { // 新增一个引用值属性 fortune
card1: 'visa'
}
}
var father = new Father();

Son.prototype = father;
function Son() {
this.hobbit = "smoke";
}
var son = new Son();

在控制台修改 son.fortune :

Javascript(笔记12) - 原型链_创建对象_03

这个操作不能修改 father.fortune 的属性,实际上这是 son 再给自己添加 fortune 属性。

换个改法:

Javascript(笔记12) - 原型链_创建对象_04

严格来说,这不算是属性的修改,是引用值调用的修改。也仅限于引用值,原始值是不能修改的。

示例2:尝试属性++

Grand.prototype.company = "Apple";   
function Grand() {
this.lastName = "Jobs";
}
var grand = new Grand();

Father.prototype = grand;
function Father() {
this.name = "Steve";
this.fortune = {
card1: 'visa'
}
this.num = 100; // 新增一个原始值属性
}
var father = new Father();

Son.prototype = father;
function Son() {
this.hobbit = "smoke";
}
var son = new Son();

控制台里,修改:

Javascript(笔记12) - 原型链_构造函数_05

怎么解释 “++”的问题?

son.num ++ 相当于把 father.num 取过来再加 ++,使得自身多了一个属性 num ,值为101。而father.num 没变。

示例3:this的问题

Person.prototype = {
name: "Jack",
sayName: function () {
console.log(this.name);
}
}
function Person() {

}
var person = new Person();

看控制台输出:

Javascript(笔记12) - 原型链_创建对象_06

sayName 中 this 的指向: 谁调用的这个方法,this就是指向谁。

person 调用的这个方法,就指向 person ,但 person 里没有 name,就向他的原型找。所以输出 Jack 

我们改一下代码:

Person.prototype = {
name: "Jack",
sayName: function () {
console.log(this.name);
}
}
function Person() {
this.name = "Tom";
}
var person = new Person();

再看控制台输出:

Javascript(笔记12) - 原型链_创建对象_07

这时 person 里面有了自己的 name 所以输出 Tom。

示例4:还是this

Person.prototype = {
height: 100
}
function Person() {
this.eat = function () {
this.height++;
}
}
var person = new Person();

看控制台输出:

Javascript(笔记12) - 原型链_创建对象_08

调用 eat 方法并不能修改 原型上的原始值,只是在给自己取了一个 height ,并 ++ 了。


原型链终端

绝大多数对象的最终都会继承自 Object.prototype

常用的创建对象的方法:

var obj = {};   // 相当于 --> new Object();  
var obj1 = new Object();

这两种创建对象的方法可以说是一样的。

Javascript(笔记12) - 原型链_原型链_09

常用的是,就用字面量的方式创建对象。

var obj = {}   // 字面量创建

这两种方式创建的对象的原型也都是 Object.prototype 。

Javascript(笔记12) - 原型链_原型链_10

所以 obj 天生就有 toString() 方法,因为它继承了原型的方法。

Javascript(笔记12) - 原型链_创建对象_11


示例1:常见对象

是不是所有的对象最终都继承自 Object.prototype 呢?

var obj = Object.create(null);

在控制台输出:

Javascript(笔记12) - 原型链_创建对象_12

居然构造出一个没有原型的对象,那人为的给他加上一个原型试试:

Javascript(笔记12) - 原型链_创建对象_13

原型添加成功了,但是属性却不能用。

所以说:绝大多数对象的最终都会继承自 Object.prototype,但有个别特例。


示例2:特殊类型

先看控制台:

Javascript(笔记12) - 原型链_构造函数_14

undefined 和 null 这两种特殊的类型没有原型,也就不使用Object.prototype 的属性。

常见的原始值:

Javascript(笔记12) - 原型链_创建对象_15

数字、布尔和字符串都可以使用 toString() 方法,是因为他们在使用方法前,都被隐式的“包装类”了。

var num = 123;
// num.toString(); (隐式的转换)--> new Number(num).toString
// 而他的原型 Number.prototype 本身就有 toString()方法。
Number.prototype.toString = function(){} // Number 自身原型重新了 toString 方法
// Number.prototype.__proto__ 的原型就指向 Object.prototype,所以这也原型链。
Number.prototype.__proto__ == Object.prototype


示例3:原型上的方法重写

先看代码:

Person.prototype ={
toString:function(){
return "surprise";
}
}
function Person(){

}
var person = new Person();
console.log(person.toString()); // surprise

这在构造函数的原型上新建了一个也叫  toString() 的方法,就把 Object.prototype 原型上的覆盖了,这种叫做“方法重写”,当然也可以在 Object.prototype 原型上修改 toString() 方法。

Object.prototype.toString = function (){
return "hello!";
}

不仅我们在重写,系统自己也在重写:包装类也重写了 toString 方法。

// Object.prototype.toString
// Number.prototype.toString
// Array.prototype.toString
// Boolean.prototype.toString
// String.prototype.toString

验证一下:

Javascript(笔记12) - 原型链_创建对象_16

可以看到 Object.prototype.toString 输出的内容并没什么卵用。

document.write 打印在屏幕上的内容,也是使用的 toString 方法,验证一下?

var obj ={};
obj.toString = function(){
return "surprise";
}
document.write(obj); // surprise


Object.create(原型)

一种更加灵活的创建原型的方法。

// var obj = Object.create(原型);
var obj = {name : "Jack",age : 23};
var obj1 = Object.create(obj);

看控制台输出:

Javascript(笔记12) - 原型链_创建对象_17

obj1 的原始指向了 obj , 也可以方便的使用原型的属性。

可以模拟 new Person() 创建对象。

Person.prototype.name = "Jack";
function Person(){
this.age = 30;
}
var person = Object.create(Person.prototype);
console.log(person.name); // Jack
console.log(person.age); // undefined

为啥说是模拟呢,因为把 person 的原型指向 Person.prototype 后,可以使用这个原型的属性,但不是构造函数的对象,所以 person.age 输出 undefined 。