
這里簡(jiǎn)單的科普一下call,apply,bind他們都是用來干嘛的。
執(zhí)行上下文中有四個(gè)變量:
- 變量環(huán)境
- 詞法環(huán)境
- outer
- this
執(zhí)行上下文分為:
- 全局執(zhí)行上下文
- 函數(shù)執(zhí)行上下文
- eval執(zhí)行上下文
全局執(zhí)行上下文中:
- 變量環(huán)境 存儲(chǔ) 變量提升和函數(shù)提升
- 詞法環(huán)境 存儲(chǔ) let const 以及塊級(jí)作用域
- this 指向 全局本身,也就是window.
- outer 為 null
函數(shù)執(zhí)行上下文中:
- 變量環(huán)境 存儲(chǔ) 變量提升和函數(shù)提升
- 詞法換 存儲(chǔ) let const 以及塊級(jí)作用域
- outer 指向 它的上一層詞法作用域 有可能是其他的函數(shù)也可能是全局
- this 指向調(diào)用它的那個(gè)瞬間來確定。
如果是在沒有明確指定的時(shí)候,那么它就指向window。
如果使用的是call,apply,bind 就使用它們指定的對(duì)象。
如果函數(shù)是對(duì)象的屬性,并且是以函數(shù)的屬性被調(diào)用的。
如果是在構(gòu)造函數(shù)里面,那么它就指向new 出來的那個(gè)對(duì)象。
現(xiàn)在我們知道call,apply,bind 改變的是 函數(shù)在執(zhí)行的時(shí)候,它的內(nèi)部執(zhí)行對(duì)象。它實(shí)際上對(duì)詞法作用域的一種補(bǔ)充。就像張三家里有一把鐵鍬,我像借用一下。甚至這把鐵鍬是公用的,我們只用的時(shí)候只需要把想借用的人
call
用法:第一個(gè)值傳入要操作的對(duì)象,后面的值傳入函數(shù)調(diào)用的參數(shù)。
我們的實(shí)現(xiàn)是基于,上面列出來的第三種情況來實(shí)現(xiàn)。
Function.prototype.myCall=function (context,...args){
if(!context || context == null){
context = window
}
//創(chuàng)造唯一的key值,作為我們構(gòu)造的context內(nèi)部方法名
let fn = Symbol();
//在context中新建一個(gè)key 為symobl的值,value是當(dāng)前調(diào)用這個(gè)myCall的**函數(shù)對(duì)象**。
context[fn] = this;
//調(diào)用這個(gè)函數(shù),并返回result
let result = context[fn](...args);
//刪除這個(gè)屬性
delete context[fn];
return result
}
apply
用法:和apply一樣,區(qū)別僅僅是它后面的參數(shù)是一個(gè)數(shù)組
Function.prototype.myApply = function (context,args){
if(!context || context == null){
context = window;
}
//創(chuàng)造唯一的key值,作為我們構(gòu)造的context內(nèi)部方法名
let fn = Symbol();
context[fn] = this;
let result = context[fn](...args);
delete context[fn];
return result;
}
bind
function isObject(it) {
return typeof it == 'object' ? it !== null : typeof it == 'function';
};
Function.prototype.myBind = function (that,...args){
if(typeof this !== "function"){
throw new Error("調(diào)用的不是函數(shù)")
}
//當(dāng)前的函數(shù)對(duì)象
let F = this;
let Prototype = F.prototype;
//拿到當(dāng)前函數(shù)對(duì)象的原型對(duì)象
function boundFunction(...arr){
return F.myApply(
this instanceof boundFunction ? this : that,
[...args,...arr]
)
}
//維護(hù)原有的原型關(guān)系
if(isObject(Prototype)){
boundFunction.prototype = Prototype;
}
return boundFunction;
}
測(cè)試用例:
let person = {
name:"張三"
};
function getName (){
console.log(this.name);
}
getName.myBind(person)()
關(guān)于bind 的面試題:
輸出什么
let a = {
name:"張三"
};
let b = {
name:"李四"
}
let c = {
name:"趙五"
}
function getName (){
console.log(this.name);
}
getName.bind(a).bind(b).bind(c)();
// 張三
為什么會(huì)輸出張三,而不是最后一個(gè)綁定的趙五呢?
可以通過上面我們自定義的bind分析一下。
可以看到 bind函數(shù)實(shí)際上是又返回了一個(gè)函數(shù)boundFunction,然后在這里函數(shù)的內(nèi)部 通過apply方法來綁定執(zhí)行傳過來的this.我們需要關(guān)注的并不是apply的操作。
而是它前面的調(diào)用函數(shù),我們?cè)诘谝徽{(diào)用bind的時(shí)候,F實(shí)際就是我們初始的getName函數(shù)。
當(dāng)我們調(diào)用完返回之后,就是boundFunction函數(shù)了。
let A = getName.bind(a);
//A實(shí)際上已經(jīng)被替換成了`boundFunction`函數(shù)了
let B = A.bind(b);
let C = B.bind(c);
所以在下一次我們執(zhí)行bind操作的時(shí)候,實(shí)際上是給新返回來的函數(shù)綁定的this。
你雖然可以綁定成功,也可以把this添加進(jìn)去,但是確無法改變第一層的調(diào)用啊。
簡(jiǎn)單來說,再次bind的時(shí)候,我們已經(jīng)無法對(duì)最原始的待綁定函數(shù)進(jìn)行操作了,我們操作的只是它的代理
這個(gè)就是為什么我們調(diào)用多次bind而無法改變的原因