1 什么是this?
this是一个keyword, 它的值总是变换,依赖于调用它场景
有6种情况,this会指向特定的值
(1) global context (全局)
(2) object construction (对象构造函数)
(3) object method (对象方法)
(4) simple function (简单函数)
(5) arrow function (箭头函数)
(6) event listener (事件监听)
2 详细说明6中使用情况
2.1 global context
当在任何函数之外调用this时,即global context, this指向浏览器的默认全局对象Window
console.log(this) // Window
2.2 object construction
当使用new创建新实例时,this指向创建的实例instance
function Human (age) {
this.age = age
}
let greg = new Human(23)
let thomas = new Human(25)
console.log(greg) // this.age = 23
console.log(thomas) // this.age = 25
new一个函数对象,返回被调用的函数名和创建的对象
function ConstructorExample() {
console.log(this);
this.value = 10;
console.log(this);
}
new ConstructorExample();
// -> ConstructorExample {}
// -> ConstructorExample { value: 10 }
2.3 object method
Methods here are defined with ES6 object literal shorthand, 比如下面
let o = {
aMethod () {}
}
在method里面的this,都指向该对象本身
let o ={
sayThis () {
console.log(this) // o
}
}
o.sayThis() // o
2.4 simple function
最常见的函数形式,类似下面的
function hello () {
// say hello!
}
this指向Window, 即使 simple function 在object method中,也是指向Window
function simpleFunction () {
console.log(this)
}
const o = {
sayThis () {
simpleFunction()
}
}
simpleFunction() // Window
o.sayThis() // Window
为什么object method中的 simpleFunction() this没有指向o, 这也是让人迷惑的地方啊,或许下面的代码能解释一下,只能说object method里面嵌套的函数的this都重新指向了Window
const o = {
doSomethingLater () {
setTimeout(function() {
this.speakLeet() // Error, this-->Window
}, 1000)
},
speakLeet() {
console.log(`1337 15 4W350M3`)
}
}
要解决这个问题,需要在嵌套函数外将this赋值给self
const o = {
doSomethingLater () {
const self = this
setTimeout(function () {
self.speakLeet()
}, 1000)
},
speakLeet () {
console.log(`1337 15 4W350M3`)
}
}
2.5 arrow function
在箭头函数中,this总是指向箭头函数所在作用域的对象
比如箭头函数在object method中,那么箭头函数中的this就指向object
const o = {
doSomethingLater () {
setTimeout(() => this.speakLeet(), 1000)
},
speakLeet () {
console.log(`1337 15 4W350M3`)
}
}
2.6 Event Listener
在事件监听函数中,this指向触发事件的元素
let button = document.querySelector('button')
button.addEventListener('click', function() {
console.log(this) // button
})
如果要在监听函数中调用其他函数,需要先将this赋值给其他变量,如self
function LeetSpeaker (elem) {
return {
listenClick () {
const self = this
elem.addEventListener('click', function () {
self.speakLeet()
})
},
speakLeet() { console.log(`1337 15 4W350M3`) }
}
}
当然,使用箭头函数也可以直接使用this, 访问元素可以使用e.currentTarget
function LeetSpeaker (elem) {
return {
listenClick () {
elem.addEventListener('click', (e) => {
console.log(e.currentTarget) // elem
this.speakLeet()
})
},
speakLeet () {
console.log(`1337 15 4W350M3`)
}
}
}
new LeetSpeaker(document.querySelector('button')).listenClick()
如果想要移除监听事件,则需要在绑定事件时,第二个参数--回调函数要使用命名函数,而非匿名函数
function someFunction () {
console.log('do something')
// Removes the event listener.
document.removeEventListener('click', someFunction)
}
document.addEventListener('click', someFunction)
需要注意的是,有些时候,上述的规则会共同作用,这是会有优先级,比如下面的例子,new和对象方法共同作用的情况,那么,new 规则主导
var obj1 = {
value: 'hi',
print: function() {
console.log(this);
},
};
new obj1.print(); // -> print {}
小结:
某些规则共同作用,优先级如下,从前往后,优先级越低:
(1) new 操作符
(2) bind()
(3) call(), apply()
(4) object method
(5) global object, except in strict mode
3 改变this的指向
什么情况下需要改变this的指向?比如想要获得类数组对象如{1:'a', 2: 'b'}的数组值,就可以使用Array.slice方法,但OOP中,方法都是和对象绑定的,所以需要手动修改this的指向。
Javascript中共提供了三种方法修改this的指向, call,apply,bind
3.1 call
我们使用func.call(param), 传递参数给this, 第一个参数绑定给this,后面的作为函数的实参
例子1:不带参数的函数
function logThis() {
console.log(this);
}
var obj = { val: 'Hello!' };
logThis(); // -> Window {frames: Window, postMessage: ƒ, …}
logThis.call(obj); // -> { val: 'Hello!' };
例子2:带参数的函数
function logThisAndArguments(arg1, arg2) {
console.log(this);
console.log(arg1);
console.log(arg2);
}
var obj = { val: 'Hello!' };
logThisAndArguments('First arg', 'Second arg');
// -> Window {frames: Window, postMessage: ƒ, …}
// -> First arg
// -> Second arg
logThisAndArguments.call(obj, 'First arg', 'Second arg');
// -> { val: 'Hello!' }
// -> First arg
// -> Second arg
call + arguments 移花接木
aruguments在Javascript中,是传递给函数的参数,一个类数组的对象
function add() {
console.log(arguments);
}
add(4); // -> { '0': 4 }
add(4, 5); // -> { '0': 4, '1': 5 }
add(4, 5, 6); // -> { '0': 4, '1': 5, '2': 6 }
当有需求要遍历这些参数,使用Array的map, forEach等,我们可以使用call来讲arguments转变为数组
Array.slice:通过this引用,返回调用数组的一份拷贝,如果Array.slice传入arguments, 就会返回一份新的数组,从arguments创建而来的
function add() {
var args = [].slice.call(arguments);
console.log(args);
}
add(4, 5, 6); // -> [ 4, 5, 6 ]
3.2 apply
apply 运行的机制和call类似,不同的是arguments是以数组的形式传递的
function logThisAndArguments(arg1, arg2) {
console.log(this);
console.log(arg1);
console.log(arg2);
}
var obj = { val: 'Hello!' };
logThisAndArguments('First arg', 'Second arg');
// -> Window {frames: Window, postMessage: ƒ, …}
// -> First arg
// -> Second arg
logThisAndArguments.apply(obj, ['First arg', 'Second arg']);
// -> { val: 'Hello!' }
// -> First arg
// -> Second arg
3.3 bind
bind和call, apply运行不太一样,func.bind调用后函数并不立即执行,而是当函数调用时才会触发, 传参的方式和call一样,一个一个传
function logThisAndArguments(arg1, arg2) {
console.log(this);
console.log(arg1);
console.log(arg2);
}
var obj = { val: 'Hello!' };
var fnBound = logThisAndArguments.bind(obj, 'First arg', 'Second arg');
console.log(fnBound);
// -> [Function: bound logThisAndArguments]
fnBound();
// -> { val: 'Hello!' }
// -> First arg
// -> Second arg
function sayThis () {
console.log(this)
}
const boundFunc = sayThis.bind({hippy: 'hipster'})
boundFunc() // {hippy: "hipster"}
遇到箭头函数,可能情况就不一样了
const sayThis = _ => console.log(this)
const boundFunc = sayThis.bind({hippy: 'hipster'})
boundFunc() // Window
传给bind的其他参数则作为实参
const sayParams = (...args) => console.log(...args)
const boundFunc = sayParams.bind(null, 1, 2, 3, 4, 5)
boundFunc() // 1 2 3 4 5
事件监听函数只调用一回的例子
function LeetSpeaker (elem) {
return {
listenClick () {
this.listener = this.speakLeet.bind(this)
elem.addEventListener('click', this.listener)
},
speakLeet(e) {
const elem = e.currentTarget
this.addLeetSpeak()
elem.removeEventListener('click', this.listener)
},
addLeetSpeak () {
const p = document.createElement('p')
p.innerHTML = '1337 15 4W350M3'
document.body.append(p)
}
}
}
const button = document.body.querySelector('button')
const leetSpeaker = LeetSpeaker(button)
leetSpeaker.listenClick()
综合示例:
function logThisAndArguments(arg1, arg2) {
console.log(this);
console.log(arg1);
console.log(arg2);
}
var obj = { val: 'Hello!' };
// NORMAL FUNCTION CALL
logThisAndArguments('First arg', 'Second arg');
// -> Window {frames: Window, postMessage: ƒ, …}
// -> First arg
// -> Second arg
// USING CALL
logThisAndArguments.call(obj, 'First arg', 'Second arg');
// -> { val: 'Hello!' }
// -> First arg
// -> Second arg
// USING APPLY
logThisAndArguments.apply(obj, ['First arg', 'Second arg']);
// -> { val: 'Hello!' }
// -> First arg
// -> Second arg
// USING BIND
var fnBound = logThisAndArguments.bind(obj, 'First arg', 'Second arg');
fnBound();
// -> { val: 'Hello!' }
// -> First arg
// -> Second arg
参考资料:https://zellwk.com/blog/this/