這些都是js基礎進階的必備了,有時候可能一會兒想不起來是什麼,時不時就回頭看看基礎,加強硬實力。javascript
誰最後調用,就指向誰
先簡單複習一次,this指向就那麼幾種:java
指向new 建立的對象git
function F() { this.name = 1 } var f = new F()
指向傳入函數的第一個參數。a.call(b),函數a內部若是是要用到this。則這個this指向bgithub
對象內部的方法指向對象自己數組
var obj = { value: 5, printThis: function () { console.log(this); } };
指向全局緩存
var obj = { value: 5, printThis: function () { console.log(this); } }; var f = obj.printThis f()
5.箭頭函數
指向箭頭函數定義時外層上下文app
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()()//obj
前二者都是同樣,只是參數表現形式不一樣,bind表示的是靜態的前二者,須要手動調用a.call(b,args)
讓函數a執行上下文指向b,也就是b的屬性就算沒有a函數,也能像b.a(args)這樣子調用函數
方法你們都知道,咱們不妨來本身實現一下這三個:this
再看一次概念,b沒有a方法,也就是沒有b.a,若是想要這個效果,那就利用這三個函數來改變執行上下文。因而咱們就能夠想到,要是本身實現一個,大概就是,給b強行加上這個a 的方法,而後拿到argument去調用:spa
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(;i<len;i++){ args.push(arguments[i]) } args = args.join(',') result = eval('ctx[hash](' + args + ')')//將參數傳遞進去調用 } delete ctx[hash]//刪除臨時增長的屬性 return result }
apply也是同理,並且少了數組這一步,更加簡單接下來咱們看一下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的上下文
首先,js沒有嚴格意義上的子類父類,實現繼承是依靠原型鏈來實現相似於所謂的類的效果。
咱們但願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(){}
也註定沒法實現函數複用了,每個實例都有一個函數,浪費內存。
要想子類得到父類的屬性,若是是經過原型來實現繼承,那麼就是父類的一個實例是子類的原型:
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
這樣子,能夠在兩個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基本上是完美選擇
利用一個封裝好繼承過程的函數來實現繼承,不須要另外定義一個子類,直接把子類的方法寫在函數裏面
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類的內部屬性
對於上面的僅僅依靠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)