this 的五种绑定方式(默认 / 隐式 / 显式 / new 构造函数 / 箭头函数)

目录

1. this 的五种绑定方式

1.1 默认绑定

1.1.1 基本概念

1.1.2 严格模式下的默认绑定

1.1.3 let、const、var 变量的默认绑定

1.1.4 函数作用域中的 this 指向

1.2 隐式绑定

1.2.1 关于 this 指向最后调用函数的那个对象,该怎么理解?

1.2.2 隐式绑定中的 this 丢失问题

1.2.3 使用 另一个变量 给函数取别名,会发生隐式丢失

1.2.4 将函数作为参数传递时,会发生隐式丢失

1.3 显示绑定

1.3.1 call()、apply()、bind() 三者区别

1.3.2 使用 call() 解决 setTimeout 内 this 丢失的问题

1.3.3 ☆★☆★ 函数返回的函数被调用,除非被 call 修改 this 指向,否则都是 window 在调用 ☆★☆★

1.3.4 call() 调用位置不同,导致结果不同

1.3.5 bind() 调用位置不同,导致结果不同

1.3.6 在函数内使用 call 显式绑定一个对象,实现函数内部 this 指向 始终指向该对象

1.3.7 冷门知识:forEach、map、filter 第二个参数可以改变 this 指向

1.3.8 ☆★☆★ 总结了一下 显式绑定 需要注意的点 ☆★☆★

1.4 new 构造函数绑定

1.4.1 基本用法

1.4.2 字面量形式创建对象 VS new 构造函数创建对象

1.4.3 new 绑定结合显示绑定

1.5 箭头函数绑定

1.5.1 基本用法 —— 通过查找作用域链,确定箭头函数的 this

1.5.2 字面量对象中,普通函数与箭头函数的区别: 只有一层函数的题目

1.5.3 字面量对象中,普通函数与箭头函数的区别:函数嵌套(返回函数)的题目

1.5.4 构造函数对象中,普通函数和箭头函数的区别:只有一层函数的题目

1.5.5 构造函数对象中,普通函数和箭头函数的区别:函数嵌套(返回函数)的题目

1.5.6 箭头函数 不能被 显式绑定函数(call、apply、bind)修改 this 指向

1.5.7 ☆★☆★ 箭头函数必坑指南 / 不能使用箭头函数的场景 ☆★☆★

2. 参考文章(非常有用👍)


1. this 的五种绑定方式

1.1 默认绑定

1.1.1 基本概念

非严格模式下,this 指向全局对象(window / self / global)

严格模式下,函数内的 this 指向 undefined,全局中的 this 指向不会改变

1.1.2 严格模式下的默认绑定

举个栗子~

"use strict";

// var 定义的变量 a 挂载到 window 对象上
var a = 10;

function foo () {
  // 严格模式下,函数内 this 指向 undefind
  console.log('this1 --- ', this) // undefined

  // 报错,Uncaught TypeError: Cannot read properties of undefined (reading 'a')
  console.log(this.a) 

  console.log(window.a) // 10
}

// 严格模式 不会改变全局中 this 的指向
console.log('this2', this) // window

foo();

1.1.3 let、const、var 变量的默认绑定

let、const 声明的全局变量,不会被绑定到 window 上

var 声明的全局变量,会被绑定到 window 上

let a = 10
const b = 20

function foo () {
  console.log(this.a) // undefined
  console.log(this.b) // undefined
}

foo();

console.log(window.a) // undefined  

1.1.4 函数作用域中的 this 指向

关键点:

  • 要判断函数是被谁调用的,进而确定 this 绑定的作用域是哪个
  • 要确定好打印的是 函数内部的变量,还是 this 上的变量(打印 a、打印 this.a 不一样)
// window 中的 a
var a = 1;

function foo () {
  // 函数中的 a
  var a = 2

  console.log(this) // Window{...}

  // 打印的是 this.a,不是 a,因此是 window 下的 a(是 window 调用的 foo 函数)
  console.log(this.a) // 1
}

foo()

 再举个例子~

var a = 1

function foo () {

  var a = 2

  function inner () {
    // inner 内没有 a,因此往上面的 foo 的函数作用域找
    // foo 内有 a,但是 foo 是 window 调用的,foo 内的 this 就是 window
    // 打印的是 this a,不是 a,因此要判断正确的 this,也就是 window
    // 最终输出 1
    console.log(this.a)
  }

  inner()
}

foo()

1.2 隐式绑定

当 函数引用 有 上下文对象 时,如 obj.foo() 的调用方式,foo() 内的 this 指向 obj

也就是说,谁调用函数,函数内的 this 就指向谁(无论是普通对象、还是全局对象),this 永远指向最后调用它的那个对象(不考虑箭头函数)

1.2.1 关于 this 指向最后调用函数的那个对象,该怎么理解?

foo() 定义在 window 上,但是是 obj 对象调用了它(也就是给 obj 临时加了个 foo() 方法)

obj.foo() 相当于 window.obj.foo(),此时的 this 指向 最后调用函数 的对象,也就是 obj(window 不是最后调用函数的对象,obj 才是最后一个)

function foo () {
  console.log(this.a)
}

var obj = { a: 1, foo }

var a = 2

obj.foo() // 1

1.2.2 隐式绑定中的 this 丢失问题

隐式丢失:被 隐式绑定 的函数,在特定的情况下,会丢失绑定对象

容易发生 隐式丢失 的两种情况:

  • 使用另一个变量来给函数取别名
  • 将函数作为参数传递时,会被隐式赋值,回调函数丢失 this 绑定,比如 setTimeout

1.2.3 使用 另一个变量 给函数取别名,会发生隐式丢失

关键点:

  • 给函数取别名的时候,无论是使用一个变量(var foo2 = obj.foo),还是使用一个对象(var obj2 = { a: 3, foo2: obj.foo } ),都会导致 this 隐性丢失
  • 如果发生了 this 隐式丢失,则使用上一层 this 替代丢失的 this(可能是 window,可能是 obj)
function foo () {
  console.log(this.a)
};

var obj = { a: 1, foo };

var a = 2;

var foo2 = obj.foo;

var obj2 = { a: 3, foo2: obj.foo }

// -----------------------------------------------------------------

// 此处的 this 指向 obj,被 obj 调用进行了隐式绑定,因此打印 obj 中的 a
// this 指向调用者 obj
obj.foo(); // 1

// foo2 指向了 obj.foo 函数
// 由于使用 另一个变量 foo2 给函数取别名,发生了 this 丢失,导致 obj 丢了
// obj 丢了,就找上层 this;是 window 调用了 foo2,除了 obj 外,上层就是 window 了
// 综上:foo2() 发生了隐式丢失,调用者是 window,使得 foo() 中的 this 指向 window
foo2(); // 2

// obj2.foo2() 发生了隐式丢失,调用者是 obj2,使得 foo() 中的 this 指向 obj2
obj2.foo2(); // 3

1.2.4 将函数作为参数传递时,会发生隐式丢失

如果把 一个函数 当成参数 传递到 另一个函数 中时,会发生 隐式丢失 的问题

隐式丢失,与包裹着 函数参数 的 外层函数 的 this 指向无关(比如外层函数的 this 指向 obj2对象,但是隐式丢失后,this 和 obj2 不会有关系,只跟 window/undefined 有关系)

  • 在非严格模式下,隐式丢失后,会把函数的 this 绑定到 window 上;
  • 在严格模式下,会把函数的 this 绑定到 undefined 上;
  • 发生隐式丢失后,永远不会把函数的 this 绑定在 其他对象 上;
function foo () {
  console.log(this.a)
}

function doFoo (fn) {
  // 因为这里是 window 调用的它,所以 doFoo() 函数内的 this 本来就指向 window
  console.log(this) // Window{...}

  // 函数当参数传入,发生了隐式丢失(也就是 obj 没了),因此 foo 指向了 window
  fn() // 2
}

var obj = { a: 1, foo }

var a = 2

var obj2 = { a: 3, doFoo }

// 将 obj.foo 当成参数传递到 doFoo 函数中,发生了隐式丢失,因此 foo 指向了 window
doFoo(obj.foo)

// doFoo 此时被 obj2 调用,this 指向 obj2
// 但是由于 obj.foo 函数被当作参数传入,导致 this 隐式丢失
// 隐式丢失 与包裹着的函数 this 没有关系,也就是和 obj2 没有关系
// 所以指向 window,打印 window 中的 a,而不是 obj2 中的 a
obj2.doFoo(obj.foo) // 2

严格模式下:

"use strict"

function foo () {
  console.log(this.a)
}

function doFoo (fn) {
  // 被 obj2 调用,this 指向 obj2
  console.log(this) // { a:3, doFoo: f }

  // 函数当参数,发生 this 隐式丢失,隐式丢失和 函数参数 的 外层函数this 没有关系
  // 因此指向 window,也就是 window.a = 2
  // 又因为 严格模式 函数内的 this 始终为 undefind,因此获取不到 a 变量,报错
  fn() // Uncaught TypeError: Cannot read property 'a' of undefined
}

var obj = { a: 1, foo }

var a = 2

var obj2 = { a: 3, doFoo }

obj2.doFoo(obj.foo)

再来一个,常见的 setTimeout,将函数作为参数

var obj2 = {
  a: 2,

  foo1: function () {
    // 普通函数内的 this 指向调用它的对象
    console.log(this.a) // 2
  },

  foo2: function () {
    // setTimeout 回调中的 this(对象调用绑定的 this),会丢失,因此指向 window
    setTimeout(function () {
      console.log(this) // window
      console.log(this.a) // 3
    }, 0)
  }
}

// 全局变量
var a = 3

obj2.foo1()
// 虽然是被 obj2 调用的,但是由于定时器接收函数作为参数,导致 this 丢失
// 因此指向的 obj2 丢失,因此最终指向了 window
obj2.foo2()

1.3 显示绑定

通过 call()、apply()、bind() 方法,直接指定 this 的绑定对象,如 foo.call(obj))

1.3.1 call()、apply()、bind() 三者区别

  • 使用 call()、apply() 的函数,会直接执行
  • bind() 创建了一个新的函数,并返回,需要 手动调用 才会执行
  • call()、apply() 用法类似,区别在于 —— call() 接收若干个参数,而 apply() 接收一个数组
  • 如果 call()、apply()、bind() 接收到的第一个参数为空、null、undefined 的话,则不改变 this

function foo () {
  console.log(this.a)
}

var obj = { a: 1 }

var a = 2

// 默认绑定,因此打印 window 上的 a
foo() // 2

// call() 改变 this 指向,并立即执行
foo.call(obj) // 1

// apply() 改变 this 指向,并立即执行
foo.apply(obj) // 1

// bind() 改变 this 指向,需要手动执行
// 此处使用 bind 创建了一个新的函数,但是没有用变量接收这个新函数,更没有调用
// 此处 没有 调用函数,只是新生成了一个函数
foo.bind(obj) // 不执行

// 如果接收到的第一个参数为空、null、undefined 的话,则不改变 this
foo.call() // 2
foo.call(null) // 2
foo.call(undefined) // 2

1.3.2 使用 call() 解决 setTimeout 内 this 丢失的问题

注意:

  • 一定要给定时器内部的 参数函数 添加 call() 方法,而不是给 foo2 添加;
  • 如果改变了 foo2 的 this 指向,foo2 函数内部的定时器,接收参数函数,仍然会发生 this 丢失的问题(就是说 obj2.foo2.call(obj1) 这么写不行);
  • 改变定时器内的参数函数的 this 指向,就可以避免 this 丢失问题
  • 此处也可以使用 bind() 函数改变 this 指向,因为定时器到时间了一定会执行函数,即使 bind 函数需要手动触发;此处由于定时器的原因,就不需要我们手动去触发 bind 函数调用了
var obj1 = {
  a: 1
}

var obj2 = {
  a: 2,

  foo1: function () {
    console.log(this.a)
  },

  foo2: function () {
    setTimeout(function () {
      console.log(this) // { a: 1 }
      console.log(this.a) // 1
    }.call(obj1), 0)
  }
}

var a = 3

// 此处 this 指向 调用函数的对象,也就是 obj2
obj2.foo1() // 2

// 此处函数内有个定时器,定时器的参数是个函数,导致 this 丢失
// 给定时器内的参数函数添加 call 方法,将他绑定到 obj1 上
obj2.foo2()

1.3.3 ☆★☆★ 函数返回的函数被调用,除非被 call 修改 this 指向,否则都是 window 在调用 ☆★☆★

匿名函数的 this,永远指向 window

js 词法作用域和动态作用域 - 沉默的土豆 - 博客园词法作用域,也叫静态作用域,它的作用域是指在词法分析阶段就确定了,不会改变。动态作用域是在运行时根据程序的流程信息来动态确定的,而不是在写代码时进行静态确定的。 js是本身是词法作用域.举个例子: 由https://www.cnblogs.com/tudou1223/p/9864875.html

var obj1 = {
  a: 1
}

var obj2 = {
  a: 2,
  foo1: function () {
    console.log(this.a)
  },
  foo2: function () {
    function inner () {
      console.log(this) // window
      console.log(this.a) // 3
    }
    inner()
  }
}

var a = 3

obj2.foo2()

虽然是 obj2 调用了 foo2(),但是 foo2 内部的函数,不是 obj2 调用的,而是 window 调用的

再举个例子~(这个例子很重要)

function foo () {
  console.log(this.a)
  return function () {
    console.log(this.a)
  }
}
var obj = { a: 1 }
var a = 2

// foo.call(obj) 将 this 指向 obj,并自动执行函数,因此打印 1
// foo 函数返回了一个函数,这个匿名函数被调用,foo.call(obj)()【 相当于 foo()() 】
// 此时,新返回的匿名函数,不是被 obj 调用,而是被 window 调用,因此打印 2
foo.call(obj)()

换汤不换药系列~: 

var obj = {
  a: 'obj',
  foo: function () {
    console.log(this.a)
    return function () {
      console.log(this.a)
    }
  }
}
var a = 'window'
var obj2 = { a: 'obj2' }

// obj.foo() 打印 obj.a = obj,返回一个函数
// obj.foo 调用后,返回的函数被调用,且是 window 调用,所以打印 window.a = window
obj.foo()() // obj、window

// obj.foo 被改变 this 指向 obj2,并立即执行,打印 obj2.a = obj2
// obj.foo 被 obj2 调用后,返回的函数被调用,且是 window 调用,所以打印 window.a = window
obj.foo.call(obj2)() // obj2 window

// obj.foo 先被调用执行,this 指向调用函数的对象,所以打印 obj.a = obj
// obj.foo 执行后返回一个函数,这个函数被 call 绑定到了 obj2 上,并自动执行
// 所以打印 obj2.a = obj2
obj.foo().call(obj2)

1.3.4 call() 调用位置不同,导致结果不同

foo.call() 和 foo().call() 的区别:

  • foo.call() —— 是针对于 函数 做处理
  • foo().call() —— 是针对于 函数的返回值 做处理
function foo () {
  console.log(this.a)
}
var obj = { a: 1 }
var a = 2

// foo() 会正常打印出 window 下的 a,也就是 2
foo()

// foo.call(obj )由于显式绑定了 this,所以会打印出 obj 下的 a,也就是 1
foo.call(obj)

// foo().call(obj) 开始会执行 foo() 函数,打印出 2
// 实际上是对 foo() 函数的 返回值 执行.call(obj) 操作
// foo() 函数的返回值是 undefined,因此报错
foo().call(obj)

如果 函数返回的值 是一个函数呢?如下所示:

function foo () {
  console.log(this.a)
  return function () {
    console.log(this.a)
  }
}
var obj = { a: 1 }
var a = 2

// 虽然 foo() 函数返回了一个匿名函数,但是并没有调用它,调用应该写成 foo()()
foo() // 2

// 改变了 foo this 指向,直接调用 foo,但是并没有调用 foo 的返回函数
// 所以纸打印了一次 obj 内的 a
foo.call(obj) // 1

// 先让 window 调用 foo,此时 this 指向 window,打印 window.a = 2
// 再让 foo() 的返回函数,调用 call 方法,改变 this 指向 obj
// 并且 call 让函数直接调用,因此打印 obj.a = 1
foo().call(obj) // 2,1

1.3.5 bind() 调用位置不同,导致结果不同

function foo () {
  console.log(this.a)
  return function () {
    console.log(this.a)
  }
}
var obj = { a: 1 }
var a = 2

// window 调用,打印 window.a = 2
foo() // 2

// 让 foo 通过 bind 指向 obj
// 由于 bind 不会自动执行新生成的函数,因此 foo.bind(obj) 并没有执行 foo
foo.bind(obj) // 不打印

// 先执行了 foo,此时打印 window.a = 2
// 使用 bind 改变了 foo 返回函数的 this 指向
// 由于 bind 不会自动执行新生成的函数,因此 并没有让 foo 的返回函数执行,所以不打印
foo().bind(obj) // 2

1.3.6 在函数内使用 call 显式绑定一个对象,实现函数内部 this 指向 始终指向该对象

function foo1 () {
  console.log(this.a)
}

var a = 1

var obj = {
  a: 2
}

var foo2 = function () {
  // 函数内部显示绑定一个对象
  foo1.call(obj)
}

// 使用 window 调用 函数,而函数内部 绑定到了 obj 上,所以打印 obj.a = 2
foo2()

// 将 foo2 显式绑定到 window 上
// 而 foo2 内部仍然是 foo1 被指向 obj,所以打印 obj.a = 2
foo2.call(window)

1.3.7 冷门知识:forEach、map、filter 第二个参数可以改变 this 指向

这三个 JavaScript 方法,都接受两个参数:

  • 回调函数,内部又接受了当前遍历项、当前遍历索引等
  • 回调函数内的 this 指向对象,默认为 window

下面的三个方法内,都将 this 指向了 obj,所以打印的也全是 obj.a = obj

function foo (item) {
  console.log(item, this.a)
}

var obj = {
  a: 'obj'
}

var a = 'window'

var arr = [1, 2, 3]

// arr.forEach(foo, obj)
// arr.map(foo, obj)

arr.filter(function (i) {
  // return 之前所有选项都可以打印出来
  // this.a,this 被第二个参数指定为了 obj 对象
  console.log(i, this.a)
  return i > 2
}, obj)

1.3.8 ☆★☆★ 总结了一下 显式绑定 需要注意的点 ☆★☆★

  • this 永远指向最后调用它的那个对象
  • 匿名函数的 this 永远指向 window
  • 使用.call() 或者 .apply() 的函数,会直接执行
  • bind() 是创建一个新的函数,需要手动调用,才会执行
  • 如果call()、apply()、bind() 接收到的第一个参数是空、null、undefined,则会忽略这个参数
  • forEach()、map()、filter() 函数的第二个参数,能显式绑定 this

1.4 new 构造函数绑定

this 指向新生成的对象

1.4.1 基本用法

var name = 'aaa';

// 构造函数
function Person (name) {
  this.name = name
  this.foo1 = function () {
    console.log(this.name)
  }
  this.foo2 = function () {
    return function () {
      console.log(this.name)
    }
  }
}

var person1 = new Person('Test');

// 打印的是对象里的值
person1.foo1() // Test

// 执行匿名函数,是 window 调用的,因此打印 window 下的 name
person1.foo2()() // aaa

1.4.2 字面量形式创建对象 VS new 构造函数创建对象

使用 new 构造函数创建的对象,字面量形式创建的对象,基本没什么大的区别

如果对象中,存在 函数类型(不是箭头函数)的属性,那么解法都一样

举个栗子~: 

var name = 'window'
function Person (name) {
  this.name = name
  this.foo = function () {
    console.log(this.name)
    // 匿名函数最终都是被 window 执行的,除非被显示绑定给其他对象
    return function () {
      console.log(this.name)
    }
  }
}
var person2 = {
  name: 'person2',
  foo: function() {
    console.log(this.name)
    // 匿名函数最终都是被 window 执行的,除非被显示绑定给其他对象
    return function () {
      console.log(this.name)
    }
  }
}
  
var person1 = new Person('person1')

person1.foo()() // person1 window
person2.foo()() // person2 window

1.4.3 new 绑定结合显示绑定

主要容易考的感觉还是在 函数返回函数 的时候,多看几遍揣摩吧

个人理解:如果函数返回了(匿名)函数,被返回的(匿名)函数若要执行,则一般是 window 执行的,除非被显式绑定给其他对象

var name = 'window'

function Person (name) {
  this.name = name
  this.foo = function () {
    console.log(this.name)
    return function () {
      console.log(this.name)
    }
  }
}

var person1 = new Person('person1')

var person2 = new Person('person2')


// foo 先被 call 显示绑定给 person2
// call 会自动执行函数,因此打印了 person2.name = person2
// 返回的匿名函数,又被 window 直接执行(因为没被显示绑定给其他对象)
// 因此打印 window.name = window
person1.foo.call(person2)() // person2 window

// foo 先执行,直接打印 person1.name = person1
// foo 返回的函数被显示绑定给了 person2,并自动执行
// 因此虽然是匿名函数,但是有了 call 显示绑定,所以打印 person2.name = person2
person1.foo().call(person2) // person1 person2

1.5 箭头函数绑定

1.5.1 基本用法 —— 通过查找作用域链,确定箭头函数的 this

箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值;

如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this;否则,this 为 undefined

综上所述,箭头函数内的 this 始终由外层作用域决定;无法被 call 等方式修改;在定义时就已经确定 this 指向了,执行时无法改变 this 指向

举个栗子~

var obj = {
 name: 'obj',

 // 箭头函数
 foo1: () => {
   console.log(this.name) // window
 },

 // 普通函数
 foo2: function () {
   console.log(this.name) // obj
   // 箭头函数
   return () => {
     console.log(this.name) // obj
   }
 }
}

// 全局变量
var name = 'window'

// 对象 obj 不属于作用域(作用域只有全局作用域、函数作用域、块级作用域)
// foo1() 箭头函数的外层作用域是 window,所以会打印出 window
obj.foo1()

// 首先会执行 obj.foo2(),这不是箭头函数,所以它里面的 this 指向调用它的 obj 对象
// 返回的匿名函数是一个箭头函数,它的 this 由外层作用域决定,也就是 foo2 的函数作用域
obj.foo2()()

思路分析:

  • obj.foo1() —— 对象 obj 不属于作用域(作用域只有全局作用域、函数作用域、块级作用域),foo1() 的外层作用域是 window,所以会打印出 window
  • obj.foo2()() —— 首先会执行 obj.foo2(),这不是箭头函数,所以 obj.foo2() 内的 this 是调用它的 obj 对象,因此第二个打印为 obj;obj.foo2() 返回的匿名函数是一个箭头函数,它的 this 由外层作用域(也就是 obj.foo2() 的函数作用域)决定,因此第三个打印也是 obj 

理解上面的概念很重要呢,涉及到了 80% 的题目~O(∩_∩)O

1.5.2 字面量对象中,普通函数与箭头函数的区别: 只有一层函数的题目

不使用箭头函数,this 指向调用函数的对象,也就是 obj1

使用箭头函数,this 由外层作用域决定,也就是 window 全局作用域(对象不是作用域哈)

var name = 'window'

var obj1 = {
	name: 'obj1',
    // 不使用箭头函数
	foo: function () {
		console.log(this.name)
	}
}

var obj2 = {
	name: 'obj2',
    // 使用箭头函数
	foo: () => {
		console.log(this.name)
	}
}

// 不使用箭头函数,this 指向调用函数的对象,也就是 obj1
obj1.foo() // obj1

// 使用箭头函数,this 由外层作用域决定,也就是 window 全局作用域(对象不是作用域哈)
obj2.foo() // window

1.5.3 字面量对象中,普通函数与箭头函数的区别:函数嵌套(返回函数)的题目

普通函数,谁调用指向谁,指向 obj

返回的普通函数,谁调用指向谁,匿名函数没有 call 绑定,所以是 window 调用,指向 window

箭头函数,this 由外层作用域决定,定义的时候就决定了,不是执行的时候决定的

返回的箭头函数,this 由外层作用域决定,定义的时候就决定了,不是执行的时候决定的

举个栗子~

var name = 'window'

var obj1 = {
  name: 'obj1',
  // 普通函数,返回普通函数
  foo: function () {
    console.log(this.name)
    return function () {
      console.log(this.name)
    }
  }
}

var obj2 = {
  name: 'obj2',
  // 普通函数,返回箭头函数
  foo: function () {
    console.log(this.name)
    return () => {
      console.log(this.name)
    }
  }
}
var obj3 = {
  name: 'obj3',
  // 箭头函数,返回普通函数
  foo: () => {
    console.log(this.name)
    return function () {
      console.log(this.name)
    }
  }
}

var obj4 = {
  name: 'obj4',
  // 箭头函数,返回箭头函数
  foo: () => {
    console.log(this.name)
    return () => {
      console.log(this.name)
    }
  }
}

// 普通函数,谁调用指向谁,指向 obj
// 返回的普通函数,谁调用指向谁,匿名函数没有 call 绑定,所以是 window 调用,指向 window
obj1.foo()() // 'obj1' 'window'

// 普通函数,谁调用指向谁,指向 obj2
// 返回的箭头函数,this 由外层作用域决定
// 定义的时候就决定了,不是执行的时候决定的,也就是在 obj2.foo 函数作用域里,指向 obj2
obj2.foo()() // 'obj2' 'obj2'

// 箭头函数,this 由外层作用域决定
// 定义的时候就决定了,外层作用域是 window(没有对象作用域),指向 window
// 返回的普通函数,谁调用只想谁,匿名函数没有 call 绑定,所以是 window 调用,指向 window
obj3.foo()() // 'window' 'window'

// 箭头函数,this 由外层作用域决定
// 定义的时候就决定了,外层作用域是 window(没有对象作用域),指向 window
// 返回的箭头函数,this 由外层作用域决定
// 外层作用域是函数作用域,外层是箭头函数,外层箭头函数的 this 前面的分析得到了指向 window,因此此处仍然是 window
obj4.foo()() // 'window' 'window'

1.5.4 构造函数对象中,普通函数和箭头函数的区别:只有一层函数的题目

new 对象(普通/箭头函数都指向 新生成的对象):

  • 普通函数调用时,谁调用就指向谁,也就是指向 新生成的对象
  • 箭头函数调用时,this 由外层作用域决定,且指向函数定义时的 this 而非执行时;箭头函数的外层作用域是 Person 构造函数作用域;构造函数在被 new 的时候,this 会指向新生成的对象;所以此时 this 指向 新生成的对象

字面量对象(普通函数指向 新生成的对象,箭头函数指向 window):

  • 普通函数调用时,谁调用就指向谁,也就是指向 新生成的对象
  • 箭头函数调用时,this 由外层作用域决定,且指向函数定义时的 this 而非执行时;普通函数的外层作用域是 window(不存在对象作用域),因此指向 window
var name = 'window'

function Person (name) {
  this.name = name
  // 普通函数
  this.foo1 = function () {
    console.log(this.name)
  }
  // 箭头函数
  this.foo2 = () => {
    console.log(this.name)
  }
}

var person2 = {
  name: 'person2',
  // 箭头函数
  foo2: () => {
    console.log(this.name)
  }
}

var person1 = new Person('person1')

// new对象
// 普通函数调用时,谁调用就指向谁,因此指向 person1
person1.foo1() // person1

// new对象
// 箭头函数调用时,this 由外层作用域决定,且指向函数定义时的 this 而非执行时
// 箭头函数的外层作用域是 Person 构造函数作用域
// 构造函数在被 new 的时候,this 会指向新生成的对象
// 所以此时 this 指向 新生成的对象 person1
person1.foo2() // person1

// 字面量对象
// 箭头函数调用时,this 由外层作用域决定,且指向函数定义时的 this 而非执行时
// 箭头函数的外层作用域是 window(不存在对象作用域)
// 所以此时 this 指向 window
person2.foo2() // window

1.5.5 构造函数对象中,普通函数和箭头函数的区别:函数嵌套(返回函数)的题目

普通函数,谁调用指向谁

返回的普通函数,谁调用指向谁,window 调用,指向 window,除非 call 显式绑定给其他对象

箭头函数,由外层作用域决定 this,外层作用域是构造函数,构造函数在 new 时指向新生成的对象

返回的箭头函数,由外层作用域决定 this,外层作用域可能是 普通函数作用域、也可能是箭头函数作用域

var name = 'window'

function Person (name) {
  this.name = name

  this.foo1 = function () {
    console.log(this.name)
    return function () {
      console.log(this.name)
    }
  }

  this.foo2 = function () {
    console.log(this.name)
    return () => {
      console.log(this.name)
    }
  }

  this.foo3 = () => {
    console.log(this.name)
    return function () {
      console.log(this.name)
    }
  }

  this.foo4 = () => {
    console.log(this.name)
    return () => {
      console.log(this.name)
    }
  }
}

var person1 = new Person('person1')

// 普通函数,谁调用指向谁,person1
// 返回的普通函数,谁调用指向谁,window 调用,指向 window
person1.foo1()() // 'person1' 'window'

// 普通函数,谁调用指向谁,person1
// 返回的箭头函数,由外层作用域决定 this
// 外层作用域是 普通函数作用域,普通函数在此处指向了 person1
person1.foo2()() // 'person1' 'person1'

// 箭头函数,由外层作用域决定 this
// 外层作用域是构造函数,构造函数在 new 时指向新生成的对象,person1
// 返回的普通函数,谁调用就指向谁,window 调用指向 window
person1.foo3()() // 'person1' 'window'

// 箭头函数,由外层作用域决定 this
// 外层作用域是构造函数,构造函数在 new 时指向新生成的对象,person1
// 返回的箭头函数,由外层作用域决定 this
// 外层作用域是 箭头函数作用域,也就是 person1
person1.foo4()() // 'person1' 'person1'

1.5.6 箭头函数 不能被 显式绑定函数(call、apply、bind)修改 this 指向

箭头函数的 this 指向,只有外层作用域决定,call() 函数无法修改 箭头函数的 this 指向

var name = 'window'

var obj1 = {
  name: 'obj1',

  foo1: function () {
    console.log(this.name)
    return () => {
      console.log(this.name)
    }
  },

  foo2: () => {
    console.log(this.name)
    return function () {
      console.log(this.name)
    }
  }
}

var obj2 = {
  name: 'obj2'
}

// 普通函数被显示绑定给 obj2 了,所以打印 obj2
// 返回的箭头函数,this 由外层作用域决定,也就是 foo1 函数作用域,也就是 obj2
obj1.foo1.call(obj2)() // 'obj2' 'obj2'

// 普通函数先执行,谁调用指向谁,所以 obj1
// 返回的箭头函数不被 call 影响,始终由外层作用域决定,也就是函数作用域,也就是 obj1
obj1.foo1().call(obj2) // 'obj1' 'obj1'

// 箭头函数不被 call 影响,始终由外层作用域决定,也就是 window 作用域
// 返回的普通函数,没被 call 改变 this 指向,因此谁调用指向谁,windwow 调用指向 window
obj1.foo2.call(obj2)() // 'window' 'window'

// 箭头函数直接执行,由外层作用域决定,也就是 window 作用域
// 返回的普通函数,被 call 改变了 this 指向,因此指向 obj2
obj1.foo2().call(obj2) // 'window' 'obj2'

1.5.7 ☆★☆★ 箭头函数必坑指南 / 不能使用箭头函数的场景 ☆★☆★

必坑指南:

  • 箭头函数的 this 是由外层作用域来决定的,且指向函数定义时的 this 而非执行时
  • 箭头函数的 this 无法通过 bind()、call()、apply() 修改,但是可以通过改变作用域中 this 的指向来间接修改(也就是想办法改变箭头函数外面作用域的 this 指向)
  • 字面量创建的对象,作用域是 window,如果里面有箭头函数属性的话,this 指向的是 window
  • 构造函数创建的对象,作用域是构造函数,且这个构造函数的 this 指向新建的对象,因此构造函数内的箭头函数属性,this 指向新建对象

不能使用箭头函数的场景:

  • 使用箭头函数定义对象的方法;会导致 this 指向 window,无法获取对象内部属性
  • 定义原型方法;会导致 this 指向 window,无法获取对象内部属性
  • 构造函数使用箭头函数;会直接报错 Uncaught TypeError: Foo is not a constructor
  • 作为事件的回调函数;会导致 this 不再指向绑定事件的元素,而是指向 window

举个栗子~

/**
 * 使用箭头函数定义对象的方法
 */
let obj = {
    value: 'LinDaiDai',
    // 箭头函数作用域由外层作用域决定,这样会打印 window 下的 value
    getValue: () => console.log(this.value)
}

obj.getValue() // undefined


/**
 * 定义原型方法
 */
function Foo (value) {
    this.value = value
}

// 箭头函数作用域由外层作用域决定,这样会打印 window 下的 value
Foo.prototype.getValue = () => console.log(this.value)

const foo1 = new Foo(1)
foo1.getValue() // undefined


/**
 * 构造函数使用箭头函数
 */
const Foo = (value) => {
    this.value = value;
}
const foo1 = new Foo(1)
// 事实上直接就报错了 Uncaught TypeError: Foo is not a constructor
console.log(foo1);


/**
 * 作为事件的回调函数
 */
const button = document.getElementById('myButton');
button.addEventListener('click', () => {
    // 我们想改的显然是 button 中的文字
    // 但此处 this 由于箭头函数,不再指向 button,而指向 window
    console.log(this === window); // => true
    this.innerHTML = 'Clicked button';
});

2. 参考文章(非常有用👍)

下面的文章确实十分……酸爽

 

每道题都做了,边看边加上自己的理解整理了这篇文章

 

受益良多,很感谢作者的干货😄🌼

【建议👍】再来40道this面试题酸爽继续(1.2w字用手整理) - 掘金你盼世界,我盼望你无bug。Hello 大家好!我是霖呆呆! 😂😂😂 你们一定觉得我是在吹牛...哼...我这是不愿意截屏发出来(否则我还不露馅了)。 哈哈 😄,收... 其实不管你是花了20分钟,30分钟,亦或者是两个小时来阅读它,你愿意把这部分时间完完全全的交给我,…https://juejin.cn/post/6844904083707396109#heading-2

  • 12
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lyrelion

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值