JavaScript中改變this指向的三種方式總結(jié)
前言
在上篇文章 可能是你看過最完整的this指向總結(jié)!中介紹了this指向在不同環(huán)境下的總結(jié)情況,里面提到過:
this 指向的值是可以通過手動方式去改變的,比如call、bind、apply方法。
那么在本篇中我們介紹的是call、bind、apply方法的使用以及如何實現(xiàn)自己的myCall、myBind、myApply方法。
一、call
1. 使用方法
function person(a,b,c){ console.log(this); console.log(this.name,this.age) } person(); // 1.直接調(diào)用,this指向window // 打印 window // 打印 undefined,undefined const obj = {name:"owl",age:18} person.call(obj) // 2.傳入指定的this值 // 打印 {name: 'owl', age: 18} // owl 18 function person(a,b,c){ console.log(a,b,c) console.log(this); console.log(this.name,this.age) } person.call(obj,1,2,3) // 3.給person函數(shù)傳入指定的this值和實參值 // 打印 1 2 3 // {name: 'owl', age: 18} // owl 18
2. 實現(xiàn)myCall方法
介紹完call()方法以后,我們來嘗試寫一個自己的myCall方法,具體實現(xiàn)代碼和注釋如下:
Function.prototype.myCall = function (obj) { if (typeof this !== "function") { throw new Error( "Function.prototype.myCall - what is trying to be bound is not callable" ); } const ctx = obj || window; // 1.定義一個ctx變量獲取傳入的對象obj,如果沒有則取window ctx.func = this; // 2.在ctx對象上新增一個屬性func,并且給他賦值為this // this就是調(diào)用myCall函數(shù)的函數(shù),在本例中就是person()方法 const args = Array.from(arguments).slice(1); // 3.處理傳入的參數(shù),第一個參數(shù)為對象obj, // 所以從第二個開始參數(shù)截取 const result = arguments.length > 1 ? ctx.func(...args) : ctx.func(); // 4. 如果傳入?yún)?shù),我們就把實參帶入到func函數(shù)中執(zhí)行,如果沒有,則直接執(zhí)行。 delete ctx.func; // 5. 執(zhí)行完函數(shù)以后,記得刪除掉這個“中間變量”屬性 ctx return result; // 6. 返回result };
在上面的代碼中,func其實就是person函數(shù),ctx則是我們傳入要指定this指向的對象,也就是 {name: 'owl', age: 18}
。
那么我們在第四步使用ctx.func()
或者ctx.func(...args)
調(diào)用func()
時是不是就滿足了上篇文章中的調(diào)用對象的函數(shù)方法時,被調(diào)用函數(shù)中的this永遠指向這個對象
。
所以自然而然就實現(xiàn)了我們手動改變this指向的目的。
var obj = { name: "owllai", }; function testCall(a, b, c) { console.log(this.name, a, b, c); } testCall.myCall(obj,1,2,3)
apply
- apply和call的唯一區(qū)別就在于,接收的第二個參數(shù)為類數(shù)組。
- 除此之外,和call幾乎一模一樣,所以我們在使用和實現(xiàn)自定義apply方法的代碼里只需要修改對應(yīng)的部分就行了。
1.使用方法
function person(a,b,c){ console.log(this); console.log(this.name,this.age) } person(); // 1.直接調(diào)用,this指向window // 打印 window // 打印 undefined,undefined const obj = {name:"owl",age:18} person.apply(obj) // 2.傳入指定的this值 // 打印 {name: 'owl', age: 18} // owl 18 function person(a,b,c){ console.log(a,b,c) console.log(this); console.log(this.name,this.age) } person.apply(obj,[1,2,3]) // 3.給person函數(shù)傳入指定的this值和實參值(類數(shù)組對象) // 打印 1 2 3 // {name: 'owl', age: 18} // owl 18
2.實現(xiàn)myApply方法
Function.prototype.myApply = function (obj) { if (typeof this !== "function") { throw new Error( "Function.prototype.myApply - what is trying to be bound is not callable" ); } const ctx = obj || window; // 1.定義一個ctx變量獲取傳入的對象obj,如果沒有則取window ctx.func = this; // 2.在ctx對象上新增一個屬性func,并且給他賦值為this // this就是調(diào)用myApply函數(shù)的函數(shù),在本例中就是person()方法 const args = arguments[1]; // 3.處理傳入的參數(shù),第一個參數(shù)為對象obj, // 第二個參數(shù)為數(shù)組實參 const result = arguments[1] ? ctx.func(...arguments[1]) : ctx.func(); //第四步: 調(diào)用方法,獲得結(jié)果。 delete ctx.func; return result; };
myApply方法里面,我們只需要更改兩點:
- 第三步獲取參數(shù)上,直接獲取arguments數(shù)組的第二項
- 第四步調(diào)用方法上,傳入獲取到的arguments 數(shù)組的第二項
bind
1.使用方法
- bind接收的參數(shù)形式和call有點相似的:第一個參數(shù)是指定this指向的值,第二個參數(shù)開始則是執(zhí)行函數(shù)需要的形參
- bind方法在調(diào)用以后不會像call、apply一樣直接執(zhí)行,而是返回一個新函數(shù)。
語法:
bind(thisArg)
bind(thisArg, arg1)
bind(thisArg, arg1, arg2)
bind(thisArg, arg1, arg2, /* …, */ argN)
function person(a, b, c) { console.log(this); console.log(this.name, this.age); } const obj = { name: "owl", age: 18 }; let newPerson = person.bind(obj); console.log(newPerson); // ? person(a, b, c) { // console.log(this); // console.log(this.name, this.age); // } newPerson(); // {name: 'owl', age: 18} // owl 18 function person(a, b, c) { console.log(a,b,c) console.log(this); console.log(this.name, this.age); } let newPersonWithArgs = person.bind(obj,1,2,3) ; newPersonWithArgs(); // 1 2 3 // {name: 'owl', age: 18} // owl 18
2.實現(xiàn)myBind方法
簡單版本
function person(a, b, c) { console.log(a, b, c); console.log(this); console.log(this.name, this.age); } let obj = { name: "owllai", age: 18, }; Function.prototype.myBind = function (obj) { if (typeof this !== "function") { throw new Error( "Function.prototype.bind - what is trying to be bound is not callable" ); } var self = this; // 1. 這個this代表調(diào)用myBind方法的函數(shù),在本例中也就是person var args = Array.prototype.slice.call(arguments, 1); // 2. 獲取傳入的其他參數(shù),從arguments數(shù)組的第二項開始截取 var fn = function () { // 3.定義一個要返回的函數(shù) //4. 返回的新函數(shù)也是可以接收參數(shù)的,所以我們要一起獲取 var newArgs = Array.prototype.slice.call(arguments); //5. 將obj和參數(shù)一起傳入,使用apply來執(zhí)行 return self.apply(obj, args.concat(newArgs)); }; //6. 最后返回結(jié)果函數(shù) return fn; }; let newBind = person.myBind(obj, 1, 2, 3); newBind();
myBind的簡單版本已經(jīng)實現(xiàn),但是還有一個問題沒有解決,那就是既然返回的是一個新函數(shù),那除了直接調(diào)用newBind() 方法以外,還可以將newBind當成構(gòu)造函數(shù),使用 new 關(guān)鍵字進行實例化。
比如下面的實例化例子:
let newBind = person.myBind(obj, 1, 2, 3); let n = new newBind();
- 我們知道變量n正常來說應(yīng)該是newBind函數(shù)的實例化對象,構(gòu)造函數(shù)的this指向?qū)嵗瘜ο蟆?/li>
- 而在
return self.apply(obj, args.concat(newArgs));
這一行代碼中,我們是寫死this指向為obj。這樣顯然是不對的。 - 我們必須考慮到new的情況,所以對簡單版本的myBind代碼進行改造
完整版本
Function.prototype.myBind = function (obj) { if (typeof this !== "function") { throw new Error( "Function.prototype.bind - what is trying to be bound is not callable" ); } var self = this; // 這個this代表調(diào)用myBind方法的函數(shù),在本例中也就是person函數(shù) var args = Array.prototype.slice.call(arguments, 1); var fn = function () { var newArgs = Array.prototype.slice.call(arguments); return self.apply( //如果沒有進行判斷,永遠寫死obj作為apply的第一個參數(shù),那么如果對fn這個返回函數(shù)進行new時,這個fn函數(shù)的this指向永遠是外部傳過來的obj //這樣是不正確的,如果作為new關(guān)鍵字使用這個fn函數(shù),this指向必須是指向new出來的實例對象 //怎么判斷是不是用new關(guān)鍵字來調(diào)用呢? // 我們可以用 instanceof 來判斷返回函數(shù)的原型是否在實例的原型鏈上 // 如果返回函數(shù)是被new了,那這個返回函數(shù)的實例對象的this就指向了person函數(shù) this instanceof fn ? this : obj, args.concat(newArgs) ); }; // 創(chuàng)建一個空函數(shù) var tmpFn = function () {}; // 修改返回函數(shù)的 prototype 為綁定函數(shù)的 prototype,實例就可以繼承綁定函數(shù)的原型中的值 // 可以直接使用 fn.prototype = this.prototype (this代表fn) // fn.prototype = this.prototype; // 也就是讓返回函數(shù)的原型對象和person函數(shù)的原型對象映射 // 至于為什么使用一個空函數(shù) tmpFn 作為中介,把 fn.prototype 賦值為空對象的實例(原型式繼承), // 這是因為直接 fn.prototype = this.prototype 有一個缺點,修改 fn.prototype 的時候,也會直接修改 this.prototype ; // tmpFn空函數(shù)的原型指向綁定函數(shù)的原型 tmpFn.prototype = this.prototype; //(this代表person函數(shù)) // 空對象的實例賦值給 fn.prototype fn.prototype = new tmpFn(); return fn; };
到此這篇關(guān)于JavaScript中改變this指向的三種方式總結(jié)的文章就介紹到這了,更多相關(guān)JavaScript改變this指向內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
javascript?實現(xiàn)純前端將數(shù)據(jù)導(dǎo)出excel兩種方式
這篇文章主要介紹了javascript?實現(xiàn)純前端將數(shù)據(jù)導(dǎo)出excel兩種方式,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參一下2022-07-07Bootstrap組件之下拉菜單,多級菜單及按鈕布局方法實例
這篇文章主要介紹了Bootstrap組件之下拉菜單,多級菜單及按鈕布局方法,結(jié)合完整實例形式分析了Bootstrap多級菜單布局相關(guān)樣式功能與具體使用技巧,需要的朋友可以參考下2017-05-05javascript適配器模式和組合模式原理與實現(xiàn)方法詳解
這篇文章主要介紹了javascript適配器模式和組合模式原理與實現(xiàn)方法,結(jié)合實例形式詳細分析了javascript適配器模式與組合模式相關(guān)原理、功能、實現(xiàn)方法與注意事項,需要的朋友可以參考下2023-07-07javascript實現(xiàn)上傳圖片并預(yù)覽的效果實現(xiàn)代碼
圖片上傳預(yù)覽,就是在使用文件選擇框選擇了文件之后就可以在頁面上看見圖片的效果,關(guān)于這個效果我一直認為是無法做到的2011-04-04