新的一次对this的理解~

阅读 https://juejin.im/post/5d51feaef265da039005219e#heading-18 所得

感觉每一次学习以前学过的知识都可以获得到一些新的东西,很开心 在又一次详细的学习后, 做了复盘 文章都是复习时自己的想法就直接写了进来 这次学习对bind的理解加深了一些 原来bind一旦指定它上下文就无法改变, 还有箭头函数中的this也很奇妙!

this指向

当被问到this指向的时候,首先应该想到的是调用函数有几种方法?

  1. 函数调用 (直接调用定义好的函数)
  2. 方法调用(作为对象的方法进行调用)
  3. 构造函数调用(用new出来的进行调用)
  4. 隐式调用
    • 这里 .call() .apply()是隐式调用
    • .bind()属于绑定
  5. 箭头函数

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值