javascript深入理解系列(二)——__proto__,prototype原型以及原型鏈
javascript深入理解系列文章網址
ofollow,noindex">https://www.jianshu.com/p/451eed9094f5今天這節可能有點長,希望大家認真看,收穫會很多
1._ proto _由來
我們都知道javascript是一種面向物件的語言,面嚮物件語言的一大特點就是一切皆物件,我們常見的一些數字,陣列,字串等都是物件。但是雖然都是物件,但因為表現形式不一樣又分為普通物件和函式物件。
普通物件
var obj={ name:"xiaoming" };
輸出以後

image.png
函式物件
function Person(){ alert("你好"); }
輸出以後

image.png
從上面的例子我們可以看出普通物件和函式物件的定義和輸出後的樣子
我們再看一下typeof後普通物件和函式物件的區別
console.log(typeof obj); console.log(typeof(Person));

image.png
不管是普通物件還是函式物件,它們都有一個隱含屬性_ proto _ ,而這屬性就是我們通常說的原型(屬性),這就是_ proto _的由來
2.prototype的由來
對於函式物件,它們還會多一個prototype的屬性,它和以它為建構函式建立的普通物件的” proto “屬性等同,即例項物件的_ proto _指向它的建構函式prototype
function Person(name,age){ this.name=name; this.age=age; } var xiaoming=new Person("小明",10); console.log(xiaoming.__proto__==Person.prototype);
下面講一下prototype的作用
javascript是通過建構函式來實現例項物件的,例項物件的屬性和方法都是在建構函式內部來定義的
<script> function Person(name,age){ this.name=name; this.age=age; } var xiaoming=new Person("xiaoming",10); console.log(xiaoming.name); console.log(xiaoming.age); </script>
這樣寫的缺點是 但是同一個建構函式之間無法共享方法和屬性,造成了系統資源的浪費 ,比如說你建立兩個Person例項,他們都有一個共同的方法比如說睡覺,可是每生成一個例項就會重新生成一個睡覺的方法。所以這就有了prototype.
JavaScript 繼承機制的設計思想就是,原型物件的所有屬性和方法,都能被例項物件共享。這裡所說的原型也就是prototype了,這樣子就能節省記憶體了
這句話的理解就是我們把私有的屬性和方法定義在建構函式裡面,把公有的屬性定義在prototype上
3._ proto _和prototype的關係
一.所有構造器/函式的proto都指向Function.prototype,它是一個空函式(Empty function)
Number.__proto__ === Function.prototype// true Boolean.__proto__ === Function.prototype // true String.__proto__ === Function.prototype// true Object.__proto__ === Function.prototype// true Function.__proto__ === Function.prototype // true Array.__proto__ === Function.prototype// true RegExp.__proto__ === Function.prototype// true Error.__proto__ === Function.prototype// true Date.__proto__ === Function.prototype// true
這裡所說的所有當然也包括自定義的函式
var Dog = function(){}; console.log(Dog.__proto__==Function.prototype);
我們知道prototype是放置公有屬性和方法的地方,那就是說我們自定義的Dog函式原型繼承了Function函式的原型,比如說bind,call,apply,length方法
Function.prototype也是唯一一個typeof XXX.prototype為 “function”的prototype。其它的構造器的prototype都是一個物件。如下
console.log(typeof Function.prototype) // function console.log(typeof Object.prototype)// object console.log(typeof Number.prototype)// object console.log(typeof Boolean.prototype)// object console.log(typeof String.prototype)// object console.log(typeof Array.prototype)// object console.log(typeof RegExp.prototype)// object console.log(typeof Error.prototype)// object console.log(typeof Date.prototype)// object console.log(typeof Object.prototype)// object
上面我們提到Function.prototype是一個空函式,
輸出後為

image.png
console.log(Function.prototype.__proto__ === Object.prototype) // true
那麼也就是說構造器和函式都會繼承Object.prototype上的方法和屬性

image.png
最後Object.prototype的 proto 是誰?
Object.prototype.__proto__ === null// true
也就是說通過__proto的最頂端是null。
二、所有物件的 proto 都指向其構造器的prototype
先看看javascript內建構造器
var obj = {name: 'jack'} var arr = [1,2,3] var reg = /hello/g var date = new Date var err = new Error('exception') console.log(obj.__proto__ === Object.prototype) // true console.log(arr.__proto__ === Array.prototype)// true console.log(reg.__proto__ === RegExp.prototype) // true console.log(date.__proto__ === Date.prototype)// true console.log(err.__proto__ === Error.prototype)// true
再看看自定義的構造器
function Person(name) { this.name = name } var p = new Person('jack') console.log(p.__proto__ === Person.prototype) // true
每個物件都有一個constructor屬性,可以獲取它的構造器
function Person(name) { this.name = name } var p = new Person('jack') console.log(p.__proto__ === p.constructor.prototype) // true
需要注意的是
function Person(name) { this.name = name } // 重寫原型 Person.prototype = { getName: function() {} } var p = new Person('jack') console.log(p.__proto__ === Person.prototype) // true console.log(p.__proto__ === p.constructor.prototype) // false
這種情況是因為給Person.prototype賦值的是一個物件直接量{getName: function(){}},使用物件直接量方式定義的物件其構造器(constructor)指向的是根構造器 Object,Object.prototype是一個空物件{},{}自然與{getName: function(){}}不等
var p = {} console.log(Object.prototype) // 為一個空的物件{} console.log(p.constructor === Object) // 物件直接量方式定義的物件其constructor為Object console.log(p.constructor.prototype === Object.prototype) // 為true,不解釋
4.原型鏈
從上面的知識我們可以知道物件的_ proto _指向其建構函式的prototype,而建構函式的prototype指向Function.prototype,Function.prototype又指向Object.prototype,從而繼承Object上面的方法,就這樣物件到原型再到原型的原型,這種鏈式結構我們稱之為原型鏈
從所有構造器/函式的proto都指向Function.prototype那一章我們可以知道,原型鏈的頂層是null
那麼物件是如何獲取屬性和方法的呢, JavaScript 引擎先尋找物件本身的屬性,如果找不到,就到它的原型去找,如果還是找不到,就到原型的原型去找。如果直到最頂層的Object.prototype還是找不到,則返回undefined。如果物件自身和它的原型,都定義了一個同名屬性,那麼優先讀取物件自身的屬性,這叫做“覆蓋”(overriding)。
function Person(name,age){ this.name=name; this.age=age; } Person.prototype.color='white'; var xiaoming=new Person("小明",10); xiaoming.color='yello'; var xiaoli=new Person("小李",12); console.log(xiaoming.color); console.log(xiaoli.color);
從上面我們還可以得出結論,如果原型指向陣列,那麼我們就可以擁有陣列上方法
var MyArray = function () {}; MyArray.prototype = new Array(); MyArray.prototype.constructor = MyArray; var mine = new MyArray(); mine.push(1, 2, 3); mine.length // 3 mine instanceof Array // true