Prototype 原型

Node.js Prototype 原型

Prototype 原型链的原理

JavaScript 并不像 Java、C++ 这些知名的物件导向语言具有「类别」(class)来区分概念与实体(instance)或天生具有继承的能力,而只有「物件」,因此只能利用设计模式来模拟这些功能

当初设计语言并没有 Class,所以只能用建构函式 function( 可以当作 constructor ),所以就可以知道 prototype 的出现是为了达到所有 instance 都能共享 property & method。

function Person(name) {
    this.name = name;
    this.sayHi = function() {
        // new 出几份 instance 就会有几份,佔用记忆体空间
        console.log('Object', this.name, 'Hi');
    }
}

Person.prototype.sayHi = function(){
    // 用 prototype 实现共享 property & method
    console.log('Object', this.name, 'Hi');
}

若没有写 prototype 的话,只要 Function 或物件被 new 出多少 instance 就会需要多少空间

而共同 prototype 的函式是共用同个记忆体,所以 比较节省记忆体空间

当建立一个 Instance 时,JavaScript 会自动加上 __proto__ 属性,告诉程式如果在原本的 Instance 找不到方法的时候,要去 __proto__

建构子

只要函式前有 new,这个函式就是建构子,只要函式前有 new 来做呼叫,就叫做建构子呼叫。

new 关键字做了哪些事情

  1. 建立一个新的物件。

设定原型

// 动物
let animal = {
    // 会吃
    eats: true
};
// 兔子
let rabbit = {
    // 会跳
    jumps: true
};

// 设定兔子原型是动物
rabbit.__proto__ = animal;

// 兔子会吃:true
console.log( rabbit.eats );
// 兔子会跳:true
console.log( rabbit.jumps );
// 动物不一定会跳:undefined
console.log( animal.jumps );
// 兔子会跳:true
console.log( 'eats' in rabbit );
// 动物不一定会跳:false
console.log( 'jumps' in animal );

除了直接在 __proto__ 设定物件原型,也可以用使用物件方法设定原型

// 设定兔子原型是动物
rabbit.__proto__ = animal;
// 使用物件方法设定原型
Object.setPrototypeOf(rabbit, animal);

Prototype 原型

设定物件原型

Object.setPrototypeOf(obj, prototype)

加入新的原型方法

// 动物
let animal = {
    // 会吃
    eats: true,
    walk : () => {
        console.log(`animal walking`);
    }
};
// 兔子
let rabbit = {
    // 会跳
    jumps: true
};

// 设定兔子原型是动物
Object.setPrototypeOf(rabbit, animal)

// 兔子会走:animal walking
rabbit.walk();

加入新的物件继承动物原型

// 动物
let animal = {
    // 会吃
    eats: true,
    walk : () => {
        console.log(`animal walking`);
    }
};
// 兔子
let rabbit = {
    // 会跳
    jumps: true
};

// 鸟
let bird = {
    // 会飞
    fly: true
}

// 设定兔子原型是动物
Object.setPrototypeOf(rabbit, animal)
// 设定鸟原型是动物
Object.setPrototypeOf(bird, animal)

// 兔子会走:animal walking
rabbit.walk();
// 鸟会走:animal walking
bird.walk();
// 兔子与鸟的原型都是一样的 true
console.log(rabbit.__proto__ === bird.__proto__);

取得物件本身自己的键值 Object.keys

// 动物
let animal = {
    // 会吃
    eats: true,
    walk : () => {
        console.log(`animal walking`);
    }
};
// 兔子
let rabbit = {
    // 会跳
    jumps: true
};

// 鸟
let bird = {
    // 会飞
    fly: true
}

// 设定兔子原型是动物
Object.setPrototypeOf(rabbit, animal);
// 设定鸟原型是动物
Object.setPrototypeOf(bird, animal);

// [ 'jumps' ]
console.log(Object.keys(rabbit));
// [ 'fly' ]
console.log(Object.keys(bird));

取得物件所有键值,包含原型资料 for..in

// 动物
let animal = {
    // 会吃
    eats: true,
    walk : () => {
        console.log(`animal walking`);
    }
};
// 兔子
let rabbit = {
    // 会跳
    jumps: true
};

// 鸟
let bird = {
    // 会飞
    fly: true
}

// 设定兔子原型是动物
Object.setPrototypeOf(rabbit, animal);
// 设定鸟原型是动物
Object.setPrototypeOf(bird, animal);

// 取得兔子所有键值资料
// [rabbit] jumps
// [rabbit] eats
// [rabbit] walk
for(let prop in rabbit) {
    console.log(`[rabbit] ${prop}`);
}

// 取得鸟所有键值资料
// [bird] fly
// [bird] eats
// [bird] walk
for(let prop in bird) {
    console.log(`[bird] ${prop}`);
}

判断是否是物件自己的属性 hasOwnProperty

// 动物
let animal = {
    // 会吃
    eats: true,
    walk : () => {
        console.log(`animal walking`);
    }
};
// 兔子
let rabbit = {
    // 会跳
    jumps: true
};

// 鸟
let bird = {
    // 会飞
    fly: true
}

// 设定兔子原型是动物
Object.setPrototypeOf(rabbit, animal);
// 设定鸟原型是动物
Object.setPrototypeOf(bird, animal);

// 取得兔子所有键值资料
// [rabbit] Our: jumps
// [rabbit] Inherited: eats
// [rabbit] Inherited: walk
for(let prop in rabbit) {
    let isOwn = rabbit.hasOwnProperty(prop);

    if (isOwn) {
        console.log(`[rabbit] Our: ${prop}`);
    } else {
        console.log(`[rabbit] Inherited: ${prop}`);
    }
}

// 取得鸟所有键值资料
// [bird] Our: fly
// [bird] Inherited: eats
// [bird] Inherited: walk
for(let prop in bird) {
    let isOwn = bird.hasOwnProperty(prop);
    if (isOwn) {
        console.log(`[bird] Our: ${prop}`);
    } else {
        console.log(`[bird] Inherited: ${prop}`);
    }
}

删除 prototype

let animal = {
    jumps: null
};
let rabbit = {
    __proto__: animal,
    jumps: true
};


// true
console.log( rabbit.jumps );

// 删除兔子 jumps
delete rabbit.jumps;
// 显示动物 jump:null
console.log( rabbit.jumps );

// 删除动物 jumps
delete animal.jumps;
// undefined
console.log( rabbit.jumps );

prototype chain 原型链

prototype 可以持续不断地继承

建构函式有预设最底层的原型物件

// 兔子
let rabbit = {
    // 会跳
    jumps: true
};

// [Object: null prototype] {}
console.log(rabbit.__proto__);

修改 prototype 后,可以用 prototype chain 找到更底层的原型物件

// 动物
let animal = {
    // 会吃
    eats: true,
    walk : () => {
        console.log(`animal walking`);
    }
};
// 兔子
let rabbit = {
    // 会跳
    jumps: true
};

Object.setPrototypeOf(rabbit, animal);
// { eats: true, walk: [Function: walk] }
console.log(rabbit.__proto__);
// [Object: null prototype] {}
console.log(rabbit.__proto__.__proto__);
// null
console.log(rabbit.__proto__.__proto__.__proto__);

JavaScript 就是透过 prototype chain 原型链,去达到继承的目的

方法取得顺序

// 动物
let animal = {
    // 会吃
    eats: true,
    walk : () => {
        console.log(`animal walking`);
    }
};
// 兔子
let rabbit = {
    // 会跳
    jumps: true
};

Object.setPrototypeOf(rabbit, animal)
// animal walking
rabbit.walk();
// [object Object]
console.log(rabbit.toString());

rabbit.walk()

顺序 寻找 所属原型 有没有找到
1 rabbit.walk() rabbit X
2 rabbit.__proto__.walk() animal V

rabbit.toString()

顺序 寻找 所属原型 有没有找到
1 rabbit.toString() rabbit X
2 rabbit.__proto__.toString() animal X
3 rabbit.__proto__.__proto__.toString() Object V

__proto__prototype 比较

function Animal(name) {
    this.name = name;
    this.walk = () => {
        console.log(`[Animal] ${this.name} walking`);
    }
}

// 设定动物函式的 Prototype
Animal.prototype.run = () => {
    console.log(`[Animal Prototype] ${this.name} run`);
}

let MyAnimal = new Animal('Kitty');

// Animal { name: 'Kitty', walk: [Function (anonymous)] }
console.log(MyAnimal);
// [Animal] Kitty walking
MyAnimal.walk();
// [Animal Prototype] undefined run
MyAnimal.run();

// { run: [Function (anonymous)] }
console.log(MyAnimal.__proto__);
// [Object: null prototype] {}
console.log(MyAnimal.__proto__.__proto__);
// null
console.log(MyAnimal.__proto__.__proto__.__proto__);

在 ES 规范中,每个变数物件都有 __proto__,只有 Function 函数 才有 prototype 属性

当建立 Function 函数 的时候,会自动加入 prototype 属性

所以使用 new FunctionName() 的方式建立物件时,在建立完 Instance 后,会继承 Function 函数 中所有的 prototype 属性和方法,将自己的 __proto__ 指向 prototype

透过 prototype 储存要共享的属性和方法,也可以设定 prototype 去指向既有的物件

所以:

  • Function 本身就是函数,Function.__proto__ 标准的建构目标是 Function.prototype
  • Function.prototype.__proto__ 标准的建构目标是 Object.prototype
  • 每个函式的 prototype 物件,会有一个 constructor 属性,指回到这个函式。例如 MyFunc.prototype 物件的 constructor 属性,会指向 MyFunc函式
  • 每个物件都有一个 __proto__ 内部属性,指向它的继承而来的原型 prototype 物件

Prototype

mollypages.org 可以看到这张图

  • Function.prototypeFunction.__proto__ 都指向 Function.prototype
  • Object.prototypeObject.__proto__ 都指向 Object.prototype
  • Object.prototype.__proto__ === null

结论

  • __proto__ 是物件实际在使用的原型链,去使用 prototype chain 原型链 解析方法
  • prototype 是函式物件在使用 new 建立物件时,用来建立 __proto__
function Animal(name) {
    this.name = name;
}


let myAnimal = new Animal('Kay');

// true
console.log(myAnimal.__proto__ === Animal.prototype);
// true
console.log(myAnimal.prototype === undefined);

prototype 原理

Object.prototype 是原型鍊的顶端物件

// true
console.log(Object instanceof Function);
// true
console.log(Function instanceof Object);
// true
console.log(Function.prototype.__proto__ == Object.prototype);
// false
console.log(Function.prototype == Object.prototype);

Function.prototypeFunction.__proto__ 为同一对象

// true
console.log(Function.prototype === Function.__proto__);
// true
console.log(Function.prototype === Object.__proto__);
// true
console.log(Function.prototype === Array.__proto__);

Object/Array/String 等等建构函式本质上和 Function 一样,都是继承 Function.prototype

Function.prototype 直接继承 root(Object.prototype)

// true
console.log(Object instanceof Function);
// true
console.log(Function instanceof Object);
// true
console.log(Function instanceof Function);

// true
console.log(Array instanceof Function);
// true
console.log(Array instanceof Object);
// false
console.log(Function instanceof Array);
// false
console.log(Object instanceof Array);

ObjectFunction 的鸡和蛋的问题

导致 Function instanceof ObjectObject instanceof Function 都为 true 的原因

1. Function.prototype 不同于一般函式的函式

The Function prototype object is itself a Function object (its [[Class]] is “Function”) that, when invoked, accepts any arguments and returns undefined.

The value of the [[Prototype]] internal property of the Function prototype object is the standard built-in Object prototype object (15.2.4). The initial value of the [[Extensible]] internal property of the Function prototype object is true.

The Function prototype object does not have a valueOf property of its own; however, it inherits the valueOf property from the Object prototype Object.

  • Function.prototype一般函式 一样可以呼叫调用,接受任何的参数,且也可回传 undefined
  • 一般函式Function 的 Instance 实例,表示 一般函式 继承 Function.prototypeCustomFunction.__proto__ == Function.prototype
  • Function.prototype 继承 Object.prototype
  • Function.prototype.prototypenull

所以 Function.prototype 是另类的函数,可以独立优先于 Function 产生

// true
console.log(Object instanceof Function);
// true
console.log(Function instanceof Object);
// {}
console.log(Function.__proto__);
// [Object: null prototype] {}
console.log(Function.__proto__.__proto__);
// null
console.log(Function.__proto__.__proto__.__proto__);

// {}
console.log(Function.prototype);
// [Object: null prototype] {}
console.log(Function.prototype.__proto__);
// null
console.log(Function.prototype.__proto__.__proto__);

// undefined
console.log(Function.prototype.prototype);

2. Object 本身是建构函式,是 Function 的实例

The value of the [[Prototype]] internal property of the Object constructor is the standard built-in Function prototype object.

The value of the [[Prototype]] internal property of the Object prototype object is null, the value of the [[Class]] internal property is “Object”, and the initial value of the [[Extensible]] internal property is true.

Object.__proto__ 就是 Function.prototype

// true
console.log(Object instanceof Function);
// {}
console.log(Object.__proto__);
// [Object: null prototype] {}
console.log(Object.__proto__.__proto__);
// null
console.log(Object.__proto__.__proto__.__proto__);
// [Object: null prototype] {}
console.log(Object.prototype);
// null
console.log(Object.prototype.__proto__);


// {}
console.log(Function.prototype);
// {}
console.log(Function.__proto__);
// [Object: null prototype] {}
console.log(Function.__proto__.__proto__);

// true
console.log(Object.__proto__ == Function.prototype);
// true
console.log(Object.__proto__ == Function.__proto__);
// true
console.log(Object.__proto__.__proto__ == Function.__proto__.__proto__);

所以 Function 与 Object 关係是

  1. 先有 Object.prototype(原型链顶端)
  2. Function.prototype 继承 Object.prototype 而产生
  3. 最后,FunctionObject 和其它建构函数继承 Function.prototype 而产生

Object prototype 研究

Object.prototype 不是 Object 的实例

// 取得物件 prototype
const root = Object.prototype;

// [Object: null prototype] {}
console.log(root);
// null : 取得 root prototype
console.log(Reflect.getPrototypeOf(root));
// {} : 取得 Object prototype
console.log(Reflect.getPrototypeOf(Object));
// object : 取得 root 类型
console.log(typeof root);
// false : root 是否为 Object 实例
console.log(root instanceof Object);

Object.prototype 是对象,但不是透过 Object 函数建立的

// Object(0) []
console.log(Array.prototype);
// Object [Map] {}
console.log(Map.prototype);
// [Object: null prototype] {}
console.log(Object.prototype);
// {}
console.log(Function.prototype);
  • Array.prototype 是阵列格式的 Object
  • Map.prototypeMap 格式的 Object
  • Function.prototype 反过来继承 {} (Object)
const funcPrototype = Function.prototype;
const func_prototype_ = Function.__proto__;
function test() {};

// true
console.log(test.__proto__ === funcPrototype);
// true
console.log(Array.__proto__ === funcPrototype);
// true
console.log(func_prototype_ === funcPrototype);
  • Function 本身也是 function
  • Function.prototype 是所有 function 的原型(包括 Function 自己的原型)
  • 但反过来,Function.prototypeFunction 没有反向关係(正向 Function 继承 Function.prototype

所以 Function.prototypeFunction.__proto__ 相同,不代表 Function 这个函数是由自己本身去建立的

是先有 Function.prototype 这个对象(也是函数),然后才有其他函数

Array.prototype 是阵列

// Object(0) []
console.log(Array.prototype);
// false
console.log(Array.prototype instanceof Array);

const arrayPrototype = Array.prototype;
arrayPrototype.push('Kay');
// Object(1) [ 'Kay' ]
console.log(arrayPrototype);
// true
console.log(Array.isArray(arrayPrototype));

ES6 使用类别实现继承

class Animal {
    constructor (name){
        this.name = name
    }
    walk = () => {
        console.log(`[Animal] ${this.name} walking`);
    }
}

class Rabbit extends Animal {
    constructor (name){
        super(name)
    }

    jump = () => {
        console.log(`[Rabbit] ${this.name} jump`);
    }
}

let myRabbit = new Rabbit('Kay');

// [Animal] Kay walking
myRabbit.walk();
// [Rabbit] Kay jump
myRabbit.jump();

// Rabbit { walk: [Function: walk], name: 'Kay', jump: [Function: jump] }
console.log(myRabbit);
// Animal {}
console.log(myRabbit.__proto__);
// {}
console.log(myRabbit.__proto__.__proto__);
// [Object: null prototype] {}
console.log(myRabbit.__proto__.__proto__.__proto__);
// null
console.log(myRabbit.__proto__.__proto__.__proto__.__proto__);

常见问题

为什麽不直接用 __proto__ 取得原型物件?

__proto__ 虽然被几乎所有的浏览器支援,但它仍是非标准属性;透过 Object.getPrototypeOf 取得物件的原型是比较正确的方法。

什麽是 __proto__

由 ES6 开始成为 Object 的原生属性,直接对 [[Prototype]] 进行读写。

什麽是 prototype

一个 Object,当 new 一个 instance 时,会被用作指向 __proto__ 作为 instance 继承的属性

prototype 只存在于 constructor functions,在 instance 上不存在。

相反的,__proto__ 则出现在所有物件。

参考资料