有关 Object Prototypes 对象原型你不得不知道的

如果进入到Javascript 面向对象的领域,那么对象原型Object Prototypes你就一定会接触到,我也是以前看的云里雾里的,总感觉空洞,和实际使用的场景结合不起来,但最近看了一篇文章,感觉清楚多了,所以分享给大家,希望有所帮助吧。

1 什么是对象原型?

对象都有一个原型属性,通过__proto__(称为dunder proto)获得,这个属性强烈不建议直接通过dot符读取或者修改, MDN 里面有特别强调,这个原型属性是一个指针,指向另一个对象

var obj = {};
console.log(obj.__proto__);
// -> {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}

对象原型有三点要注意:

1 每个对象都有一个__proto__属性

2 对象字面量的__proto__恒等于Object.prototype

3 Object.prototype的__proto__ 等于null

2 __proto__有什么用?

用于对象属性的查找,Javascript engine查找对象属性的时候,首先查找对象本身,如果没有,会查找对象原型所指向的对象,如果仍然没有找到,会查找原型的原型,直到某个对象的__proto__为null,这就是原型链

var obj = {};
obj.__proto__.testValue = 'Hello!';  // 注意,这里的操作是为了演示,不推荐操作__proto__

console.log(obj); // -> {}
console.log(obj.testValue); // -> Hello!

3 有关的方法和属性

obj.hasOwnProperty()

判断某个属性是否是obj自己的

var obj = {};
obj.__proto__.testValue = 'Hello!'; // 注意,这里的操作是为了演示,不推荐操作__proto__
 
console.log(obj.hasOwnProperty('testValue'));
// -> false

console.log(obj.hasOwnProperty('__proto__'));
// -> false

console.log(obj.__proto__.hasOwnProperty('testValue'));
// -> true

 

Object.getOwnPropertyNames()

获得对象自己的属性名组成的数组

var obj = { prop: 'Hi there!' };
obj.__proto__.testProp = 'Hello!';
console.log(Object.getOwnPropertyNames(obj)); // -> [ 'prop' ]

Object.getPrototypeOf()

获得对象的原型

var obj = {};
console.log(Object.getPrototypeOf(obj) === obj.__proto__);
// -> true

 

Object.setPrototypeOf()

设置原型对象

var obj = {};
var protoObj = {};
Object.setPrototypeOf(obj, protoObj);
console.log(Object.getPrototypeOf(obj) === protoObj);
// -> true

4 原型继承

Javascript中的继承是通过原型完成的...

4.1 Function Prototypes & new

函数的prototype不同于它的__proto__属性,看起来有点绕

functions's prototype's __proto__ === Object.prototype

function fn() {}
var protoOfPrototype = Object.getPrototypeOf(fn.prototype);

// protoOfPrototype === fn.prototype.__proto__
console.log(protoOfPrototype === Object.prototype);
// -> true

但我们使用new 调用function 时, Javascript engine会设置this的__proto__ 等于function的prototype,这就是继承的关键

function PersonConstructor(name, age) {
    // this = {};
    // this.__proto__ = PersonConstructor.prototype;

    // Set up logic such that: if
    // there is a return statement
    // in the function body that
    // returns anything EXCEPT an
    // object, array, or function:
    //     return 'this' (the newly
    //     constructed object)
    //     instead of that item at
    //     the return statement;

    this.name = name;
    this.age = age;

    // return this;
}

到这里,可以得出三点:

1 使用new 创建的函数对象的__proto__等于该函数的prototype

2 function's prototype's__proto__ 等于Object.prototype

3 Object.prototype's__proto__等于null

function Fn() {}
var obj = new Fn();

var firstProto = Object.getPrototypeOf(obj);
// firstProto === obj.__proto__
console.log(firstProto === Fn.prototype); // -> true

var secondProto = Object.getPrototypeOf(firstProto);
// secondProto === obj.__proto__.__proto__
console.log(secondProto === Object.prototype); // -> true

var thirdProto = Object.getPrototypeOf(secondProto);
// thirdProto === obj.__proto__.__proto__.__proto__
console.log(thirdProto === null); // -> true

注意:区别于1中的对象字面量

4.2 实现继承

我们可以安全地操作function的prototype,比如修改function's prototype的方法或属性,那么通过new 创建的对象就会继承这些方法或属性

function Fn() {}

Fn.prototype.print = function() {
    console.log("Calling Fn.prototype's print method");
};


var obj = new Fn();
obj.print(); // -> Calling Fn.prototype's print method

如果我们把print方法写在Fn的构造函数里呢?效果是不是一样?

function Fn() {
    this.print = function() {
        console.log("Calling Fn.prototype's print method");
    };
}

var obj = new Fn();
obj.print(); // -> Calling Fn.prototype's print method

不同点是:如果print写在构造函数里,那么通过new生成的每个对象都有一份print方法,在内存和性能上和写在原型上是不一样的,写在原型是是所有new出来的对象共用一份print方法

下面是一份对比试验,可以看出创建print方法在prototype上的对象比创建print方法在构造函数中的对象快一倍左右

function FuncOnThis () {
  this.print = function () {
    console.log('Calling print')
  }
}

let arr = []
console.time('FuncOnThis')
for (let i = 0; i < 2000000; i++) {
  arr.push(new FuncOnThis())
}

console.timeEnd('FuncOnThis')


// FuncOnThis: 763.890ms

function FuncOnProto () {
}

FuncOnProto.prototype.print = function () {
  console.log('Calling print')
}

console.time('FuncOnProto')

let arr2 = []
for (let i = 0; i < 2000000; i++) {
  arr2.push(new FuncOnProto())
}

console.timeEnd('FuncOnProto')

// FuncOnProto: 334.594ms

4.3 字面量的__proto__

现在已经知道object's __proto__等于创建该对象的function's prototype, object 字面量来自Object, array 来自 Array, function来自Function

console.log(
    Object.getPrototypeOf({}) === Object.prototype
); // -> true

console.log(
    Object.getPrototypeOf([]) === Array.prototype
); // -> true

console.log(
    Object.getPrototypeOf(function fn() {})
    === Function.prototype
); // -> true

4.4 构造函数constructor

function's prototype都有一个constructor属性,这是一个指针,指向函数本身

function Fn() {}
console.log(Fn.prototype.constructor === Fn);
// -> true

所以,使用new创建的对象,它的构造函数也指向该函数,沿着原型链向上查找,会找到Fn's prototype’s constructor

function Fn(){}
var obj = new Fn();

console.log(obj.constructor);
// -> [Function: Fn]

通过对象的构造函数属性,可以知道该对象是哪个函数创建的

function Fn() {};

var normalObj = {};
var fnObj = new Fn();

console.log(normalObj.constructor); // -> [Function: Object]
console.log(fnObj.constructor); // -> [Function: Fn]

4.5 自定义原型创建对象

Object.create(param)

这个函数用于创建对象,接收一个对象作为参数,创建的新对象的__proto__等于传入的参数

var prototypeObj = {
    testValue: 'Hello!'
};

var obj = Object.create(prototypeObj);
console.log(obj); // -> {}

console.log(
    Object.getPrototypeOf(obj) === prototypeObj
); // -> true

console.log(obj.testValue); // -> 'Hello!'

Object.create()提供了一种更灵活的方式去扩展原型链,使得对象不仅继承于function's prototype, 而是任何object

总结:

1 function's prototype 不同于 __proto__

2 functions' prototype's __proto__ 等于Object.prototype

3 Object.prototype's __proto__等于null

4 使用new创建函数对象的__proto__等于该构造函数的prototype

参考资料:

https://www.educative.io/collection/page/5679346740101120/5707702298738688/5665117697998848

https://www.educative.io/collection/page/5679346740101120/5707702298738688/6330230964748288

猜你喜欢

转载自my.oschina.net/u/2510955/blog/1581754