【es6入门】函数中的this到底指向谁?箭头函数有this指向吗?初学者不再晕了

前言

很多JS初学者第一次接触this的时候,很容易被他的指向搞晕。我当时自学的时候也不例外,看了些文章也是越看越蒙,于是我自己总结了一下自己发现的规律来判断。


ES5中

在es5中,我觉得可以简单的理解this的指向规律为:在代码执行中,函数调用的对象

网上说的“执行上下文”咱就简单粗暴的理解为代码执行过程中。

下面就举例几个场景,你就明白我在说啥了。

在全局作用域中

function fn() {
	console.log(this)
}
fn() // 执行打印window

因为我们的函数是定义在全局作用域中,也就是把变量定义在window上,作为属性(window.fn)。

那么这个函数是属于window对象的,也是window对象在调用这个函数,所以函数中this指向这个函数的拥有者,window。

在对象上

let obj = {
	showThis(){ // 函数简写
		console.log(this)
	}
}
obj.showThis() // 打印obj

一样原理,因为函数是obj上身的,也是obj执行的,所以this就指向obj。

要在执行中判断

在前面说到this的指向规律,有个最重要的前提是在代码执行中,这个在代码执行中怎么理解?

严格来说是在执行上下文,但为了好理解就理解为代码执行过程中

前面讲的this指向的两个例子,我们都是以静态代码去判断的。但实际上,this取的什么值,看的是执行的时候

例如:

let obj = {
    name: 'a',
    showName(){
        console.log(this.name);
    }
}

如果此时执行obj.showName(),按照之前说的这个方法是在对象身上,所以函数内部的this指向这个对象,打印a。

那如果

let fn = obj.showName
fn()

会打印什么呢?可以看到声明了一个变量fn,并将obj上的showName函数赋值给了它。重点来了,fn的声明是在window下的,所以当变量fn执行时,函数的this指向window。

有意思吧,再来个题看看

let obj = {
	showThis(){
		setTimeOut(function(){
			console.log(this)
		}
		,0)
	}
}

当执行obj.showThis(),打印什么呢?

我想初学者基本上认为还是obj,哈哈,其实都忽略了一个点,setTimeOut方法是window上的api,所以你可以这样看:

let obj = {
	showThis(){
		window.setTimeOut(function(){
			console.log(this)
		}
		,0)
	}
}

所以最终是window在执行函数,this指向window,打印window。

执行时,要把代码拿出来

再看个难点的,基本上es5指向就都明白了:

const obj = {
    f3() {
        function fn() { 
        	console.log('this3', this) 
        } 
        fn() 
        fn.call(this) 
    }
}
obj.f3() 

当执行obj.f3()的时候相当于把里面的东西都拿了出来:

function fn() { 
	console.log('this3', this) 
} 
fn() 
fn.call(this)

看,是不是fn在window下声明了,所以当fn执行的时候,this指向window。

call这个方法是改写this指向,后面会讲,此时的传入的this就是window,所以最终打印window。

记住本质,95%的es的this指向问题绝对没问题了!!!!!


ES6箭头函数

先来看看什么是箭头函数

用法

一、完整使用写法:

let fn = (a, b) => {
	// 当{}内有多个js语句,并且入参有2个以上时,必须写完整写法
} 

二、{}内只有一条语句或者return一个东西:

let fn = (n) => n*2
// {}可以省略不写,如果是return,那return和{}一起被省略

//注意:如果返回的是个对象,那么需要用()包起来
() => ({a:1})

三、如果有且仅有一个参数,()可以不写:

let fn = n => {
  console.log(n);
};
// 但没有参数的时候,前面的括号不能省略
let fn = () => { console.log('@') };

this指向

首先要说的结论是:箭头函数没有this!!!

可是箭头函数确实能打印出this啊?那是因为通过作用域链的访问往外找了!而且是在编译阶段找的(行话叫在词法作用域)

重点:

  • 没有this
  • 通过作用域链的访问往外找
  • 在编译阶段找(不理解可以直接认为是定义阶段)

废话少说,拿上面例子改写

let obj = {
	showThis(){ 
		// console.log(this) this为obj
		setTimeOut(()=>{
			console.log(this) 
		}
		,0)
	}
}
obj.showThis() 

箭头函数里访问this,没有,从外面找,找到showThis函数的作用域,如果obj去调用这个函数,this就是指向obj。所以最终打印的是obj。

缺点

箭头函数的缺点都是因为没有this照成的:

  1. 不能用于构造函数。
  2. 内部无arguments对象。
// es5
function fn(){
    console.log(arguments)  //arguments为内置对象,显示1,2,3
}
fn(1,2,3);

// es6
let fn = (...rest) => { // 可以用...代替实现
	console.log(rest)
}
fn(1,2,3);
  1. 不能使用apply、bind、call函数去修改this

  2. 不能定义原型上的方法


class类中

具体可看【es6入门】类的声明与继承,class与extends语法糖


如何改变this指向

主要是三剑客:callapplybind

function fn() {
	console.log(this)
}

fn.call({a: 1}) // 打印this为{a: 1}

可以看到call就是改变指向的同时执行函数。call的第二个参数是fn的入参,例如fn.call(null, 1, 2)

const fn1 = fn.bind({a: 1}) 
fn1() // 打印this为{a: 1}

可以看出,bind改变this指向,并返回改变指向后的函数,还需要自己亲自执行。bind的第二个参数是fn的入参,例如bind(null, 1, 2)()

apply的用法和call一样,只不过第二个参数传的是数组,例如:fn.apply(null, [1, 2])

坑注意

注意了bind有一些大坑!!!

fn.bind(obj) // 没有用新的变量去赋值再执行是不会改变this指向的
fn()

bind还存在闭包现象:

let fn1 = fn.bind(obj) 
fn1() // 假设函数this打印2
fn1.call(obj2) // 上面使用bind会形成闭包,传入的obj2因为闭包的存在导致用的还是obj,所以改完后的this指向还是obj,同样打印2

手写

一般面试到这三者的区别后,有可能还会让你手写一下bind等函数。第一次遇见可能会有点害怕,但其实就是把实现的核心换成除bind以外的另两位函数(同理apply、call也一样)。

es5实现bind:

// 因为三剑客都是挂在Function原型上的,所以实现也挂在原型上
Function.prototype.bindCopy = function () {
    // 将参数拆解为数组
    const args = Array.prototype.slice.call(arguments)

    // 获取 this(数组第一项)
    const t = args.shift()

    // 获取fn1.bindCopy(...) 中的 fn1,可能新人看到这个还是会有点懵逼,我们前面讲到了,谁调用,this就执向谁。所以这里的this就是fn1
    const self = this

    // 返回一个函数,即利用另外两个改变this指向的方法的其中一个
    return function () {
        return self.apply(t, args) // 这里为什么不直接this.apply(t, args),因为this的判断前提是在代码执行的时候
    }
}
function fn1(){
 	console.log(this)
}
fn1.bindCopy ({a:1})()

用es6来实现call:

Function.prototype._call = function (_this, ...rest) {
    let self = this
    self.apply(_this, rest)
}

例题挑战

例题一

var x = 1
var obj = {
    x: 3,
    fun: function () {
        var x = 5
        return this.x
    }
}

var fun = obj.fun;
console.log(obj.fun(), fun());

例题二

const obj = {
    f1() {
        const fn = () => { 
        	console.log('this1', this) 
        } 
        fn() 
        fn.call(window) 
    }
}
obj.f1() 

例题三

const obj = {
    f2: () => { 
        function fn() { 
        	console.log('this2', this) 
        } 
        fn() 
        fn.call(this) 
    }
}
obj.f2()

提示:对象的{}里面不是一个作用域!还要往外找,那就只能是window了。

例题四

面试遇到的,这个你也懂了那估计98%的this指向问题你都没有问题了。

var a = 1
let obj = {
    a: 2,
    fn1: function(){
        return function (){
            console.log('fn1', this.a);
        }
    },
    fn2: function() {
        return () => {
            console.log('fn2', this.a);
        }
    }
}

obj.fn1()()
obj.fn2()()
let fn = obj.fn2
fn()()

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值