阅读 https://juejin.im/post/5d51feaef265da039005219e#heading-18 所得
感觉每一次学习以前学过的知识都可以获得到一些新的东西,很开心 在又一次详细的学习后, 做了复盘 文章都是复习时自己的想法就直接写了进来 这次学习对bind的理解加深了一些 原来bind一旦指定它上下文就无法改变, 还有箭头函数中的this也很奇妙!
this指向
当被问到this指向的时候,首先应该想到的是调用函数有几种方法?
- 函数调用 (直接调用定义好的函数)
- 方法调用(作为对象的方法进行调用)
- 构造函数调用(用new出来的进行调用)
- 隐式调用
- 这里 .call() .apply()是隐式调用
- .bind()属于绑定
- 箭头函数
1. 函数调用
函数调用就是定义好一个函数,然后在全局上下文中调用这个函数
function addNum (a, b) {
return a + b
}
addNum(10,20) // 30
首先需要明确什么是函数调用,以上那个例子就是了
接着需要考虑 函数调用中的this
-
严格模式下, 全局作用域下的this指向undefined
function addNum (a, b) { 'use strict' console.log(this === undefined) //true return a + b } addNum(10, 20)
-
非严格模式下,全局作用域下的this指向window
function addNum (a, b) {
console.log(this === window) //true
return a + b
}
addNum(10, 20)
然后考虑到当this在内部函数中的一个陷阱
const numbers = {
numberA: 5,
numberB: 10,
sum: function () {
console.log(this === numbers) //true
function addNumber () {
console.log(this === numbers) //false
return this.numberA + this.numberB
}
return addNumber()
}
}
numbers.sum()
复盘一下,首先在全局作用域下调用了numbers.sum(),由于sum函数是由numbers这个对象调用的,所以this指向numbers,sum函数最后return了addNumber(),因此执行addNumbers()函数。但是此时需要注意的是,addNumbers并没有被numbers对象调用,所以此时addNumbers中的this指向的是window!全局作用域
所以this!==numbers,this.numbersA和this.numbersB为undefined
解决方法有两种,
第一种,手动绑定this
return addNumber.call(this) //因为在return addNumber.call()这个的上下文中,this指向的是numbers
//所以进行手动绑定的话,addNumbers中的this就指向了numbers
第二种,利用es6中的箭头函数,es6中箭头函数的this指向的定义时所在的那个环境,而不是调用时所在的对象
const addNumber = () => {...}
// 定义addNumber时的上下文的this就是指向的numbers
2.方法调用
方法调用中this其实就是指向了调用这个方法的对象 要注意区分方法调用和对象调用
const cals = {
num: 0,
increment: function () {
console.log(this === cals) //true
this.num += 1
return this.num
}
}
cals.increment() //1
cals.increment() // 2
上面那个例子很浅显易懂,就是cals这个对象调用了increment这个方法,this就指向了cals,所以this.num初始值就是0
这里还有一个需要注意的陷阱,就是当方法与对象分离的时候,其实这里就是考虑到方法名只是一个指向方法的指针
比如 const alone = Object.myMethod
当调用alone()方法时, this指向全局变量。因为alone只是指向 Object.myMethod所指代的这个方法。在全局调用该方法,当然this指向window
function Animal (type, legs) {
this.type = type
this.legs = legs
this.logInfo = function () {
console.log(this === myCat)
console.log(this.type + ' has ' + this.legs + ' legs ')
}
}
const myCat = new Animal('cat', 4)
myCat.logInfo()
//true cat has 4 legs
setTimeout(myCat.logInfo,1000)
//false undefined has undefined legs
后面那个为什么this会指向undefined呢?
因为此时mycat.logInfo相当于是指向logInfo的一个函数名,在setTimeout()中调用时,此时this指向全局作用域,所以this为undefined
3.构造函数调用
构造函数调用就是调用使用new生成的,
function Foo () {
console.log(this instanceof Foo) //true
this.property = 'Default value'
}
const fooInstance = new Foo() //fooInstance是Foo构造函数的实例 fooInstance.__proto__ === Foo.prototype
console.log(fooInstance.property) //Default value 此时this指向fooInstance
陷阱,如果忘了使用new生成实例将会是什么情况?
function Vehicle (type, wheelCount) {
// 如果这里使用判断的话,就会报错,因为此时的car就不是Vehicle的实例呀
// if (!(this instanceof Vehicle)) {
// throw Error('Error: Incorrect invocation')
// }
this.type = type
this.wheelCount = wheelCount
return this
}
var car = Vehicle('Car', 4)
console.log(car.type) //Car
console.log(car.wheelCount) //4
console.log(car === window)
以上例子可以发现,没有用new构造函数来生成car这个对象。虽然能取到car的相关属性值
但是car居然和window全等,所以我们可以知道并没有生成对象,而是在window上设置了属性
4.隐式绑定
隐式绑定主要是call()和apply()方法,如 Runner.call(myRabbit, 4)
this指向myRabbit。 调用Runner方法, 参数4也传入Runner函数里,Runner中的this指向myRabbit
function Runner (name) {
console.log(this instanceof Rabbit)
this.name = name
}
function Rabbit (name, countlegs) {
console.log(this instanceof Rabbit) //true
Runner.call(this, name)
this.countlegs = countlegs
}
const myRabbit = new Rabbit('white Rabbit', 4)
console.log(myRabbit) //Rabbit {name: "white Rabbit", countlegs: 4}
由上例可知,通过构造函数生成对象myRabbit,所以this指向的是myRabbit,所以this instanceof Rabbit是对的;Runner.call(this, name) 这里的this就是myRabbit, 这里的意思就是myRabbit调用Runner方法,传入参数name, this指向myRabbit
5.绑定函数
主要是bind()方法 调用bind方法返回的是一个绑定了this的新函数
function multiply (number) {
'use strict'
return this * number
}
const double = multiply.bind(2)
double(3) //6
double(2) //4
上述例子中,使用了use strict 全局作用域中,this指向undefined 这里使用bind(2) ,绑定了this给2,同时由于bind函数不是立即执行,而是返回一个函数,即double函数。
注意的是绑定函数bind紧密的上下文绑定
bind创建一个永久的上下文链接,并始终保持它,无法通过call()或apply()来改变它
function getThis () {
'use strict'
return this
}
const one = getThis.bind(1)
one() //1 this指向的是1 所以返回1
//现在,我们试图来通过call()或apply来改变
one.apply(2) //1
one.call(3) //1 仍然返回1!!! 意味着bind创建的无法通过call或apply来改变它
6.箭头函数
箭头函数下,this指向定义该箭头函数的执行上下文。箭头函数不会创建自己的执行上下文,而是从它的外部函数中获取this
class Point {
constructor (x, y) {
this.x = x
this.y = y
}
log () {
console.log(this === myPoint) //true
setTimeout(() => {
console.log(this === myPoint) //true
console.log(this.x + ":" + this.y) // '95:165'
},1000)
}
}
const myPoint = new Point (95, 165)
myPoint.log()
以上例子的setTimeout中的是箭头函数,箭头函数中的this指向当前箭头函数所在的上下文环境中,所以this指向myPoint
如果不使用箭头函数呢?其实和我们上面提到过函数调用中最后一个例子相类似
内部函数的this只依赖于它的调用类型,不依赖于它的上下文
log () {
setTimeout(function () {
console.log(this === myPoint) //false
console.log(this.x + ":" + this.y) 'undefined:undefined'
}, 1000)
}
因为这里定时器中的function是内部函数,所以this依赖于它的调用类型,在全局环境下调用,所以此时函数内的this指向的是window,为undefined,所以this === myPoint为false
解决方法
解决方法上文提到过,利用call/apply/bind去绑定
log () {
setTimeout(function () {
console.log(this === myPoint) //false
console.log(this.x + ":" + this.y) 'undefined:undefined'
}.bind(this), 1000)
}
总结一下箭头函数
- 如果箭头函数是在最顶层的作用域中定义的,则上下文始终是全局对象
- 箭头函数一劳永逸地与上下文绑定,即使修改上下文,this也不会改变
const numbers = [1, 2];
(function() {
const get = () => { //这里箭头函数定义在函数内,这个函数当前的上下文是由call(numbers)绑定的,指向numbers
console.log(this === numbers); // => true
return this;
};
console.log(this === numbers); // => true
// 尝试修改get箭头函数的上下文,发现this值不会变化,仍然指向numbers[1,2]
get(); // => [1, 2]
get.call([0]); // => [1, 2]
get.apply([0]); // => [1, 2]
// Bind
get.bind([0])(); // => [1, 2]
}).call(numbers);
!!! 箭头函数真的是一旦一开始确定了this,后面就不会再改变了,无论这个箭头函数是不是被对象调用啊什么的
function Period (hours, minutes) {
this.hours = hours
this.minutes = minutes
}
Period.prototype.format = () => {
console.log(this === window) //true
return this.hours + 'hours and' + this.minutes + 'minutes' //undefined hours and undefined
}
const walkPeriod = new Period(2, 30)
walkPeriod.format()
上面的例子中,很明显,walkPeriod是Period构造函数的实例,因为它会继承Period上的方法format
尽管format方法是由walkPeriod这个对象调用的,平常来说,this会指向这个对象!
但是在箭头函数中就不一样了,箭头函数在定义的时候就确定好了上下文,所以当前这个箭头函数的this指向的是window 所以this.hours this.minutes是undefined