在JavaScript中,函數是一個包含屬性和方法的Function
類型的物件。而原型(Prototype)
就是Function
類型物件的一個屬性。
在函數定義時就包含了prototype
屬性,它的初始值是一個空物件。在JavaScript中並沒有定義函數的原型類型,所以原型可以是任何類型。
原型是用來保存物件的共享屬性和方法的,原型的屬性和方法並不會影響函數本身的屬性和方法。
// Function類型的屬性->所有函數都具有的屬性console.log(Function.prototype);//[Function] //定義函數function fn() { console.log('this is function'); } //原型的預設值是空物件console.log(fn.prototype);//fn {} // 函式包含建構函式-> 所有參考型別其實都是建構函式console.log(Number.prototype); //[Number: 0] console.log(Object.prototype);//{}
透過以下兩種方式可以取得物件的原型,從而設定共享的屬性與方法:
prototype
屬性getPrototype
(obj)方法。function fn() { console.log('this is function'); } //使用存取物件的屬性語法結構console.log(fn.prototype);//fn {} console.log(fn['prototype']);//fn {} //Object型別提供getPrototypeOf()方法console.log(Object.getPrototypeOf(fn));//[Function]
Object.getOwnPropertyDescriptors()
方法用來取得一個物件的所有自身屬性的描述符。
var result = Object.getOwnPropertyDescriptor(Object.prototype,'constructor'); console.log(result) //輸出結果如下: //{ // value: [Function: Object], // writable: true, // enumerable: false, // configurable: true // }
constructor是在创建函数的时候自动添加的,指向构造函数本身
透過以下兩種方式可以設定原型的屬性和方法:
建構子.prototype.屬性名稱= 屬性值;建構子.prototype.方法名稱= function(){} ;
當我們需要在原型上添加很多很多屬性的時候一遍一遍的去寫
构造函数.prototype.属性名
太麻煩了,可以直接修改整個prototype
構造函數.prototype = { 屬性名:屬性值, 方法名稱:function(){}}
function foo () {}foo.prototype = { constructor: foo, name: 'jam', age: 18, address: '北京市'}var fn = new foo()console.log(fn.address) // 北京市
每個物件中都會具有一個isPrototypeOf()
方法,該方法用來判斷一個物件是否是另一個物件的原型。
範例程式碼如下: // 以初始化器方式定義物件var obj = { name:'jam' } // 定義建構子function Hero() {} // 將物件obj賦值給建構子Hero的原型Hero.prototype = obj; // 透過建構函式建立物件var hero = new Hero(); // isPrototypeOf()方法判斷指定物件是否為另一個物件的原型var result = obj.isPrototypeOf(hero); console.log(result);//true
驗證了
obj
物件是hero
物件的原型
接下來我們使用一段程式碼來展開對原型鏈的認識:
場景:查找obj物件身上的address屬性js執行的步驟: 1. 會觸發get操作2. 在目前的物件中查找屬性3. 如果沒有找到,這個時候會去原型鏈(__proto__)物件上查找1. 查找到結束2. 沒查找到一直順著原型鏈查找,直到查找到頂層原型(頂層原型是什麼暫時賣個關子)
var obj = { name: 'jam', age: 19 } /* 需求:找出obj物件身上的address屬性*/ // 原型鏈一層一層向上查找,如果一直找不到,直到查找到頂層原型結束obj.__proto__ = {} obj.__proto__.__proto__ = {} obj.__proto__.__proto__.__proto__ = { address: '北京市' } console.log(obj.address) // 北京市console.log(obj.__proto__.__proto__.__proto__) // { address: '北京市' }
最終查找到address屬性
那么这里有一个问题,如果一直没有查到,会无穷尽的去查找吗?接下来我们就来了解一下
上面我們說到,順著原型鏈不會無休止的去查找,當查到頂層原型的時候,如果還沒查到就會返回undefined
。
那麼頂層原型是什麼呢?
範例程式碼如下:
var obj = { name: 'jam' }console.log(obj.__proto__) // {}console.log(obj.__proto__.__proto__) // null
字面量物件obj的原型是:
{}
。{}
就是頂層的原型當我們繼續向上打印__proto__
時,返回一個null值,就證明上一層就已經是頂層原型瞭
如下圖是針對第一段代碼中缺少的頂層原型做的補充:
顶层原型就是Object.prototype
3.1 那麼什麼地方是原型鏈的盡頭呢?例如第三個物件是否也有原型__proto__
屬性呢?
var obj = {name:'jam'}obj.__proto__ = {}obj.__proto__.__proto__ = {}obj.__proto__.__proto__.__proto__ = {}console.log(obj.__proto__.__proto__.__proto__ = {}conto__) // {}
我們發現上面印出結果為空对象{}
var obj = { name: 'jam', age: 19 } console.log(obj.__proto__) // {} console.log(Object.prototype) // {} console.log(obj.__proto__ === Object.prototype) // true
Object是所有類別的父類別所以obj.__proto__其實就是Object.prototype ,
console.log(obj.__proto__ === Object.prototype) // true
我們可以看出結果Object.prototype就是頂層原型
{}
3.2 那麼我們可能會問: {}
原型有什麼特殊的嘛?
console.log(obj.__proto__.__proto__.__proto__.__proto__.__proto__) // null
Object.prototype
的結果為空物件{},但它不是空的,只是裡面的屬性不可枚舉而已,例如我們就列印constructor
屬性看看<!-- 可以看出是有constructor屬性的,並不是空的-->console.log(Object.prototype.constructor) // [Function: Object] <!-- constructor 指回了Object -->
Object.getOwnPropertyDescriptors()
方法來取得Object.prototype
中的所有自身屬性的描述符。 console.log(Object.getOwnPropertyDescriptors(Object.prototype)) // 如下長截圖所示