JavaScript中改變this指向的三種方式總結(jié)
前言
在上篇文章 可能是你看過最完整的this指向總結(jié)!中介紹了this指向在不同環(huán)境下的總結(jié)情況,里面提到過:
this 指向的值是可以通過手動方式去改變的,比如call、bind、apply方法。
那么在本篇中我們介紹的是call、bind、apply方法的使用以及如何實(shí)現(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值和實(shí)參值
// 打印 1 2 3
// {name: 'owl', age: 18}
// owl 18
2. 實(shí)現(xiàn)myCall方法
介紹完call()方法以后,我們來嘗試寫一個自己的myCall方法,具體實(shí)現(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ù),我們就把實(shí)參帶入到func函數(shù)中執(zhí)行,如果沒有,則直接執(zhí)行。
delete ctx.func; // 5. 執(zhí)行完函數(shù)以后,記得刪除掉這個“中間變量”屬性 ctx
return result; // 6. 返回result
};
在上面的代碼中,func其實(shí)就是person函數(shù),ctx則是我們傳入要指定this指向的對象,也就是 {name: 'owl', age: 18}。
那么我們在第四步使用ctx.func()或者ctx.func(...args)調(diào)用func()時是不是就滿足了上篇文章中的調(diào)用對象的函數(shù)方法時,被調(diào)用函數(shù)中的this永遠(yuǎn)指向這個對象。
所以自然而然就實(shí)現(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幾乎一模一樣,所以我們在使用和實(shí)現(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í)參值(類數(shù)組對象)
// 打印 1 2 3
// {name: 'owl', age: 18}
// owl 182.實(shí)現(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ù)組實(shí)參
const result = arguments[1] ? ctx.func(...arguments[1]) : ctx.func();
//第四步: 調(diào)用方法,獲得結(jié)果。
delete ctx.func;
return result;
};myApply方法里面,我們只需要更改兩點(diǎn):
- 第三步獲取參數(shù)上,直接獲取arguments數(shù)組的第二項(xiàng)
- 第四步調(diào)用方法上,傳入獲取到的arguments 數(shù)組的第二項(xiàng)
bind
1.使用方法
- bind接收的參數(shù)形式和call有點(diǎn)相似的:第一個參數(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 182.實(shí)現(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ù)組的第二項(xiàng)開始截取
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)實(shí)現(xiàn),但是還有一個問題沒有解決,那就是既然返回的是一個新函數(shù),那除了直接調(diào)用newBind() 方法以外,還可以將newBind當(dāng)成構(gòu)造函數(shù),使用 new 關(guān)鍵字進(jìn)行實(shí)例化。
比如下面的實(shí)例化例子:
let newBind = person.myBind(obj, 1, 2, 3); let n = new newBind();
- 我們知道變量n正常來說應(yīng)該是newBind函數(shù)的實(shí)例化對象,構(gòu)造函數(shù)的this指向?qū)嵗瘜ο蟆?/li>
- 而在
return self.apply(obj, args.concat(newArgs));這一行代碼中,我們是寫死this指向?yàn)閛bj。這樣顯然是不對的。 - 我們必須考慮到new的情況,所以對簡單版本的myBind代碼進(jìn)行改造
完整版本
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(
//如果沒有進(jìn)行判斷,永遠(yuǎn)寫死obj作為apply的第一個參數(shù),那么如果對fn這個返回函數(shù)進(jìn)行new時,這個fn函數(shù)的this指向永遠(yuǎn)是外部傳過來的obj
//這樣是不正確的,如果作為new關(guān)鍵字使用這個fn函數(shù),this指向必須是指向new出來的實(shí)例對象
//怎么判斷是不是用new關(guān)鍵字來調(diào)用呢?
// 我們可以用 instanceof 來判斷返回函數(shù)的原型是否在實(shí)例的原型鏈上
// 如果返回函數(shù)是被new了,那這個返回函數(shù)的實(shí)例對象的this就指向了person函數(shù)
this instanceof fn ? this : obj,
args.concat(newArgs)
);
};
// 創(chuàng)建一個空函數(shù)
var tmpFn = function () {};
// 修改返回函數(shù)的 prototype 為綁定函數(shù)的 prototype,實(shí)例就可以繼承綁定函數(shù)的原型中的值
// 可以直接使用 fn.prototype = this.prototype (this代表fn)
// fn.prototype = this.prototype;
// 也就是讓返回函數(shù)的原型對象和person函數(shù)的原型對象映射
// 至于為什么使用一個空函數(shù) tmpFn 作為中介,把 fn.prototype 賦值為空對象的實(shí)例(原型式繼承),
// 這是因?yàn)橹苯?fn.prototype = this.prototype 有一個缺點(diǎn),修改 fn.prototype 的時候,也會直接修改 this.prototype ;
// tmpFn空函數(shù)的原型指向綁定函數(shù)的原型
tmpFn.prototype = this.prototype; //(this代表person函數(shù))
// 空對象的實(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實(shí)現(xiàn)自動生成網(wǎng)頁元素功能(按鈕、文本等)
這篇文章主要介紹了JavaScript實(shí)現(xiàn)自動生成網(wǎng)頁元素功能,文章列出了三種可以進(jìn)行增刪改克隆按鈕、文本等元素的方法,感興趣的小伙伴們可以參考一下2015-11-11
Bootstrap的基本應(yīng)用要點(diǎn)淺析
BootStrap是基于HTML、CSS和JavaScript的框架,使你只需要寫簡單的代碼就可以很快的搭建一個還不錯的前端框架,他是后端程序員的福音,使他們只需要專注業(yè)務(wù)邏輯,而無須浪費(fèi)太多的精力在界面設(shè)計(jì)上2016-12-12

