apply禀倔、call、bind 實現(xiàn)和this指向問題

  • 如何正確判斷 this参淫,箭頭函數(shù)的 this 是什么救湖?

  • 原則:
    • 1.元素綁定事件,方法中的 this 是元素本身
    • 2.方法名前面是否有點涎才,有的話前面是誰 this 就是誰鞋既,沒有 this 就是 window ( 嚴格模式下是 undefined )
    • 3.構造函數(shù)執(zhí)行,方法中的 this 是當前類的一個實例
var fullName = 'language'
var obj = {
    fullName: 'javascript',
    prop: {
        getFullName: function() {
            return this.fullName
        }
    }
}
console.log(obj.prop.getFullName()) // undefined 方法調(diào)用前面有點則點前面就是 this => obj.prop => obj.prop.fullName => undefined
var test = obj.prop.getFullName
console.log(test()) // language
var name = 'window'
var Tom = {
    name: 'Tom',
    show: function() {
        console.log(this.name)
    },
    wait: function() {
        var fun = this.show
        fun() // 直接執(zhí)行憔维,管你在哪呢涛救,this 就是 window
    }
}
Tom.wait() // window
this
  • 對于直接調(diào)用的函數(shù)來說,不管函數(shù)被放在了什么地方业扒,this 一定是 window
    直接調(diào)用的形式如下:
// 1. 最普通的函數(shù)調(diào)用
function fun1() {
    console.log(this) // Window
}
fun1()

// 2. 函數(shù)嵌套
function fun1() {
    function fun2() {
        console.log(this) // Window
    }
    fun2()
}
fun1()

// 3. 把函數(shù)賦值之后在調(diào)用
var a = 1
var obj1 = {
    a: 2,
    fun1: function() {
        console.log(this.a)
    }
}
var fun2 = obj1.fun1
fun2() // 1
// 如果想輸出 2 則需要顯式的綁定 obj1 如下
fun2.call(obj1) // 2

// 4. 回調(diào)函數(shù)
var num1 = 1
function fun1(fun) {
    fun()
    console.log(this.num1)
}
fun1(fun2) // 1
function fun2() {
    var num1 = 2
}
// 改寫如下:成自執(zhí)行函數(shù)
var num1 = 1
function fun1() {
    (function fun2() {
        var num1 = 2
    })()
    console.log(this.num1)
}
fun1() // 1
// 上述可推理出 setTimeout 這個自執(zhí)行函數(shù)就約等于回調(diào)函數(shù)检吆,所以他的 this 也是 window 代碼如下:
setTimeout(function fun1() {
    console.log(this) // window
    function fun2() {
        console.log(this) // window
    }
    fun2()
}, 0)
  • 對于被調(diào)用的函數(shù)來說,誰調(diào)用了函數(shù)程储,誰就是 this
    方法調(diào)用的形式如下:
// 1. 直接調(diào)用
var a = 1
var obj1 = {
    a: 2,
    fun1: function() {
        console.log(this.a) // 2
    }
}
obj1.fun1()

// 2. DOM 對象綁定事件調(diào)用
// 頁面監(jiān)聽 click 事件屬于方法調(diào)用蹭沛,this 指向 DOM 源對象
document.addEventListener('click', function(e) {
    console.log(this) // document
    setTimeout(function() {
        console.log(this) // Window
    }, 200)
}, false)
  • 對于 new 的方式來說,this 永遠被綁定在了 new 出來的函數(shù)上面章鲤,不會被任何方式改變
    new 一個新函數(shù)時摊灭,會創(chuàng)建一個連接到 prototype 成員的新對象,同時 this 會綁定到那個新對象上

構造器調(diào)用模式示例如下:

function Person(name, age) {
    this.name = name
    this.age = age
    this.sayName = function () {
        console.log(this.name)
    }
}   
var person1 = new Person('xiaoming', 22)
person1.sayName() // 'xiaoming'
  • 箭頭函數(shù)是沒有 this 的败徊,箭頭函數(shù)的 this 只取決于包裹箭頭函數(shù)的第一個普通函數(shù)的 this帚呼。另外,對于箭頭函數(shù)使用 bind 這類函數(shù)是無效的皱蹦。
    示例如下:
function test2() {
    return () => {
        return () => {
            console.log(this) 
        }
    }
}
console.log(test2()()()) // Window
  • call apply bind 的作用就是改變 this 的指向即改變上下文的 API煤杀,對于這些函數(shù)來說 this 取決于第一個參數(shù),如果第一個參數(shù)為空沪哺,那么就是 window沈自。

多次 bind 實例探究:

let test3 = { }
let fun3 = function () {
    console.log(this)
}
fun3.bind().bind(test3)() // => ?

如上示例 愚鈍的筆者是看不出來輸出結果的所以我只能用變形的方法來更清楚的看一下執(zhí)行函數(shù)的順序如下:

let test3 = { }
let fun3 = function () {
    console.log(this)
}
let fun4 = function fun5() {
    return function () {
        return fun3.apply()
    }.apply(test3)
}
fun4()

這樣改變就比較清晰的看到不管給函數(shù) bind 幾次,fun3 中的 this 永遠由第一次 bind 來決定辜妓,即為 window枯途。

let test6 = { name: 'test6' }
function fun6() {
    console.log(this.name)
}
fun6.bind(test6)() // test6

如果發(fā)生多個規(guī)則同時出現(xiàn)忌怎,則需要遵循優(yōu)先級:new 方式優(yōu)先級最高,bind call apply 其次酪夷,接下來是 test1.fun() 調(diào)用榴啸,最后是直接調(diào)用,同時箭頭函數(shù)的 this 一旦被綁定就不可改變晚岭。

this指向流程圖.png
call插掂、apply
  • 第一個參數(shù)是要綁定給 this 的值,后面?zhèn)魅氲氖菂?shù)列表腥例,當?shù)谝粋€參數(shù)為空、null酝润、undefined 的時候燎竖,默認指向 window。
obj1.fun1() => obj1.fun1.call(obj1)
fun1() => fun1.call(null)
fun1(fun2) => fun1.call(null, fun2)
Function.prototype.call = function(obj) {
    if(typeof this != 'function') {
        throw Error('this is not a function')
    }
    // 判斷第一個參數(shù)如果沒有則指向 window
    obj = obj || window
    // 取出外層參數(shù) call
    const args1 = Array.prototype.slice.call(arguments, 1)
    // apply const args1 = arguments[1]
    // 將 this 放入到綁定的對象里面
    const fn = Symbol('fn')
    obj[fn] = this
    // 執(zhí)行保存的函數(shù), 這個時候作用域就是在綁定對象的作用域下執(zhí)行要销,改變的this的指向
    const result = obj[fn](...args1)
    // 刪除上述綁定的方法
    delete obj[fn]
    return result
}
bind
  • 第一個參數(shù)同 call构回、apply 是要綁定給 this 的值,區(qū)別是它的返回值是函數(shù)以及 bind 接受的參數(shù)列表的使用疏咐。
// bind 返回值是函數(shù)
var object1 = {
    name: 'bind'
}
function fun1() {
    console.log(this.name)
}
var bind = fun1.bind(object1)
console.log(bind) // ? fun1() { console.log(this.name) }
bind() // bind

// 參數(shù)的使用
function fun1 (a, b, c) {
    console.log(a, b, c)
}
var fun2 = fun1.bind(null, 'bind')

fun1('1', '2', '3') // 1, 2, 3
fun2('1', '2', '3') // bind, 1, 2 
fun2('2', '3') //bind, 2, 3
fun1.call(null, 'bind') // bind, undefined, undefined
Function.prototype.bind = function(obj) {
    if(typeof this != 'function') {
        throw Error('this is not a function')
    } 
    // 判斷第一個參數(shù)如果沒有則指向 window
    obj = obj || window
    // 緩存 this 出來纤掸,防止被改變
    const self = this
    // 取出外層參數(shù)
    const args1 = Array.prototype.slice.call(arguments, 1)
    // 返回一個函數(shù) 
    const result = function() {
        // 綁定 this 到 obj 上并執(zhí)行方法,參數(shù)進行內(nèi)外合并
        // return self.apply(obj, args1.concat([...arguments]))
        // 如果函數(shù)放在 new 后面進行調(diào)用浑塞,則需要判斷當前 this 是不是屬于綁定函數(shù)的實例
        return self.apply(this instanceof self ? this : obj, args1.concat([...arguments]))
    }
    // 維護好原型鏈
    const ret = function() {}
    if(this.prototype) {
        ret.prototype = this.prototype
    }
    // 下行的代碼使 result.prototype是 ret 的實例,因此返回的 reslt 若作為 new 的構造函數(shù)
    // new 生成的新對象作為 this 傳入 result, 新對象的 __proto__ 就是 ret 的實例
    result.prototype = new ret()
    return result
}
應用場景
  • 求數(shù)組的最大最小值
var arr = [1, 2, 3, 4, 5]
var max = Math.max.apply(null, arr)
var min = Math.min.apply(null, arr)
console.log(max, ' --- ', min) // 5 " --- " 1
  • 數(shù)組追加
var arr1 = [1,2,3];
var arr2 = [4,5,6];
var total = [].push.apply(arr1, arr2);//6
// arr1 [1, 2, 3, 4, 5, 6]
// arr2 [4,5,6]
  • 判斷變量類型
function isArray(obj){
    return Object.prototype.toString.call(obj) == '[object Array]';
}
isArray([]) // true
isArray('dot') // false
  • 利用call和apply做繼承
function Person(name,age){
    // 這里的this都指向實例
    this.name = name
    this.age = age
    this.sayAge = function(){
        console.log(this.age)
    }
}
function Female(){
    Person.apply(this,arguments)//將父元素所有方法在這里執(zhí)行一遍就繼承了
}
var dot = new Female('Dot',2)
call借跪、apply和bind函數(shù)存在的區(qū)別: 簡單實現(xiàn)請戳這里。酌壕。掏愁。

bind返回對應函數(shù), 便于稍后調(diào)用; apply, call則是立即調(diào)用卵牍。
除此外, 在 ES6 的箭頭函數(shù)下, call 和 apply 將失效, 對于箭頭函數(shù)來說:

  • 箭頭函數(shù)體內(nèi)的 this 對象, 就是定義時所在的對象, 而不是使用時所在的對象;所以不需要類似于var _this = this這種丑陋的寫法
  • 箭頭函數(shù)不可以當作構造函數(shù)果港,也就是說不可以使用 new 命令, 否則會拋出一個錯誤
  • 箭頭函數(shù)不可以使用 arguments 對象,,該對象在函數(shù)體內(nèi)不存在. 如果要用, 可以用 Rest 參數(shù)代替
  • 不可以使用 yield 命令, 因此箭頭函數(shù)不能用作 Generator 函數(shù)糊昙,什么是Generator函數(shù)可自行查閱資料辛掠,推薦閱讀阮一峰老師的Generator 函數(shù)的含義與用法Generator 函數(shù)的異步應用
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末释牺,一起剝皮案震驚了整個濱河市萝衩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌船侧,老刑警劉巖欠气,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異镜撩,居然都是意外死亡预柒,警方通過查閱死者的電腦和手機队塘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宜鸯,“玉大人憔古,你說我怎么就攤上這事×苄洌” “怎么了鸿市?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長即碗。 經(jīng)常有香客問我焰情,道長,這世上最難降的妖魔是什么剥懒? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任内舟,我火速辦了婚禮,結果婚禮上初橘,老公的妹妹穿的比我還像新娘验游。我一直安慰自己,他們只是感情好保檐,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布耕蝉。 她就那樣靜靜地躺著,像睡著了一般夜只。 火紅的嫁衣襯著肌膚如雪垒在。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天盐肃,我揣著相機與錄音爪膊,去河邊找鬼。 笑死砸王,一個胖子當著我的面吹牛推盛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播谦铃,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼耘成,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了驹闰?” 一聲冷哼從身側響起瘪菌,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎嘹朗,沒想到半個月后师妙,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡屹培,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年默穴,在試婚紗的時候發(fā)現(xiàn)自己被綠了怔檩。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡蓄诽,死狀恐怖薛训,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情仑氛,我是刑警寧澤乙埃,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站锯岖,受9級特大地震影響介袜,放射性物質發(fā)生泄漏。R本人自食惡果不足惜出吹,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一米酬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧趋箩,春花似錦、人聲如沸加派。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽芍锦。三九已至竹勉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間娄琉,已是汗流浹背次乓。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留孽水,地道東北人票腰。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像女气,于是被迫代替她去往敵國和親杏慰。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344