updateTime: 2019-4-11 01:15javascript
updateContent: 繼承的實現,優化部份內容細節html
updateTime: 2019-4-15 00:11java
updateContent: 解析完美繼承,es6繼承內部一探es6
updateTime: 2019-4-17編程
updateContent: js內置類型構建過程json
讀了本文後,你會對js中的屬性追溯機制,繼承原理,構造函數和類,萬物皆對象,有必定深度理解。c#
爲何要讀這些? 爲了提升代碼的複用,js中的繼承,函數封裝,複製extends,混入mixin,借用call/apply,都是爲了代碼的複用,爲了更高效和更省時的編程!一個好的FEer必須跨過這些!segmentfault
js在es6以前中沒有類的關鍵詞,在一些須要進行面向對象編碼場景中經過js中一種引用數據類型Function(函數類型)來實現類。原型,英文名prototype,一般出如今一個函數類型的屬性中,看遍六大基本類型,三大引用類型,惟有函數類型天生擁有屬性prototype。這個屬性被稱爲原型。api
js爲了實現繼承而設計了原型鏈,經過對原型鏈的查找,來實現公共區域屬性方法公用,實例間方法屬性私用的目的。原型對象,就是一些公用方法和屬性的集合處。瀏覽器
能夠經過簡單的幾個公式
是否是很好奇這幾個公式怎麼來的,公式的源式通常源自現實實踐(工)的總結或是理論規範(理)的制定,而這幾個公式的源式就來自於咱們的章節六——js內置類型構建過程。
-爲何我要用構造方法函數這麼拗口的名稱來稱呼constructor?(便於區分概念引入class)
prototype做爲一個函數對象所擁有的對象類型屬性,他內部有什麼呢,ok,上碼
function Foo () {
}
console.dir(Foo)
/*
ƒ Foo()
arguments: null
caller: null
length: 0
name: "Foo"
prototype: {constructor: ƒ}constructor: ƒ ()
__proto__: Object__proto__: ƒ ()apply: ƒ apply()arguments: (...)bind: ƒ bind()call: ƒ call()caller: (...)constructor: ƒ Function()length: 0name: ""toString: ƒ toString()Symbol(Symbol.hasInstance): ƒ [Symbol.hasInstance]()get arguments: ƒ ()set arguments: ƒ ()get caller: ƒ ()set caller: ƒ ()__proto__: Object[[FunctionLocation]]: <unknown>[[Scopes]]: Scopes[0]
[[FunctionLocation]]: VM535:1
[[Scopes]]: Scopes[1]
*/
複製代碼
能夠看到,輸出了prototype中擁有constructor屬性,而這個對象指向的就是Foo自己!
固然還有一個屬性,那就是做爲對象都會擁有的__proto__!
一個對象的__proto__指向該對象的構造函數的prototype,該構造函數的prototype中有一個__proto__繼續指向該構造函數的原型對象(父類)的prototype,一直到最頂級的對象(基類)Object的——原型。
即便是天不生我Object也得靠着__proto__來尋找父類原型(Function.prototype)。可是,做爲創造者的Function類,卻也得按照js的規範定義中來走,即全部對象經過__proto__追溯,在盡頭大門前坐着的守護者,永遠是——Object。
按照盡頭守護原理(此句可忽略),規範產生了如下定義,Function.__proto__ === Object.prototype, 而, 門外的僞神,則只有守護者Object才能夠經過自身被賦予的prototype來直接觸摸代理神的原型,而代理神的原型就是——null(神,又名空空,js的本源,js世界的盡頭)!
這裏解釋下小標題: 由於Object是萬物的原型鏈的查找源,即萬物均可以從Object上去找他們對應的一些公共特性,那麼萬物皆對象~(順便猜下爲何僞神和神沒有被傳開,由於是做爲世界的抽象級別存在,比較難理解,因此沒有宣揚)
世界從某個角度來看分爲靜和動,全部的物種類(靜)仍是動做類(動),所建立的引用數據類型,在追溯(findFather)的動做上,都是按照如下兩個步驟來遞歸執行:
1. 判斷本次所指向的prototype是否爲是盡頭(神,null),若爲盡頭則執行返回
2. 若不爲盡頭,則執行.__proto繼續向上查找
findFaher (obj) { // 用一行代碼來描述就是:
obj.__proto__ === null ? (() => {})() : findFather(obj.__proto)
}
let objOne = {}
function FnOne () {}
let fnOneInstance = new FnOne()
function findChain (obj, key) {
console.info(Object.prototype.toString.call(obj))
obj.__proto__ === null ? (()=>{})() : findChain(obj.__proto)
}
findChain(objOne)
findChain(fnOneInstance)
findChain(FnOne)
findChain(Object)
console.log(typeof objOne)
console.log(typeof fnOneInstance)
console.log(typeof FnOne)
console.log(typeof Object)
// 好奇你的腦機輸出結果與實際結果是否對等嗎?好奇的話就快點複製代碼打開調試器,黏貼回車看一看吧!=-=複製代碼
上面的例子中,FnOne類和Object類的父類原型是Function,而爲何FnOne類的對象fnOneInstance輸出的原型鏈上爲何沒有Function呢?一塊兒來看看做爲構造函數在new的過程作了什麼吧
屌屌的Function函數對象我指我本身,即Function.__proto===Function.prototype,爲何呢,由於Function即有屬性prototype又有__proto__同時也是屬於js變量中的一員,由於prototype的強特徵,因此叫函數對象。
屬性查找機制(先從自己找,沒有後在原型鏈上找,沒有則爲未定義)就是利用了原型鏈,一樣繼承共有成員也是利用了原型和原型鏈。
私有成員即該對象的成員(由new中第三步,Father.call(objInstance)來添加(this.xxx = xxx;))
有趣的地方,啊,無限循環的條件必然是有一個等式存在(無限遞歸),否則就是悖論!
Function.__proto__.constructor === Function
Object.__proto__.__proto__.constructor === Object
a = {}
1. a.__proto__ === Object.prototype
b = new Foo()
1. b.__proto__ === Foo.prototype
2. b.__proto__.__proto__ === Foo.prototype.__proto__ === Object.prototype (did u find it ? b not find the prototyperty of Function in find prototypeChain.)
but
Foo.__proto__ === Function.prototype
Object.__proto__ === Function.prototype === Function.__proto__
Function.__proto__ === Function.prototype
Foo.__proto__ === Function.__proto__ === Object.__proto__
Function.prototype .__proto === Object.prototype === Object.__proto__.__proto__ === Foo.__proto__._proto__
Object.prototype.__proto__ === null.
so
Object.prototype === Object.__proto__.__proto__ === function(){} (一個沒有prototype的匿名函數)
There is a special Function that don't have property of prototype. And this Function is the end of 原型鏈(prototypeChain), cause its __proto__ === null.
查找對象屬性時,根據__proto__一級級向上查找,遞歸的終點是null,即上面的Object.prototype.__proto.
一個用於new實例對象的函數對象叫作構造函數。
構造方法,默認位於構造函數上的prototype對象中。因此若是原型改變了,那麼該函數的construtor也就改變了。
那麼一個構造函數的constructor位於哪裏呢,經過追溯,發現他位於Function.prototype上!
Foo = () => {}
Foo.constructor === Function.prototype.constructor
f = new Foo()
f.constructor === Foo.prototype.constructor //證實構造方法位於構造函數的prototype上
f.constructor === Foo.prototype.constructor === Foo // 構造函數即構造方法複製代碼
原來是返回一個追溯第一次指向父類構造函數的原型的對象啊,而且還經過3步驟具有了構造函數中的私有成員建立,經過2共用了父類的共有屬性,而且其祖先類的共有屬性也能夠經過原型鏈公用了哦。
這就是繼承了嗎?在業務複雜場景中,不能知足呀,那麼讓咱們來研究一下繼承吧!
一個經典的問題,傳統的繼承使用了原型鏈,進行類繼承,簡單羅列下2級目錄
function Animal(color) { this.color = color; this.name = 'animal'; this.type = ['pig', 'cat'];}Animal.prototype.greet = function(sound) { console.log(sound);}function Dog(color) { Animal.apply(this, arguments); this.name = 'dog';}/* 注意下面兩行 */Dog.prototype = Object.create(Animal.prototype);Dog.prototype.constructor = Dog;
Dog.prototype.getName = function() { console.log(this.name);}
var dog = new Dog('白色'); var dog2 = new Dog('黑色');
dog.type.push('dog'); console.log(dog.color); // "白色"console.log(dog.type); // ["pig", "cat", "dog"]
console.log(dog2.type); // ["pig", "cat"]console.log(dog2.color); // "黑色"dog.greet('汪汪'); // "汪汪"複製代碼
// 最優化 聖盃模式
var inherit = (function(c,p){ // c : target p: origin var F = function(){};
return function(c,p){
F.prototype = p.prototype;
c.prototype = new F();
c.uber = p.prototype; // 爲了讓咱們知道Target真正繼承自誰
c.prototype.constructor = c; // 把Target的構造函數指向歸位
}
})();複製代碼
export default class {
params = {};
set(key, value) {
this.params[key] = value;
return this;
}
done () {
const headers = new Header();
fetch( `/api?params=${encodeURIComponent(JSON.stringify(this.params))}` , {
method: "GET",
mode: "cors",
credentials: "include",
headers
});
}
}
//繼承並覆蓋done方法
const temp = new class extends access {
done() {
const headers = {
'Accept': 'application:/json',
'content-type': 'application/x-www-form-urlencoded;charset=utf-8'
};
fetch( `/api?params=${encodeURIComponent(JSON.stringify(this.params))}` , {
method: "GET",
mode: "cors",
credentials: "include",
headers
});
}
}();
複製代碼
what
講真的,我以爲弄清什麼是完美繼承纔是最難的,由於,網上不少例子只講瞭如何實現完美繼承,講了不少繼承,像上面同樣,可是從實用角度出發, 其實,不少時候,咱們弄明白原理是爲了使用甚至創造輪子,可是更多時候從業務出發,咱們是更須要記住場景的對應使用,那麼用一句話講清完美繼承的場景就是:
使得子類能夠繼承父類的共有變量,也能夠將父類中的私有變量在本身中獨立的建立一份,同時可使自身的原型保持獨立性,再添加子類本身的共有變量,豐富本身。
是否是看的有點暈,好的,更加簡潔易懂的描述:
1. 使子類繼承父類的共有變量(原型屬性),使子類繼承父類的私有變量(對象成員);
2. 使子類能夠豐富自身,保持自身的私有變量和共有變量(原型)與父類間的獨立性。
是否是寫的有點不接地氣?好!再用通俗的話(俗稱:人話)描述一下:
父類有的子類有(copy了一份),子類有的父類沒有。
你看,是否是就像是父子關係之間的傳承,讀到這裏,腦子裏是否是蹦出了兩個字,原來這就是——繼承!什麼叫繼承: 完成了從上層的傳承後,再保持自身的豐富(是否是很像基因!!)
why
場景的複雜,業務的須要,效率的提升,js世界的設計,面向對象的須要
how
1. 使用對子類prototype賦值父類的實例來實現獨立的共有變量(原型)繼承,同時因爲構造函數位於自身的ptototype上,使用對子類prototype.constructor賦值子類來保持獨立性(以便於子類自身原型的豐富)
2. 使用父類構造函數在子類構造函數中第一行執行(因爲this指向的不一樣,則實現了獨立性)實現父類私有變量在子類中的繼承
do
實現的話上面已經介紹了,其實上面三種方案裏到處充斥着how的執行,就算是語法糖的es6,其extends也就是實現了1,而後還須要在子類中首行執行super(),這不就是在實現2嗎!
還不懂?
extends super到底幹了什麼?別急,這就爲你解析!
oneDemo
class People{
constructor(name, age) {
this.name = name;
this.age = age;
}
say(){
alert("hello")
}
static see(){
alert("how are you")
}
}
class xiaowang extends People{
constructor(){
super()
}
say(){alert("not hello , i am KingXiao")} // 豐富
}複製代碼
extends
今天有點晚,改天詳細,具體原理本身分析就是建立一個自執行函數,將父類穿進去,而後將父類Create一份將實例賦值給子類的原型,而後再將子類原型上的構造函數屬性賦值爲子類。作完這系列操做後返回子類遇到了calss,而後就是子類自身的豐富了。(父類原型方法繼承)
super
super作了什麼呢,其實就是一句es5的語法糖,People.call(this) 父類對象方法繼承
看到這裏,是否是對於js的繼承,甚至於面向對象中的繼承有了很是透徹的理解了呢,愣着幹嗎?點贊呀!哈哈0-0媽媽不再怕我不會寫代碼了
學習自 木易楊說進階探究Function&Object雞蛋問題
JavaScript 內置類型是瀏覽器內核自帶的,瀏覽器底層對 JavaScript 的實現基於 C/C++,那麼瀏覽器在初始化 JavaScript 環境時都發生了什麼?
沒找到官方文檔,下文參考自 segmentfault.com/a/119000000…
一、用 C/C++ 構造內部數據結構建立一個 OP 即 (Object.prototype) 以及初始化其內部屬性但不包括行爲。
二、用 C/C++ 構造內部數據結構建立一個 FP 即 (Function.prototype) 以及初始化其內部屬性但不包括行爲。
三、將 FP 的 [[Prototype]]
指向 OP。(Function.prototype.___proto__ = Object.prototype)
四、用 C/C++ 構造內部數據結構建立各類內置引用類型。
五、將各內置引用類型的[[Prototype]]指向 FP。(Array.__proto__ = Function.prototype)
六、將 Function 的 prototype 指向 FP。(FP = Function.ptototype)
七、將 Object 的 prototype 指向 OP。(OP = Object.prototype)
八、用 Function 實例化出 OP,FP,以及 Object 的行爲並掛載。(行爲掛載)
九、用 Object 實例化出除 Object 以及 Function 的其餘內置引用類型的 prototype 屬性對象。(Array.prototype = new Object() ...)
十、用 Function 實例化出除Object 以及 Function 的其餘內置引用類型的 prototype 屬性對象的行爲並掛載。(行爲掛載)
十一、實例化內置對象 Math 以及 Grobal
至此,全部內置類型構建完成。
本文中引用了一下連接中部份內容做爲參考
ghmagical.com/article/pag… © ghmagical.com
一遍熬夜一邊查寫一遍理解寫出本身的想法,有的可能錯了,歡迎指出!
若是對你有幫助,能點個贊那就太感謝了!^_^
tot我寫的真的不好嗎,爲何閱讀量進百麼有一個點贊也沒有一個評論TOT。