若是進入到Javascript 面向對象的領域,那麼對象原型Object Prototypes你就必定會接觸到,我也是之前看的雲裏霧裏的,總感受空洞,和實際使用的場景結合不起來,但最近看了一篇文章,感受清楚多了,因此分享給你們,但願有所幫助吧。javascript
對象都有一個原型屬性,經過__proto__(稱爲dunder proto)得到,這個屬性強烈不建議直接經過dot符讀取或者修改, MDN 裏面有特別強調,這個原型屬性是一個指針,指向另外一個對象java
var obj = {}; console.log(obj.__proto__); // -> {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
對象原型有三點要注意:數組
1 每一個對象都有一個__proto__屬性安全
2 對象字面量的__proto__恆等於Object.prototype函數
3 Object.prototype的__proto__ 等於null性能
用於對象屬性的查找,Javascript engine查找對象屬性的時候,首先查找對象自己,若是沒有,會查找對象原型所指向的對象,若是仍然沒有找到,會查找原型的原型,直到某個對象的__proto__爲null,這就是原型鏈this
var obj = {}; obj.__proto__.testValue = 'Hello!'; // 注意,這裏的操做是爲了演示,不推薦操做__proto__ console.log(obj); // -> {} console.log(obj.testValue); // -> Hello!
判斷某個屬性是不是obj本身的spa
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
得到對象本身的屬性名組成的數組prototype
var obj = { prop: 'Hi there!' }; obj.__proto__.testProp = 'Hello!'; console.log(Object.getOwnPropertyNames(obj)); // -> [ 'prop' ]
得到對象的原型指針
var obj = {}; console.log(Object.getPrototypeOf(obj) === obj.__proto__); // -> true
設置原型對象
var obj = {}; var protoObj = {}; Object.setPrototypeOf(obj, protoObj); console.log(Object.getPrototypeOf(obj) === protoObj); // -> true
Javascript中的繼承是經過原型完成的...
函數的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中的對象字面量
咱們能夠安全地操做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
如今已經知道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
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]
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