- 如何正確判斷 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 一旦被綁定就不可改變晚岭。
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ù)的異步應用