面試題熱點話題工育,this
指向、如何改變this
指向搓彻、call()
apply()
bind()
區(qū)別如绸,還有手寫call()
apply()
以及bind()
。
一旭贬、 this指向問題怔接,這個問題主要是分一些情況來去理解的,整體而言可以理解成this的指向在函數(shù)定義的時候是確定不了的稀轨,只有函數(shù)執(zhí)行的時候才能確定this到底指向誰扼脐,實際上this的最終指向的是那個調(diào)用它的對象,不過具體的特殊情況還需要特殊對待奋刽。更多關(guān)于this指向問題可以參考文章 《徹底理解js中this的指向》瓦侮。
二、 call() apply()
和bind()
的區(qū)別
相同點:
都可以改變函數(shù)的this
指向 第一個參數(shù)都是this要指向的對象佣谐。
不同點:
call()
和apply()
接收參數(shù)不同:
call()
接收多個參數(shù)肚吏,第一個參數(shù)為this
要指向的對象,第二個參數(shù)為調(diào)用調(diào)用call
方法的函數(shù)的第一個參數(shù)狭魂,第三個參數(shù)為該函數(shù)的第二個參數(shù)罚攀,依次類推。
apply()
接收兩個參數(shù)雌澄,第一個參數(shù)為this
要指向的對象斋泄,第二個參數(shù)為一個數(shù)組,數(shù)組的第一項為調(diào)用 apply
方法的第一個參數(shù)镐牺,數(shù)組第二項為該函數(shù)的第二個參數(shù)炫掐,依次類推。
bind()
第一個參數(shù)是this
要指向的對象睬涧,第二個參數(shù)為調(diào)用調(diào)用bind
方法的函數(shù)的第一個參數(shù)卒废,第三個參數(shù)為該函數(shù)的第二個參數(shù),依次類推宙地。
call()
apply()
和bind()
執(zhí)行時機不同:
call()
apply()
在被函數(shù)調(diào)用后立即執(zhí)行摔认,而bind()
方法則是返回一個新的函數(shù)(未執(zhí)行)。
三宅粥、 call()
参袱、apply()
、bind()
語法
call()
語法:
var obj = {
name:"lucy"
}
function FunCall(a,b,c){
console.log(this.name); //lucy
console.log("參數(shù)",a,b,c) // a b c
}
FunCall.call(obj,'a','b','c')
apply()
語法:
var obj = {
name:"lucy"
}
function FunApply(a,b,c){
console.log(this.name); //lucy
console.log("參數(shù)",a,b,c) // a b c
}
Funapply.Apply(obj,["a","b","c"])
bind()
語法:
var obj = {
name:"lucy"
}
function FunBind(a,b,c){
console.log(this.name); //lucy
console.log(a,b,c); // a b c
}
var funbind = FunBind.bind(obj,'a','b','c')
funbind();
注意:bind()
方法還可以這樣傳參
var obj = {
name:"lucy"
}
function FunBind(a,b,c){
console.log(this.name); //lucy
console.log(a,b,c); // a b c
}
var funbind = FunBind.bind(obj)
funbind('a','b','c');
bind()
除了this
還接收其他參數(shù),bind()
返回的函數(shù)也接收參數(shù)抹蚀。
以上是對call()
,apply()
,bind()
的異同以及語法使用剿牺,需要了解更多call()
,apply()
,bind()
的信息,可以參考 MDN 對這幾個方法更詳細的的說明环壤。
四晒来、 手寫call()
、apply()
郑现、bind()
方法湃崩。
- 手寫
call()
:
在寫之前思考一下,call()
方法實際上做的事情就是改變了this
的指向接箫,那如何改變一個函數(shù)的this
指向呢攒读?就是改變函數(shù)的調(diào)用者
。
按照這個思路來看下面代碼辛友。
Function.prototype.mycall = function (thisArg,...args){
//先明白我們的參數(shù) 都帶代表什么薄扁,thisArg代表this當前需要指向的對象,args表示該函數(shù)接收的參數(shù)废累。
//我們參照call 方法理解 foo.call(obj,"a","b") , 此時thisArg就代表obj,...args 就代表參數(shù)啊 a,b
var fn = Symbol("fn");
//創(chuàng)建一個變量邓梅,使用Symbol是因為保證它的唯一性,不和thisArg其他相同fn屬性沖突
var thisArg = thisArg || window;
// 當thisArg不存在時也就是調(diào)用call()沒傳第一個參數(shù)時邑滨,默認為window震放。
thisArg[fn] = this;
//這里的這個this代表的是調(diào)用call方法的函數(shù),比如foo.call() this代表foo函數(shù)驼修,通過賦值操作,此時對象thisArg下面的fn屬性就是一個方法
var result = thisArg[fn](...args);
//這一步是核心诈铛,調(diào)用對象thisArg下的fn方法乙各,在這一步函數(shù)foo的this已改變?yōu)閠hisArg
delete thisArg[fn];
//當函數(shù)調(diào)用完成之后銷毀掉
return result;
}
var obj = {
name:"lucy"
}
function foo(a,b){
console.log(this.name); //lucy
console.log(a,b); //a b
}
foo.mycall(obj,'a','b');
如果上述邏輯不是很清楚,就先去看一下《徹底理解js中this的指向》幢竹。再看看this指向再回來思考這段代碼耳峦。
- 手寫
apply()
:
apply()
跟call()
其實很相似,按照上面代碼的注釋理解一下下面的代碼
Function.prototype.myapply = function(thisArgs,args){
var fn = Symbol("fn");
var thisArgs = thisArgs || window;
thisArgs[fn] = this;
var result = thisArgs[fn](...args);
delete thisArgs[fn];
return result;
}
var obj = {
name:"zhangsan"
}
function foo (a,b,c){
console.log(this.name); //zhangsan
console.log(a,b,c);// a b c
}
foo.myapply(obj,['a','b','c']);
- 手寫
bind()
bind()
需要思考到的地方比較多焕毫,先一點點來蹲坷。
第一步借助實現(xiàn)bind()
基本功能改變this指向。
Function.prototype.mybind = function(thisArgs){
var args = Array.prototype.slice.call(arguments,1);
var _this = this;
return function(){
_this.apply(thisArgs,[...args]) //this指向被改變到thisArgs
}
}
var obj = {
name:"lili"
}
function foo(a,b,c){
console.log(this.name); // lili
console.log(a,b,c); // a b c
}
var ffff = foo.mybind(obj,'a',"b","c")
ffff();
由代碼可知bind()
的改變this功能實現(xiàn)了
這里會有個問題
看下面代碼
Function.prototype.mybind = function(thisArgs){
var args = Array.prototype.slice.call(arguments,1);
var _this = this;
return function(){
_this.apply(thisArgs,[...args]) //this指向被改變到thisArgs
}
}
var obj = {
name:"lili"
}
function foo(a,b,c){
console.log(this.name); // lili
console.log(a,b,c); // undefined undefined undefined
}
var ffff = foo.mybind(obj)
ffff("a","b","c");//當把參數(shù)放到這里時會發(fā)生什么邑飒?
//看上面輸出循签,參數(shù)沒傳遞進去,但是人家原生bind是可以在綁定this之后再傳參的,不信可以試試疙咸。
接著來完善
Function.prototype.mybind = function(thisArgs){
var args = Array.prototype.slice.call(arguments,1);
var _this = this;
return function(){
_this.apply(thisArgs,[...args,...arguments]) //這里與上面代碼不同的地方县匠,這里獲取到當前函數(shù)的參數(shù),并一并傳遞給函數(shù)執(zhí)行。
}
}
var obj = {
name:"lili"
}
function foo(a,b,c){
console.log(this.name); // lili
console.log(a,b,c); // a b c
}
var ffff = foo.mybind(obj)
ffff("a","b","c");//此時在這里傳參
再接著完善:
我們知道對一個函數(shù)使用new
操作符乞旦,會生成一個對象贼穆,該對象稱為當前函數(shù)的實例。該函數(shù)可以稱為構(gòu)造函數(shù)兰粉,構(gòu)造函數(shù)里的this
指向當前的實例化對象故痊。
function Person(name){
this.name = "sss"
}
var person = new Person()
console.log(person) // { name:"sss" }
如果 ffff
函數(shù)被當做構(gòu)造函數(shù)使用new
操作符產(chǎn)生一個實例化對象,按照道理來說this應(yīng)該指向?qū)嵗瘜ο缶凉茫俏覀兪褂?code>bind()方法函數(shù)foo
的this指向了對象obj
,因此我們需要處理一下當ffff
函數(shù)被使用new
操作符生成實例化對象
時愕秫,改變this
指向,讓它指向當前實例化對象
而非obj
客峭。
Function.prototype.mybind = function(thisArgs){
if(typeof this !== "function"){
return new Error('not a function')
}
var args = Array.prototype.slice.call(arguments,1);
var _this = this;
var result = function (){ //稍微做了改變將函數(shù)賦值給result變量
_this.apply(thisArgs,[...args,...arguments])
}
return result;
}
var obj = {
name:"lucy"
}
function foo(name){
this.name = name;
}
var ffff= foo.mybind(obj);
var instance = new ffff("jack"); //實例化被bind()方法綁定this之后返回的函數(shù)
console.log(obj) //{name:"jack"} //理想輸出應(yīng)該是 {name:"lucy"}
console.log(instance); // {} //理想輸出應(yīng)該是 { name:"jack" }
此時可以使用原生bind()
方法觀察比較
var obj = {
name:"lucy"
}
function foo(name){
this.name = name;
}
var ffff= foo.bind(obj); //用原生bind()方法
var instance = new ffff("jack"); //實例化被bind()方法綁定this之后返回的函數(shù)
console.log(obj) //{ name:"lucy" }
console.log(instance); // { name:"jack" }
這下很直觀的能觀察到我們的mybind方法有問題豫领,問題就在當ffff
函數(shù)被使用new
操作符生成實例化對象
時,沒有改變this
指向到實例化對象舔琅,而還是指向mybind()
的第一個參數(shù)obj
等恐。
如何改變this呢?看一下代碼
Function.prototype.mybind = function(thisArgs){
if(typeof this !== "function"){
return new Error('not a function')
}
var args = Array.prototype.slice.call(arguments,1);
var _this = this;
var result = function (){
var thisArgs = this instanceof result ? this : thisArgs;
/*
在這里對即將傳入apply的第一個參數(shù)進行判斷,
判斷當前this是否是result函數(shù)的實例备蚓,
如果是那就將當前的this傳遞進去课蔬,
如果不是那就還是使用傳遞進來的第一個參數(shù)。
*/
_this.apply(thisArgs,[...args,...arguments])
}
return result;
}
var obj = {
name:"lucy"
}
function foo(name){
this.name = name;
}
foo.prototype.age = 18; // 第26行
var ffff = foo.mybind(obj);
var instance = new ffff("jack");
console.log(obj) //{name:"lucy"}
console.log(instance); // { name:"jack" }
console.log(instance.age);// undefined(先不用看這個) // 第33行
我們在第9行加了一個判斷郊尝,判斷這里的this
是不是指向當前函數(shù)的實例二跋,如果是,那么我們就需要改變傳入apply
的第一個參數(shù)了流昏。
最終實現(xiàn):
看上面代碼26行我們給foo函數(shù)原型對象上寫入了一個屬性age
,理論上我們通過instance.age
就能訪問到扎即,但是通過第33行代碼我們發(fā)現(xiàn),instance
的原型鏈上沒有age屬性况凉。
再看一下代碼:
Function.prototype.mybind = function(thisArgs){
if(typeof this !== "function"){
return new Error('not a function')
}
var args = Array.prototype.slice.call(arguments,1);
var _this = this;
var result = function (){
console.log(this);//result{}
var alignThis = _this instanceof result ? _this : thisArgs;
_this.apply(alignThis,[...args,...arguments])
}
//將調(diào)用mybind方法的函數(shù)的原型對象利用原型繼承的方式將該函數(shù)的原型繼承到result的原型對象上谚鄙。
var FUN = function(){} //第15行
FUN.prototype = _this.prototype;
result.prototype = new FUN() //第17行
return result;
}
var obj = {
name:"lucy"
}
function foo(name){
this.name = name;
}
foo.prototype.age = 18;
var ffff = foo.mybind(obj);
var instance = new ffff("jack");
console.log(obj)//{name:"lucy"}
console.log(instance);//{name:"jack"}
console.log(instance.age); // 18
通過15-17行,完成原型對象的繼承刁绒,這樣就完成了手寫bind()
闷营。
到此為止,call()
apply()
bind()
方法的手寫實現(xiàn)也完成了知市。
寫的有些啰嗦傻盟,但是邊寫邊怕自己寫不清楚,又怕寫錯了嫂丙,只能慢慢一步步來娘赴,希望看到此文的伙伴有疑惑或者發(fā)現(xiàn)錯誤請不吝賜教。謝謝跟啤。
寫在最后:文中內(nèi)容大多為自己平時從各種途徑學(xué)習(xí)總結(jié)筝闹,文中參考文章大多收錄在我的個人博客里媳叨,歡迎閱覽http://www.tianleilei.cn