前端this指向详解

  • 什么是this
  • this在不同应用场景中的取值
    • 普通函数(非箭头函数)被调用时的this指向
    • 普通函数(非箭头函数)作为对象方法被调用时的this指向
    • 普通函数(非箭头函数)作为class方法被调用时的this指向
    • 普通函数(非箭头函数)被call\apply\bind转化之后被调用时的this指向
    • 箭头函数中的this指向
    • 回调函数的this指向
  • call\apply\bind
    • call\apply\bind区别
    • 手写call\apply\bind
    • 如何选用call\apply\bind
  • 函数柯里化
  • 箭头函数详解
    • 什么是箭头函数
    • 箭头函数的原理
    • 箭头函数的特点(和普通函数的区别)
  • 总结
  • 参考

什么是this

this 是 JavaScript 中的一个关键字。依赖于函数调用的上下文条件。

函数的this指向谁,其实可以分为两种情况讨论:

  1. 普通函数的this:
    普通函数的this指向函数调用者,如果找不到调用者,默认指向window。普通函数的this指向可以通过call\apply\bind去改变
  2. 箭头函数的this
    箭头函数本身并无this,箭头函数的this由定义箭头函数时所处的作用域决定,即箭头函数的this永远指向定义箭头函数时所在的作用域的this(也可以说是上层作用域,强调的是作用域!)。箭头函数的this只和定义时的作用域this有关,和调用者无关,和调用环境无关,也永远不会改变。

this在不同应用场景中的取值

普通函数(非箭头函数)被调用时的this指向

普通函数(非箭头函数)被调用时,this的指向是window
示例:

function fn () {console.log(this); // this指向window
}fn();

普通函数(非箭头函数)作为对象方法被调用时的this指向

普通函数(非箭头函数)作为对象方法被调用时,this的指向是对象本身
示例:

const obj = {name: '张三',normal() {console.log(this); // this指向对象本身,即obj},
}obj.normal();


备注:如果对象方法中使用了计时器如setTimeout,setTimeout中回调函数的this会有不同的情况,在后面我们会介绍setTimeout的this指向

普通函数(非箭头函数)作为class方法被调用时的this指向

普通函数(非箭头函数)作为class方法被调用时,this的指向是class实例本身
示例:

class Person {constructor(name, age) {this.name = name; // this指向class实例本身this.age = age; // this指向class实例本身}logInfo() {console.log(this); // this指向class实例本身}
}const zhangsan = new Person('张三', 20);
zhangsan.logInfo();

普通函数(非箭头函数)被call\apply\bind转化之后被调用时的this指向

call\apply\bind可以改变普通函数(非箭头函数)的this指向,因此普通函数(非箭头函数)被call\apply\bind转化之后,this指向变成指定的对象
示例:
我们定义一个普通函数fn,
当fn作为普通函数正常调用时,this指向window;
当使用call\apply\bind将fn的this指向修改成一个对象obj时,此时this指向obj。

function fn(params1, params2) {console.log(this, params1, params2);
}const obj = {name: '张三',age: 20,
}fn('参数1', '参数2'); // 当fn作为普通函数正常调用时,this指向window;
fn.call(obj, '参数1', '参数2'); // 当使用call将fn的this指向修改成一个对象obj时,此时this指向obj。
fn.apply(obj, ['参数1', '参数2']); // 当使用apply将fn的this指向修改成一个对象obj时,此时this指向obj。
fn.bind(obj, '参数1', '参数2')(); // 当使用bind将fn的this指向修改成一个对象obj时,此时this指向obj。

箭头函数中的this指向

之前我们讲过,箭头函数本身并无this,箭头函数的this由定义箭头函数时所处的作用域决定,即箭头函数的this永远指向定义箭头函数时所在的作用域的this(也可以说是上层作用域,强调的是作用域!)。箭头函数的this只和定义时的作用域this有关,和调用者无关,和调用环境无关,也永远不会改变。接下来我们用以下示例来说明:

  1. 普通函数fn1作为对象obj的方法,并return一个普通函数

    const obj = {name: '张三',age: 20,fn1() {console.log(this); // 打印obj,普通函数(非箭头函数)作为对象方法被调用时,this的指向是对象本身return function () {console.log(this); // 打印window,因为fn1 return出去的函数,调用者并不是obj,实际上是相当于普通函数(非箭头函数)的调用方式,所以此时this指向window}},
    }obj.fn1()();
    

  2. 普通函数fn2作为对象obj的方法,并return一个箭头函数

    const obj = {name: '张三',age: 20,fn2() {console.log(this); // 打印obj,普通函数(非箭头函数)作为对象方法被调用时,this的指向是对象本身return () => {console.log(this); // 打印obj,箭头函数本身并无this,箭头函数的this由定义箭头函数时所处的作用域决定,即箭头函数的this永远指向定义箭头函数时所在的作用域的this(也可以说是上层作用域,强调的是作用域!)}},
    }obj.fn2()();
    

  3. 对象obj的对象字面量fn3赋值一个箭头函数,并return一个箭头函数

    const obj = {name: '张三',age: 20,fn3: () => {console.log(this); // 打印window// 箭头函数本身并无this,箭头函数的this由定义箭头函数时所处的作用域决定,即箭头函数的this永远指向定义箭头函数时所在的作用域的this(也可以说是上层作用域,强调的是作用域!)// 在字面量中直接定义的箭头函数无法继承该对象的this,而是往外再找一层,就找到了window,因为字面量对象无法形成自己的一层作用域,但是构造函数可以哦return () => {console.log(this); // 打印window,箭头函数本身并无this,箭头函数的this由定义箭头函数时所处的作用域决定,即箭头函数的this永远指向定义箭头函数时所在的作用域的this(也可以说是上层作用域,强调的是作用域!)}},
    }obj.fn3()();
    

  4. 对象obj的对象字面量fn4赋值一个普通函数,并return一个普通函数,再return一个箭头函数

    const obj = {name: '张三',age: 20,fn4: function () { console.log(this) // 打印obj,普通函数(非箭头函数)作为对象方法被调用时,this的指向是对象本身return function () {console.log(this); // 打印window,因为fn4 return出去的函数,调用者并不是obj,实际上是相当于普通函数(非箭头函数)的调用方式,所以此时this指向windowreturn () => {console.log(this); // 打印window,箭头函数本身并无this,箭头函数的this由定义箭头函数时所处的作用域决定,即箭头函数的this永远指向定义箭头函数时所在的作用域的this(也可以说是上层作用域,强调的是作用域!)}}},
    }obj.fn4()()();
    

  5. 普通函数fn,return一个箭头函数

    function fn() {console.log(this); // 打印window,普通函数(非箭头函数)被调用时,this的指向是windowreturn () => {console.log(this); // 打印window,箭头函数本身并无this,箭头函数的this由定义箭头函数时所处的作用域决定,即箭头函数的this永远指向定义箭头函数时所在的作用域的this(也可以说是上层作用域,强调的是作用域!)}
    }fn()();
    

  6. 普通函数fn,return一个箭头函数,并且fn通过call修改this指向为obj

    const obj = {name: '张三',age: 20,
    }function fn() {console.log(this); // 打印obj,普通函数可以被call修改this指向return () => {console.log(this); // 打印obj,箭头函数本身并无this,箭头函数的this由定义箭头函数时所处的作用域决定,即箭头函数的this永远指向定义箭头函数时所在的作用域的this(也可以说是上层作用域,强调的是作用域!)}
    }fn.call(obj)(); // 使用call修改fn的this指向
    

  7. 普通函数fn,return一个普通函数

    function fn() {console.log(this); // 打印window,普通函数(非箭头函数)被调用时,this的指向是windowreturn function() {console.log(this); // 打印window,普通函数(非箭头函数)被调用时,this的指向是window}
    }fn()();
    

  8. 普通函数fn,return一个普通函数,并且fn通过call修改this指向为obj

    function fn() {console.log(this); // 打印obj,普通函数可以被call修改this指向return function() {console.log(this); // 打印window,因为fn return出去的普通函数,调用者并不是obj,实际上是相当于普通函数(非箭头函数)的调用方式,所以此时this指向window}
    }fn.call(obj)()
    

回调函数的this指向

计时器setTimeout\setInterval的回调函数,包括各种方法的回调函数,实际上都比较特殊,关于这类回调函数的this指向,首先我们要关注两个点,第一是回调函数的类型是属于普通函数还是箭头函数;第二是回调函数的调用者是谁,是window还是对象本身。

const obj = {name: '张三',fn1() {console.log(this); // 打印obj,普通函数(非箭头函数)作为对象方法被调用时,this的指向是对象本身},fn2() {setTimeout(function() {console.log(this); // 打印window,因为fn2中的setTimeout的回调函数,调用者并不是obj})},fn3() {setTimeout(() => {console.log(this); // 打印obj,因为fn23中的setTimeout的回调函数是一个箭头函数,箭头函数本身并无this,箭头函数的this由定义箭头函数时所处的作用域决定,即箭头函数的this永远指向定义箭头函数时所在的作用域的this(也可以说是上层作用域,强调的是作用域!)})}
}obj.fn1();
obj.fn2();
obj.fn3();

call\apply\bind

call,apply,bind都是可以改变普通函数的this指向(不能改变箭头函数的this指向)的方法,这三个函数实际上都是绑定在Function构造函数的prototype上,而每一个函数都是Function的实例,因此每一个函数都可以直接调用call\apply\bind

call\apply\bind区别

  1. call的第一个参数是this要指向的对象,并且第二个参数开始,可以接收多个参数作为改造后的函数参数,使用call方法,改造后的函数会立即执行。call的返回值是使用调用者提供的 this 值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined。

    const obj = {name: '张三',age: 20,
    }
    function fn(params1, params2) {console.log(this.name, this.age, params1, params2);
    }
    const result = fn.call(obj, '参数1', '参数2'); // 打印:张三, 20, 参数1, 参数2
    console.log(result); // 打印undefined
    
  2. apply接收两个参数,第一个参数是this要指向的对象,第二个参数是一个数组(或一个类数组对象),数组或者类数组对象中包裹的是要传递给改造后的函数的参数,使用apply方法,改造后的函数会立即执行。apply的返回值是使用调用者提供的 this 值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined。

    const obj = {name: '张三',age: 20,
    }
    function fn(params1, params2) {console.log(this.name, this.age, params1, params2);
    }
    const result = fn.apply(obj, ['参数1', '参数2']); // 打印:张三, 20, 参数1, 参数2
    console.log(result); // 打印undefined
    
  3. bind的第一个参数是this要指向的对象,并且第二个参数开始,可以接收多个参数作为改造后的函数参数,使用bind方法,改造后的函数并不会立即执行,而是作为bind方法的返回值return出来,因此需要额外调用。bind的返回值就是改造后的函数

    const obj = {name: '张三',age: 20,
    }
    function fn(params1, params2) {console.log(this.name, this.age, params1, params2);
    }
    const resultFun = fn.bind(obj, '参数1', '参数2');
    resultFun(); // 打印:张三, 20, 参数1, 参数2
    

手写call\apply\bind

  1. 实现call
    首先我们要分析call的主要特性:
    ①call挂载在Function构造函数的原型链上
    ②call的调用者必须是函数
    ③接收的参数数量不固定,因此我们需要获取传入call的实参,即通过argument实现
    ④argument是类数组对象,我们需要将类数组对象转成数组,有三种方法:Array.prototype.slice.call、Array.from、循环遍历
    ⑤接收的第一个参数是this指定的对象(要保证指定的this指向(即context)必须是一个复杂类型的数据结构(如object\function),不能是简单类型的数据(如boolean\number\string\symbol)。如果context是简单类型的数据,需要转成对应类型的对象;如果context是复杂类型的数据,则保持不变;如果是undefined、null,则默认指向window;)(判断数据类型最推荐的办法是通过Object.prototype.toString.call()来判断,但是我们需要手动实现call\apply\bind,那么这里就不通过这种方式来判断数据类型,而是通过其他的方法来判断数据类型)
    ⑥接收的参数,从第二个算起,是传给改造之后的函数的,作为改造之后的函数的参数
    ⑦call是立即执行函数
    ⑧call返回的是改造后的函数所返回的值(原函数返回啥,call就返回啥)

    从上述主要特性,那么我们可以着手实现call:

    // 实现call
    Function.prototype.myCall = function () { // call挂载在Function构造函数的原型链上,因此myCall也要挂载在Function的prototype上// 先获取调用myCall的函数const fn = this;// myCall的调用者数据类型必须是函数,否则抛出错误if(typeof fn !== 'function') {throw TypeError('Not a Function');}const args = []; // 创建一个数组,用于接收myCall的实参/*arguments是原生js的一个对象,可以获取传入myCall函数的实参,是一个类数组对象,类数组对象拥有数组的特性,即length,但是不能使用Array的方法我们需要将arguments类数组对象转成数组:1.正常情况下可以通过Array.prototype.slice.call()的方式,即Array.prototype.slice.call(arguments)转成数组,但是我们还没实现call,所以此方法暂时不用2.es6中的新方法Array.from()也可以将类数组对象转成数组,即Array.from(arguemtns)3.我们使用最原始的方法,以循环的形式,将类数组对象转成数组,获取myCall的实参数组*/for (let i = 0; i < arguments.length; i++) {args.push(arguments[i]);}// 获取指定的this指向,即实参数组中的第一个值,并将指定的this指向从参数数组中剔除let context = args.shift() ;/*容错机制:要保证指定的this指向(即context)必须是一个复杂类型的数据结构(如object\function),不能是简单类型的数据(如boolean\number\string\symbol)。如果context是简单类型的数据,需要转成对应类型的对象;如果context是复杂类型的数据,则保持不变;如果是undefined、null,则默认指向window;备注:其实判断数据类型最推荐的办法是通过Object.prototype.toString.call()来判断,但是我们需要手动实现call\apply\bind,那么这里就不通过这种方式来判断数据类型,而是通过其他的方法来判断数据类型*/if (typeof context === 'undefined') {context = window;} else if (typeof context === 'boolean') {context = new Boolean(context);} else if (typeof context === 'number') {context = new Number(context);} else if (typeof context === 'string') {context = new String(context);} else if (typeof context === 'symbol') {context = new Symbol(context);} else if (typeof context === 'function') {context = context;} else if (typeof context === 'object') {// context = context ? context : window;context = context ? context : window;}let randomAttributeName = Symbol(); // 创建一个随机属性名,Symbol()始终返回唯一值context[randomAttributeName] = fn; // 将fn临时作为context这个对象的属性存储起来,去调用fn的时候this也就指向contextlet result = context[randomAttributeName](...args); // 用context这个对象调用函数fndelete context[randomAttributeName]; // 删除临时绑定在context上的属性,让context回归原本的样子return result; // call返回的是改造后的函数调用后所返回的值(原函数返回啥,call就返回啥)
    }// 测试
    function fn(params1, params2) {console.log(this, params1, params2)
    }const obj = {name: '张三',age: 20,
    }fn.myCall(obj, '参数1', '参数2'); // 打印obj、参数1、参数2
    
  2. 实现apply (类似call的实现,只是参数传递不同)
    首先我们要分析apply的主要特性:
    ①apply挂载在Function构造函数的原型链上
    ②apply的调用者必须是函数
    ③接收两个参数,第一个参数是指定的this指向,第二个参数是数组,数组中是要传给改造后的函数作为参数
    ④接收的第一个参数是this指定的对象(要保证指定的this指向(即context)必须是一个复杂类型的数据结构(如object\function),不能是简单类型的数据(如boolean\number\string\symbol)。如果context是简单类型的数据,需要转成对应类型的对象;如果context是复杂类型的数据,则保持不变;如果是undefined、null,则默认指向window;)(判断数据类型最推荐的办法是通过Object.prototype.toString.call()来判断,但是我们需要手动实现call\apply\bind,那么这里就不通过这种方式来判断数据类型,而是通过其他的方法来判断数据类型)
    ⑤apply是立即执行函数
    ⑥apply返回的是改造后的函数所返回的值(原函数返回啥,call就返回啥)

    从上述主要特性,那么我们可以着手实现apply:

    // 实现apply
    Function.prototype.myApply = function (context, args = []) { // apply挂载在Function构造函数的原型链上,因此myApply也要挂载在Function的prototype上// 第一个参数是指定的this指向// 第二个参数是一个数组,数组中是要传给改造后的函数作为参数// 先获取调用myApply的函数const fn = this;// myApply的调用者数据类型必须是函数,否则抛出错误if(typeof fn !== 'function') {throw TypeError('Not a Function');}/*容错机制:要保证指定的this指向(即context)必须是一个复杂类型的数据结构(如object\function),不能是简单类型的数据(如boolean\number\string\symbol)。如果context是简单类型的数据,需要转成对应类型的对象;如果context是复杂类型的数据,则保持不变;如果是undefined、null,则默认指向window;备注:其实判断数据类型最推荐的办法是通过Object.prototype.toString.call()来判断,但是我们需要手动实现call\apply\bind,那么这里就不通过这种方式来判断数据类型,而是通过其他的方法来判断数据类型*/if (typeof context === 'undefined') {context = window;} else if (typeof context === 'boolean') {context = new Boolean(context);} else if (typeof context === 'number') {context = new Number(context);} else if (typeof context === 'string') {context = new String(context);} else if (typeof context === 'symbol') {context = new Symbol(context);} else if (typeof context === 'function') {context = context;} else if (typeof context === 'object') {// context = context ? context : window;context = context ? context : window;}let randomAttributeName = Symbol(); // 创建一个随机属性名,Symbol()始终返回唯一值context[randomAttributeName] = fn; // 将fn临时作为context这个对象的属性存储起来,去调用fn的时候this也就指向contextlet result = context[randomAttributeName](...args); // 用context这个对象调用函数fndelete context[randomAttributeName]; // 删除临时绑定在context上的属性,让context回归原本的样子return result; // myApply返回的是改造后的函数调用后所返回的值(原函数返回啥,myApply就返回啥)
    }// 测试
    function fn(params1, params2) {console.log(this, params1, params2)
    }const obj = {name: '张三',age: 20,
    }fn.myApply(obj, ['参数1', '参数2']); // 打印obj、参数1、参数2
    
  3. 实现bind(类似call的实现,只是bind返回的是函数)
    首先我们要分析bind的主要特性:
    ①bind挂载在Function构造函数的原型链上
    ②bind的调用者必须是函数
    ③接收的参数数量不固定,因此我们需要获取传入bind的实参,即通过argument实现
    ④argument是类数组对象,我们需要将类数组对象转成数组,有三种方法:Array.prototype.slice.call、Array.from、循环遍历
    ⑤接收的第一个参数是this指定的对象(要保证指定的this指向(即context)必须是一个复杂类型的数据结构(如object\function),不能是简单类型的数据(如boolean\number\string\symbol)。如果context是简单类型的数据,需要转成对应类型的对象;如果context是复杂类型的数据,则保持不变;如果是undefined、null,则默认指向window;)(判断数据类型最推荐的办法是通过Object.prototype.toString.call()来判断,但是我们需要手动实现call\apply\bind,那么这里就不通过这种方式来判断数据类型,而是通过其他的方法来判断数据类型)
    ⑥接收的参数,从第二个算起,是传给改造之后的函数的,作为改造之后的函数的参数
    ⑦bind返回值的是一个函数,即改造之后的函数,不是立即执行的,需要手动调用
    ⑧因为bind返回的是函数,而不是立即执行的,所以我们需要优化这个return出来的函数,让其支持柯里化

    从上述主要特性,那么我们可以着手实现bind:

    // 实现bind
    Function.prototype.myBind = function () { // bind挂载在Function构造函数的原型链上,因此myBind也要挂载在Function的prototype上// 先获取调用myBind的函数const fn = this;// myBind的调用者数据类型必须是函数,否则抛出错误if(typeof fn !== 'function') {throw TypeError('Not a Function');}const args = []; // 创建一个数组,用于接收myBind的实参/*arguments是原生js的一个对象,可以获取传入myBind函数的实参,是一个类数组对象,类数组对象拥有数组的特性,即length,但是不能使用Array的方法我们需要将arguments类数组对象转成数组:1.正常情况下可以通过Array.prototype.slice.call()的方式,即Array.prototype.slice.call(arguments)转成数组,但是我们还没实现call,所以此方法暂时不用2.es6中的新方法Array.from()也可以将类数组对象转成数组,即Array.from(arguemtns)3.我们使用最原始的方法,以循环的形式,将类数组对象转成数组,获取myBind的实参数组*/for (let i = 0; i < arguments.length; i++) {args.push(arguments[i]);}// 获取指定的this指向,即实参数组中的第一个值,并将指定的this指向从参数数组中剔除let context = args.shift() ;/*容错机制:要保证指定的this指向(即context)必须是一个复杂类型的数据结构(如object\function),不能是简单类型的数据(如boolean\number\string\symbol)。如果context是简单类型的数据,需要转成对应类型的对象;如果context是复杂类型的数据,则保持不变;如果是undefined、null,则默认指向window;备注:其实判断数据类型最推荐的办法是通过Object.prototype.toString.call()来判断,但是我们需要手动实现call\apply\bind,那么这里就不通过这种方式来判断数据类型,而是通过其他的方法来判断数据类型*/if (typeof context === 'undefined') {context = window;} else if (typeof context === 'boolean') {context = new Boolean(context);} else if (typeof context === 'number') {context = new Number(context);} else if (typeof context === 'string') {context = new String(context);} else if (typeof context === 'symbol') {context = new Symbol(context);} else if (typeof context === 'function') {context = context;} else if (typeof context === 'object') {// context = context ? context : window;context = context ? context : window;}// bind方法返回的是一个函数,所以myBind也应该返回一个函数,而且这个函数的this得是指定的this指向,即contextreturn function F(...rest) { // 除了通过arguments可以获取F的实参外,还能通过剩余参数...rest的形式来获取F的实参。这边是为了区分myBind和F的实参,所以F不用arguments而是...restif (this instanceof F) { // 处理myBind return出去的函数被作为构造函数使用的情况,即通过new运算符来调用return出去的函数实例( 如 new func.bind() ) (很少有这样的用法,一般都是return出去之后直接调用的)return new fn(...args, ...rest);}/*以下一段完全可以用return fn.apply(context, args.concat(...rest))代替。只是我们假装没有apply方法,故以下面的写法来实现*/let randomAttributeName = Symbol(); // 创建一个随机属性名,Symbol()始终返回唯一值context[randomAttributeName] = fn; // 将fn临时作为context这个对象的属性存储起来,去调用fn的时候this也就指向contextlet paramsArr = args.concat(...rest); // args.concat(...rest)是为了支持函数柯里化let result = context[randomAttributeName](...paramsArr); // 用context这个对象调用函数fndelete context[randomAttributeName]; // 删除临时绑定在context上的属性,让context回归原本的样子return result;}
    }// 测试
    function fn(params1, params2) {console.log(this, params1, params2)
    }const obj = {name: '张三',age: 20,
    }fn.myBind(obj, '参数1', '参数2')(); // 打印obj、参数1、参数2
    

如何选用call\apply\bind

如果不需要关心具体有多少参数被传入函数,选用apply();
如果确定函数可接收多少个参数,并且想一目了然表达形参和实参的对应关系,用call();
如果我们想要将来再调用方法,不需立即得到函数返回结果,则使用bind();

函数柯里化

手动实现bind函数的底层原理就是函数柯里化的应用,关于函数柯里化,可以参考我的文章函数柯里化详解

箭头函数详解

什么是箭头函数

箭头函数是ES6的新特性,箭头函数本质上也是一个匿名函数。箭头函数表达式的语法比函数表达式更简洁,并且没有自己的this,arguments,super或new.target。箭头函数表达式更适用于那些本来需要匿名函数的地方,并且它不能用作构造函数。
创造箭头函数的初衷就是为了简化函数的定义,以及规避this指向带来的问题。

  1. 写法:
x => x * x

相当于:

function (x) {return x * x;
}
  1. 如果使用箭头函数,以前的那种hack写法就不再需要了。
var that = this; // 不再需要这样的写法

箭头函数的原理

实现原理还没找到,后续补充

箭头函数的特点(和普通函数的区别)

  1. this指向:

    • 普通函数this指向:
      指向它的调用者,如果没有调用者则默认指向window
    • 箭头函数指向:
      箭头函数本身并无this,箭头函数的this由定义箭头函数时所处的作用域决定,即箭头函数的this永远指向定义箭头函数时所在的作用域的this(也可以说是上层作用域,强调的是作用域!)。箭头函数的this只和定义时的作用域this有关,和调用者无关,和调用环境无关,也永远不会改变(因此不能使用call\apply\bind改变箭头函数的this指向)。
      MDN的解释:箭头函数不会创建自己的this,所以它没有自己的this,它只会从自己的作用域链的上一层继承this。
  2. 箭头函数不能当构造函数,用的话会抛出一个错误(因此也不能使用new关键字)

    var Fun = (name, age) => { this.name = name; this.age = age; };
    var foo = new Foo('张三', 20); // TypeError: Foo is not a constructor
    

    我们先了解一下构造函数的new都做了些什么?简单来说,分为四步:

    ① JS内部首先会先生成一个对象;

    ② 再把函数中的this指向该对象;

    ③ 然后执行构造函数中的语句;

    ④ 最终返回该对象实例。

    但是!!因为箭头函数没有自己的this,它的this其实是继承了外层执行环境中的this,且this指向永远不会随在哪里调用、被谁调用而改变,所以箭头函数不能作为构造函数使用,或者说构造函数不能定义成箭头函数,否则用new调用时会报错!

  3. 箭头函数没有 prototype 属性

    var Foo = () => {};
    console.log(Foo.prototype); // undefined
    
  4. 箭头函数没有自己的arguments对象。在箭头函数中访问arguments实际上获得的是外层局部(函数)执行环境中的值。

    // 例子一
    let fun = (val) => {console.log(val);   // 111// 下面一行会报错// Uncaught ReferenceError: arguments is not defined// 因为外层全局环境没有arguments对象console.log(arguments);
    };
    fun(111);// 例子二
    function outer(val1, val2) {let argOut = arguments;console.log(argOut);    // ①let fun = () => {let argIn = arguments;console.log(argIn);     // ②console.log(argOut === argIn);  // ③};fun();
    }
    outer(111, 222);
    

    依据上面的输出结果,
    很明显,普通函数outer内部的箭头函数fun中的arguments对象,其实是沿作用域链向上访问的外层outer函数的arguments对象。

    但是,我们可以在箭头函数中使用rest参数(也叫剩余参数)代替arguments对象,即通过(形式为: …rest)来访问箭头函数的参数列表!!

    function foo(...arg) { return arg;
    }foo(1, 2, 3, 4); // 1
    
    function foo(...numbers) { numbers.forEach((number)=>{console.log(number);})
    }
    foo(1, 2, 3, 4);  // 1 2 3 4
    
  5. 无法使用yield命令,yield 关键字通常不能在箭头函数中使用(除非是嵌套在允许使用的函数内),所以箭头函数无法用作Generator函数

  6. 因为没有自己的this,所以没法通过bind、call、apply来改变this指向

  7. 但是这不代表箭头函数的this指向是静态的,我们可以通过改变它外层代码库的this指向来控制

  8. 箭头函数的this从外层代码库继承,所以箭头函数的this是在定义的时候就绑定好了的,而普通函数是在调用的时候确定this指向

  9. 字面量对象中直接定义的箭头函数的this不绑定该对象,而是往外找一层,最简单的情况是绑定到window(因为对象字面量形成不了作用域)

  10. 只有一个参数的时候,参数可以不加小括号,没有参数或2个及以上参数的,必须加上小括号

  11. 返回语句只有一条的时候可以不写{}和return,会自动加上return的,返回多条语句时必须加上{}和return

  12. 箭头函数在返回对象的时候必须在对象外面加上小括号
    记住用params => {object:literal}这种简单的语法返回对象字面量是行不通的。

    var func = () => { foo: 1 };
    // Calling func() returns undefined!var func = () => { foo: function() {} };
    // SyntaxError: function statement requires a name
    

    这是因为花括号({} )里面的代码被解析为一系列语句(即 foo 被认为是一个标签,而非对象字面量的组成部分)。

    所以,记得用圆括号把对象字面量包起来:

    var func = () => ({foo: 1});
    
  13. 箭头函数位于构造函数内部,它的定义生效的时候,是在构造函数执行的时候。这时,箭头函数所在的运行环境,肯定是实例对象,所以this会总是指向实例对象。

  14. 箭头函数是匿名函数,普通函数可以是匿名函数也可以是具名函数

总结

  1. 普通函数的this不看作用域,而是看调用者,即谁调用,this就指向谁(call\apply\bind改造之后除外)
  2. 箭头函数本身无this,其函数内部的this不看函数调用者,而是看定义箭头函数时所处的作用域,即箭头函数的this永远指向定义箭头函数时所处的作用域的this
  3. 对象字面量形成不了作用域,但是构造函数可以形成作用域

参考

https://blog.csdn.net/qinge_Crazy/article/details/120087645
https://blog.csdn.net/weixin_46267040/article/details/125370111
https://blog.csdn.net/fe_watermelon/article/details/125700486
https://blog.csdn.net/XIAO_A_fighting/article/details/116701887

前端this指向详解相关推荐

  1. window 程序报错 自动重启_好程序员web前端教程之详解JavaScript严格模式

    好程序员web前端教程之详解JavaScript严格模式,严格模式(Strict mode)是由ECMA-262规范定义的新兴JavaScript标准,发布于2009年12月第五版.旨在改善错误检查功 ...

  2. Javascript this关键字 指向详解

    Javascript this关键字 指向详解 面向对象语言中 this 表示当前对象的一个引用.在 JavaScript 中 this 不是固定不变的,它会随着执行环境的改变而改变. 1) 单独使用 ...

  3. DD每周前端七题详解-第五期

    DD每周前端七题详解-第五期 系列介绍 你盼世界,我盼望你无bug.Hello 大家好!我是霖呆呆! 呆呆每周都会分享七道前端题给大家,系列名称就是「DD每周七题」. 系列的形式主要是:3道JavaS ...

  4. layUI前端框架使用详解_layUI前端框架视频教程

    百度云网盘下载 ayUI前端框架使用详解_layUI前端框架视频教程 课程目录: 1前言 2为什么要用layUI框架 3layer组件的引用方法 4layer组件的使用方法详解 5用layer组件快速 ...

  5. layUI前端框架使用详解_layUI前端框架视频教程完整版

    layUI前端框架使用详解_layUI前端框架视频教程 课程目录: 1前言 2为什么要用layUI框架 3layer组件的引用方法 4layer组件的使用方法详解 5用layer组件快速开发一个表单验 ...

  6. 前端路由模式详解(hash和history)

    前端路由模式详解(hash和history) 前端路由有两种模式:hash 模式和 history 模式,接下来分析这两种模式的实现方式和优缺点. hash 模式 hash 模式是一种把前端路由的路径 ...

  7. 一零四、前端性能优化详解

    1 前端性能优化 介绍 页面性能优化 浏览器 浏览器的主要作用 浏览器的组成结构 浏览器是多进程的 浏览器的渲染机制 重排reflow与重绘repaint 页面加载缓慢的原因 浏览器部分 代码部分 优 ...

  8. 用WEB技术栈开发NATIVE应用(二):WEEX 前端SDK原理详解

    摘要: WEEX依旧采取传统的web开发技术栈进行开发,同时app在终端的运行体验不输native app.其同时解决了开发效率.发版速度以及用户体验三个核心问题.那么WEEX是如何实现的?目前WEE ...

  9. web前端 --- HTML标签详解

    HTML标签详解 head头标签: meta标签.title标签.link标签.style标签.script标签 body标签: 字体标签: h1~h6标签.font标签.u标签.b标签.strong ...

最新文章

  1. 构建JSE 开发环境(图文并茂)
  2. SDUT_2116 数据结构实验之链表一:顺序建立链表
  3. java lombok
  4. android 格式化代码
  5. 网上整理的对于Rest和Restful api的理解 - 那啥快看 - 博客园
  6. [蓝桥杯2017初赛]等差素数列-巧妙枚举(思维)
  7. portal认证 php,如何用PHP制作OSSH 免费版华为Portal系统认证前端页面
  8. Google Map 附近查询
  9. JAVA面向对象之对象和类
  10. 计算机一级标题底纹,2017年计算机一级考试MSOffice考点解析:幻灯片背景的设置...
  11. linux firefox 显示PDF,某个 pdf 文件用 firefox 和 okular 显示乱码
  12. Thinkphp 5.0 仿百度糯米开发多商家电商平台
  13. 关于高通平台下camera一些参数的设置
  14. 精心挑选12款优秀 jQuery 手风琴效果插件和教程
  15. 导航信号测试用什么软件,专业GPS测试软件 VisualGPSXP入门
  16. 首都师范 博弈论 6 5 1有限次的重复博弈
  17. xlinx芯片JTAG烧录
  18. NX二次开发 UFUN创建倒角特征 UF_MODL_create_chamfer
  19. 升级本地javaweb项目为https访问
  20. UC Berkeley AI Project -MindsDB 学习

热门文章

  1. linux pptp客户端_Linux下如何配置pptp
  2. 小众框架JeePlus的理解
  3. vcode(visual studio code)中文配置
  4. 暗黑世界 网络游戏从0开始搭建
  5. 车内看车头正不正技巧_驾考科目二自动挡学车攻略小技巧。
  6. mac osx下jetbrains家 IDE 开发工具 错误修复 优化
  7. 物联网中的设备,都需要传感器的支持吗
  8. elementui 下拉框滚动条样式修改
  9. 屌炸天的browserlink,前端开发的福音
  10. 211213-LaTex生成Reference但无法显示Journal