摘要:因為屬性查找是按照原型鏈查找,先查找自身再查找原型鏈,找到為止。用了等號,先給自身賦值,所以自身賦值成功了也不會繼續去原型鏈查找。因為都有各自的缺陷,所以就有一種組合繼承,將構造函數繼承和繼承混合起來,方法寫在父類的上,是比較常見的方法。
本文來自我的github 0.前言
這些都是js基礎進階的必備了,有時候可能一下子想不起來是什么,時不時就回頭看看基礎,增強硬實力。
1.this 1.1this指向誰最后調用,就指向誰
先簡單復習一次,this指向就那么幾種:
new 關鍵字
指向new 創建的對象
function F() { this.name = 1 } var f = new F()
call、apply、bind
指向傳入函數的第一個參數。a.call(b),函數a內部如果是要用到this。則這個this指向b
對象的方法
對象內部的方法指向對象本身
var obj = { value: 5, printThis: function () { console.log(this); } };
按值傳遞
指向全局
var obj = { value: 5, printThis: function () { console.log(this); } }; var f = obj.printThis f()如果出現上面對條規則的累加情況,則優先級自1至4遞減,this的指向按照優先級最高的規則判斷。
5.箭頭函數
指向箭頭函數定義時外層上下文
var obj = { value: 5, printThis: function () { return function(){ console.log(this)} } }; obj.printThis()//window var obj = { value: 5, printThis: function () { return () => console.log(this) } }; obj.printThis()()//obj2.call、apply、bind
前兩者都是一樣,只是參數表現形式不同,bind表示的是靜態的前兩者,需要手動調用
a.call(b,args)讓函數a執行上下文指向b,也就是b的屬性就算沒有a函數,也能像b.a(args)這樣子調用
方法大家都知道,我們不妨來自己實現一下這三個:
2.1 call實現再看一次概念,b沒有a方法,也就是沒有b.a,如果想要這個效果,那就利用這三個函數來改變執行上下文。于是我們就可以想到,要是自己實現一個,大概就是,給b強行加上這個a 的方法,然后拿到argument去調用:
Function.prototype.mycall = function(){ var ctx = arguments[0]||window||global//獲取上下文,call的第一個參數 var len = arguments.length var hash = new Date().getTime()//避免名字重復 ctx[hash] = this//將this緩存,this就是那個想在另一個上下文利用的函數 var result if(len === 1){ result = ctx[hash]()//如果后面沒有其他參數直接運行 } else{ var i = 1 var args = [] for(;iapply也是同理,而且少了數組這一步,更加簡單接下來我們看一下bind怎么實現:
Function.prototype.mybind = function(){ var ctx = arguments[0]||window||global var f = this var args1 = [] if(arguments.length>1){//預先填入的參數 var i = 1 for(;i < arguments.length;i++){ args1.push(arguments[i]) } } return function(){ var args2 = Array.prototype.slice.call(arguments)//call和apply我們都可以實現,這里就不再重復 return f.apply(ctx,args1.concat(args2))//將預先填入的參數和執行時的參數合并 } }此外,需要注意的,一個函數被bind后,以后無論怎么用call、apply、bind,this指向都不會變,都是第一次bind的上下文
3.從call到繼承首先,js沒有嚴格意義上的子類父類,實現繼承是依靠原型鏈來實現類似于所謂的類的效果。
3.1 call繼承(構造函數繼承)我們希望G繼承F,或者是說,開發的時候,由于G有很多屬性繼承F我們想偷懶,那么就可以這樣
function F(name,age){ this.name = name this.age = age } function G(name,age,a) { F.call(this,...arguments) this.a = a } var g = new G("a",12,1) //G?{name: "a", age: 12, a: 1}這個方法特別之處是,子類可以向父類構造函數傳參。但是,無法獲取F的原型上的屬性。
另外,方法也是寫在內部this.f = function(){}
也注定無法實現函數復用了,每一個實例都有一個函數,浪費內存。
3.2 prototype繼承要想子類獲得父類的屬性,如果是通過原型來實現繼承,那么就是父類的一個實例是子類的原型:
function F(){ this.a = [1,2,3,4] this.b = 2 } var f = new F() function G(){} G.prototype = f var g = new G() var h = new G() g.a //[1,2,3,4] g.b //2 //對于引用類型,如果我們修改g.a(不是用=賦值,用=不會操作到原型鏈) g.a.push(123) g.a//[1,2,3,4,123] //而且其他的實例也會變化 h.a //[1,2,3,4,123] g.b = 666 //只是在實例里面對b屬性進行改寫,不會影響原形鏈可以看見,對于父類的引用類型,某個值是引用類型的屬性被改寫后,子類的所有的實例繼承過來的屬性都會變,主要的是,子類都可以改變父類。但是=賦值操作相當于直接在某一個實例上面改寫。因為屬性查找是按照原型鏈查找,先查找自身再查找原型鏈,找到為止。用了等號,先給自身賦值,所以自身賦值成功了也不會繼續去原型鏈查找。
因為都有各自的缺陷,所以就有一種組合繼承,將構造函數繼承和prototype繼承混合起來,方法寫在父類的prototype上,是比較常見的方法。但是實例化都會調用兩次構造函數,new和call
3.3Object.create繼承(原型繼承)這樣子,可以在兩個prototype中間加上一個中介F類,使得子類不會污染父類,子類A是父類B繼承而來,而且還可以在中間給他定義屬性
function A() {}? function B() {}? A.prototype = Object.create(B.prototype,{father:{value:[1,2,3]}}); //Object.create的hack Object.create =Object.create|| function (o) { ????var F = function () {}; ????F.prototype = o; ????return new F(); } //其實create函數內部的原理就是這樣子,看回去上面的A和B,這些操作相當于 var F = function () {}; F.prototype = B.prototype;//原型被重寫,a.__proto__.constructor是B而不是F A.prototype = new F() //create方法,第二個參數類似于defineProperty,而且定義的屬性可以自行配置,默認是不可以重新賦值 var a = new A() a.father //[1,2,3] a.father = 1 a.father //[1,2,3]在不需要動用構造函數的時候,只是想看到讓子類父類這種繼承關系,create基本上是完美選擇
3.4 寄生式繼承利用一個封裝好繼承過程的函數來實現繼承,不需要另外定義一個子類,直接把子類的方法寫在函數里面
function createobj (obj) { var temp = Object.create(obj) temp.f = function () { console.log("this is father") } return temp } function B() {}? var b = createobj (B.prototype) b.f() //this is father但是,不能做到函數復用,每一個實例都要寫一份,而且寫了一個createobj就是寫死了,也不能獲取B類的內部屬性
3.5 寄生組合式繼承對于上面的僅僅依靠Object.create繼承,a.__proto__原型對象被重寫,他的構造函數是B,而不是中間量F,對于這種中間類F無意義,而且只是依靠中間原型對象,我們可以用比較完美的寄生組合式繼承:
function A() {}? function B() {}? var prototype = Object.create(B.prototype)//創建 prototype.constructor = A//增強 A.prototype = prototype//指定,這下a.__proto__.constructor 就是A了 var a = new A()不用創建中間類F,而且構造函數A的確是造出a的(a.__proto__.constructor == A),而不是像create那樣改寫原型鏈,構造函數是B
附上原型鏈圖解:(注意終點是null,中間的都是正常new構造,沒有改寫prototype)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/94360.html
摘要:綁定函數被調用時,也接受預設的參數提供給原函數。原型鏈官方文檔上有一句話說明綁定過后的函數被實例化之后,需要繼承原函數的原型鏈方法,且綁定過程中提供的被忽略繼承原函數的對象,但是參數還是會使用。 bind 官方描述 bind() 函數會創建一個新函數(稱為綁定函數),新函數與被調函數(綁定函數的目標函數)具有相同的函數體(在 ECMAScript 5 規范中內置的call屬性)。當目標...
摘要:應該非常小心,避免出現不使用命令直接調用構造函數的情況。上面代碼表示,使用屬性,確定實例對象的構造函數是,而不是。當然,從繼承鏈來看,只有一個父類,但是由于在的實例上,同時執行和的構造函數,所以它同時繼承了這兩個類的方法。 基本概念 類和實例是大多數面向對象編程語言的基本概念 類:類是對象的類型模板 實例:實例是根據類創建的對象但是,JavaScript語言的對象體系,不是基于類的,...
摘要:新函數也能使用操作符創建對象這種行為就像把原函數當成構造器,提供的值被忽略。說明綁定后的新函數被實例化之后,需要繼承原函數的原型鏈方法,且綁定過程中提供的被忽略繼承原函數的對象,但是參數還是會使用。 在討論bind方法前,我們可以先看一個例子: var getElementsByTagName = document.getElementsByTagName; getElementsBy...
摘要:指向原型對象的構造函數二原型鏈什么是原型鏈原型鏈就是實例對象和原型對象之間的關系,他們使用來關聯。改變指向實現繼承創建類創建原型對象每天堅持鍛煉創建構造函數函數就是改變執行代碼片段中指向。 一. JS原型的簡單理解 1.1 prototype prototype:是一個函數的屬性,每個函數中都會有一個prototype屬性,這個屬性是一個指針,指向一個對象。在JavaScript...
閱讀 1030·2023-04-25 22:27
閱讀 877·2021-11-22 14:56
閱讀 992·2021-11-11 16:54
閱讀 1688·2019-08-30 15:54
閱讀 3509·2019-08-30 13:20
閱讀 1219·2019-08-30 10:55
閱讀 2087·2019-08-26 13:34
閱讀 3287·2019-08-26 11:53