【建议👍】再来40道this面试题酸爽继续(1.2w字用手整理)

前言

你盼世界,我盼望你无bug。Hello 你们好!我是霖呆呆!javascript

首先很是感谢你们对我上篇文章《【建议星星】要就来45道Promise面试题一次爽到底(1.1w字用心整理)》的支持和喜欢!html

甚至有小姐姐在个人公众号里扬言说:前端

"我太喜欢你"java

"的文章了"面试

😂😂😂 大家必定以为我是在吹牛...哼...我这是不肯意截屏发出来(不然我还不露馅了)。数组

对于这样的读者,霖呆呆我只想说:浏览器

"小妹妹不要在网上晒本身,要多学习提升本身的知识才是正事,让哥哥来考考你一个常识问题,你的微信多少?"缓存

哈哈 😄,收...bash

其实无论你是花了20分钟,30分钟,亦或者是两个小时来阅读它,你愿意把这部分时间完彻底全的交给我,这让我很荣幸。因此我也但愿能写出更多高质量的文章来和你们一块儿学习,进步。微信

这篇文章是我整理和自编的一些this面试题,感谢掘友吴佳宸给了我编写this的灵感 😂。

其实在写以前,我也没想到关于this面试题能有这么的考点,可是若是你的思想开放一点,大胆一点,结合例如let、闭包、forEach、map等ES6方法来出题的话,会发现一些好玩有趣的题。

(固然若是你是一位害羞保守的小伙子<姑娘>的话可能会对霖呆呆骂娘...)

咳咳,开玩笑的哈,这篇文章对你理解this仍是挺有帮助的,因此,请放心"食用"吧。😁

让咱们来看看,经过阅读本篇文章你能够学习到:

  • this的默认绑定
  • 隐式绑定
  • 隐式绑定的隐式丢失问题
  • 显式绑定
  • 显式绑定的其它用法
  • new绑定
  • 箭头函数绑定
  • 综合题
  • 几道手写题

前期准备

在正式阅读以前,你须要知道this的5种绑定方式:

  • 默认绑定(非严格模式下this指向全局对象, 严格模式下this会绑定到undefined)
  • 隐式绑定(当函数引用有上下文对象时, 如 obj.foo()的调用方式, foo内的this指向obj)
  • 显示绑定(经过call()或者apply()方法直接指定this的绑定对象, 如foo.call(obj))
  • new绑定
  • 箭头函数绑定(this的指向由外层做用域决定的)

1. 默认绑定

先介绍一种最简单的绑定方式吧:默认绑定

也就是咱们常说的:在非严格模式下this指向的是全局对象window,而在严格模式下会绑定到undefined

1.1 题目一

老规矩,来看个最基本的案例:

var a = 10;
function foo () {
  console.log(this.a)
}
foo();
复制代码

咱们知道在使用var建立变量的时候(不在函数里),会把建立的变量绑定到window上,因此此时awindow下的属性。

而函数foo也是window下的属性。

所以上面的代码其实就至关因而这样:

window.a = 10;
function foo() {
  console.log(this.a)
}
window.foo();
复制代码

在这里,调用foo()函数的是window对象,且又是在非严格模式下,因此foo()this的指向是window对象,所以this.a会输出10

答案:

10
复制代码

1.2 题目二

改造下题目一,看看在严格模式下。

(想要开启严格模式,只要在须要开启的地方写上"use strict")

"use strict";
var a = 10;
function foo () {
  console.log('this1', this)
  console.log(window.a)
  console.log(this.a)
}
console.log(window.foo)
console.log('this2', this)
foo();
复制代码

须要注意的点:

  • 开启了严格模式,只是说使得函数内的this指向undefined,它并不会改变全局中this的指向。所以this1中打印的是undefined,而this2仍是window对象。

  • 另外,它也不会阻止a被绑定到window对象上。

因此最后的执行结果:

f foo() {...}
'this2' Window{...}
'this1' undefined
10
Uncaught TypeError: Cannot read property 'a' of undefined
复制代码

1.3 题目三

let a = 10
const b = 20

function foo () {
  console.log(this.a)
  console.log(this.b)
}
foo();
console.log(window.a)
复制代码

若是把var改为了let 或者 const,变量是不会被绑定到window上的,因此此时会打印出三个undefined

答案:

undefined
undefined
undefined
复制代码

1.4 题目四

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

foo()
复制代码

这里咱们很容易就知道,foo()函数内的this指向的是window,由于是window调用的foo

可是打印出的this.a呢?注意,是this.a,不是a,所以是window下的a

而且因为函数做用域的缘由咱们知道window下的a仍是1

所以答案为:

Window{...}
1
复制代码

1.5 题目五

把题目1.4改造一下 😁。

var a = 1
function foo () {
  var a = 2
  function inner () { 
    console.log(this.a)
  }
  inner()
}

foo()
复制代码

其实这里和1.4很像,不过一看到函数内的函数,就很容易让人联想到闭包 😂,而后... 而后就脱口而出,答案是2啊,这还不简单。

小伙伴们,审题可得仔细啊,这里问你的是this.a,而在inner中,this指向的仍是window

答案:

1
复制代码

(我知道有的小伙伴不满这种题目,这吖的不就是和人玩文字游戏吗?有什么技术含量,可是现实就是这样,不少面试官会喜欢问这种细节题来考察你细心不细心。在没能力改变这种状况的前提下,你只能试着接受它...)

2. 隐式绑定

OK👌,介绍完了默认绑定以后,让咱们来看看第二种隐式绑定

其实大佬 sunshine小小倩 教了咱们一个简单的规则,this 永远指向最后调用它的那个对象

谁最后调用的函数,函数内的this指向的就是谁(不考虑箭头函数)。

(了解上下文对象的小伙伴应该都知道,这与上下文对象以及做用域有关,想要了解的小伙伴能够看个人这篇文章《JavaScript进阶-执行上下文栈和变量对象(一周一更)》

下面你能够用这个规则来作题啦 😁。

2.1 题目一

function foo () {
  console.log(this.a)
}
var obj = { a: 1, foo }
var a = 2
obj.foo()
复制代码

(var obj = { foo }就至关因而var obj = { foo: foo },这个你们应该都知道吧)

在这道题中,函数foo()虽然是定义在window下,可是我在obj对象中引用了它,并将它从新赋值到obj.foo上。

且调用它的是obj对象,所以打印出来的this.a应该是obj中的a

换个角度想,上面👆这段代码是否是就至关因而这样:

var obj = {
  a: 1,
  foo: function () {
    console.log(this.a)
  }
}
var a = 2
obj.foo()
复制代码

在这里foo函数内的this指向的就是obj,和题目效果同样。

答案都是:

1
复制代码

有小伙伴就会有有疑问了,obj.foo()不就至关因而window.obj.foo()吗?

foo()内的this究竟是算window呢,仍是obj呢?

请注意我前面说的,是最后调用函数的对象,显然,obj要比window更后面一点。

3. 隐式绑定的隐式丢失问题

隐式绑定的基本概念你们应该都清楚了,不过其实有一个关于隐式绑定的经常使用考点,那就是隐式丢失问题

隐式丢失其实就是被隐式绑定的函数在特定的状况下会丢失绑定对象。

有两种状况容易发生隐式丢失问题:

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

咱们一种一种来看哈。

3.1 题目一

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

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

obj.foo();
foo2();
复制代码

执行这段代码会打印出啥呢 🤔️?

在这里咱们已经知道了,obj.foo()this的指向是为obj的(能够看第二部分隐式绑定),因此obj.foo()执行的时候,打印出来的是obj对象中的a,也就是1

可是foo2它不也是obj.foo吗?我只不过是用了一个变量foo2来盛放了它而已。因此你是否是认为它打印的也是1呢?

额 😅,其实这里不是的,它打印出的是window下的a

答案:

1
2
复制代码

这是由于虽然foo2指向的是obj.foo函数,不过调用它的倒是window对象,因此它里面this的指向是为window

其实也就至关因而window.foo2(),若是你不相信的话,能够看下面一题👇。

3.2 题目二

让咱们在一个新的变量obj2中也定义一个foo2看看:

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 }

obj.foo();
foo2();
obj2.foo2();
复制代码

这三种不一样的foo()打印出来的分别是什么呢?

答案:

1
2
3
复制代码
  • obj.foo()中的this指向调用者obj
  • foo2()发生了隐式丢失,调用者是window,使得foo()中的this指向window
  • foo3()发生了隐式丢失,调用者是obj2,使得foo()中的this指向obj2

3.3 题目三

再就是若是你把一个函数当成参数传递时,也会被隐式赋值,发生意想不到的问题。

来看看这道题目:

function foo () {
  console.log(this.a)
}
function doFoo (fn) {
  console.log(this)
  fn()
}
var obj = { a: 1, foo }
var a = 2
doFoo(obj.foo)
复制代码

这里咱们将obj.foo当成参数传递到doFoo函数中,在传递的过程当中,obj.foo()函数内的this发生了改变,指向了window

所以结果为:

Window{...}
2
复制代码

注意,我这里说的是obj.foo()函数,而不是说doFoo()doFoo()函数内的this原本就是指向window的,由于这里是window调用的它。

可是你不要觉得是doFoo()函数内的this影响了obj.foo(),不信你看下一题。

3.4 题目四

如今咱们不用window调用doFoo,而是放在对象obj2里,用obj2调用:

function foo () {
  console.log(this.a)
}
function doFoo (fn) {
  console.log(this)
  fn()
}
var obj = { a: 1, foo }
var a = 2
var obj2 = { a: 3, doFoo }

obj2.doFoo(obj.foo)
复制代码

如今调用obj2.doFoo()函数,里面的this指向的应该是obj2,由于是obj2调用的它。

可是obj.foo()打印出来的a依然是2,也就是window下的。

执行结果为:

{ a:3, doFoo: f }
2
复制代码

因此说,若是你把一个函数当成参数传递到另外一个函数的时候,也会发生隐式丢失的问题,且与包裹着它的函数的this指向无关。在非严格模式下,会把该函数的this绑定到window上,严格模式下绑定到undefined。

同样的代码,试试严格模式下:

"use strict"
function foo () {
  console.log(this.a)
}
function doFoo (fn) {
  console.log(this)
  fn()
}
var obj = { a: 1, foo }
var a = 2
var obj2 = { a: 3, doFoo }

obj2.doFoo(obj.foo)
复制代码

执行结果:

{ a:3, doFoo: f }
Uncaught TypeError: Cannot read property 'a' of undefined
复制代码

4. 显式绑定

功能如其名,就是强行使用某些方法,改变函数内this的指向。

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

这里有几个知识点须要注意:

  • 使用.call()或者.apply()的函数是会直接执行的
  • bind()是建立一个新的函数,须要手动调用才会执行
  • .call().apply()用法基本相似,不过call接收若干个参数,而apply接收的是一个数组

(其实这应该是常常听到的知识点了吧 😅)

来看看题目一。

4.1 题目一

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

foo()
foo.call(obj)
foo.apply(obj)
foo.bind(obj)
复制代码

第一个foo() 都很好理解,这不就是默认绑定吗?😁

而第二个和第三个foo都使用了callapply来改变this的指向,而且是当即执行的。

第四个foo,仅仅是使用bind建立了一个新的函数,且这个新函数也没用别的变量接收并调用,所以并不会执行。

答案:

2
1
1
复制代码

这里想要提一嘴,若是call、apply、bind接收到的第一个参数是空或者null、undefined的话,则会忽略这个参数。

例如🌰:

function foo () {
  console.log(this.a)
}
var a = 2
foo.call()
foo.call(null)
foo.call(undefined)
复制代码

输出的是:

2
2
2
复制代码

4.2 题目二

了解了显式绑定的基本使用以后,让咱们来看看它的妙用。

首先,是这个例子🌰:

var obj1 = {
  a: 1
}
var obj2 = {
  a: 2,
  foo1: function () {
    console.log(this.a)
  },
  foo2: function () {
    setTimeout(function () {
      console.log(this)
      console.log(this.a)
    }, 0)
  }
}
var a = 3

obj2.foo1()
obj2.foo2()
复制代码

对于obj2.foo1(),咱们很清楚,它就是打印出2

可是对于obj2.foo2呢?在这个函数里,设置了一个定时器,并要求咱们打印出thisthis.a

想一想我前面说过的话,谁调用的函数,函数内的this指向的就是谁。

而对于setTimeout中的函数,这里存在隐式绑定的隐式丢失,也就是当咱们将函数做为参数传递时会被隐式赋值,回调函数丢失this绑定,所以这时候setTimeout中的函数内的this是指向window的。

(以前呆呆一直认为的是定时器里的函数和定时器是有关系的,因此有一些错误的理解,感谢掘友朝游夕宴l.jx的指出)

因此最终的结果是:

2
Window{...}
3
复制代码

4.3 题目三

面对上面👆这种状况咱们就可使用call、apply 或者bind来改变函数中this的指向,使它绑定到obj1上,从而打印出1

var obj1 = {
  a: 1
}
var obj2 = {
  a: 2,
  foo1: function () {
    console.log(this.a)
  },
  foo2: function () {
    setTimeout(function () {
      console.log(this)
      console.log(this.a)
    }.call(obj1), 0)
  }
}
var a = 3
obj2.foo1()
obj2.foo2()
复制代码

如今的执行结果就是:

2
{ a: 1 }
1
复制代码

可是看看我这里的写法,我是将.call运用到setTimeout里的回调函数上,并非运用到obj2.foo2()上。

因此有小伙伴就会问了,我下面的这种写法不能够吗?

obj2.foo2.call(obj1)
复制代码

注意⚠️:若是是这种写法的话,我改变的就是foo2函数内的this的指向了,可是咱们知道,foo2函数内this的指向和setTimeout里函数的this是没有关系的,由于调用定时器的始终是window

而且这里使用.bind()也是能够的,由于定时器里的函数在时间到了以后本就是会自动执行的。

4.4 题目四

OK👌,咱们不用定时器,把它干掉,换成一个函数:

var obj1 = {
  a: 1
}
var obj2 = {
  a: 2,
  foo1: function () {
    console.log(this.a)
  },
  foo2: function () {
    function inner () {
      console.log(this)
      console.log(this.a)
    }
    inner()
  }
}
var a = 3
obj2.foo1()
obj2.foo2()
复制代码

其实这里有点像题目1.5有木有,都是函数内包裹着函数。

调用inner函数的依然是window,因此结果为:

2
Window{...}
3
复制代码

若是给inner()函数显式绑定的话:

inner.call(obj1)
复制代码

结果为

2
{ a: 1 }
1
复制代码

4.5 题目五

其实在实际面试中,面试官喜欢以这样的方式考你:

看看这道题,会输出什么呢 🤔️?

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

foo()
foo.call(obj)
foo().call(obj)
复制代码

也就是使用.call()方法位置的不一样。

结果:

2
1
2
Uncaught TypeError: Cannot read property 'call' of undefined
复制代码
  • foo()会正常打印出window下的a,也就是2
  • foo.call(obj)因为显式绑定了this,因此会打印出obj下的a,也就是1
  • foo().call(obj)开始会执行foo()函数,打印出2,可是会对foo()函数的返回值执行.call(obj)操做,但是咱们能够看到foo()函数的返回值是undefined,所以就会报错了。

因此咱们能够看到foo.call()foo().call()的区别了,一个是针对于函数,一个是针对于函数的返回值。

下面我会用更多的例题来帮助你们了解这类题目的解法。

4.6 题目六

OK👌,既然刚刚4.5是由于函数没有返回值才报的错,那我如今给它加上返回值看看:

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

foo()
foo.call(obj)
foo().call(obj)
复制代码

你能想到如今会输出什么吗?

答案是会输出3个数,仍是4个数,亦或者6个数呢?

😁 嘻嘻,不逗你了,结果居然是:

2
1
2
1
复制代码
  • 第一个数字2天然是foo()输出的,虽然foo()函数也返回了一个匿名函数,可是并无调用它呀,只有写成foo()(),这样才算是调用匿名函数。
  • 第二个数字1foo.call(obj)输出的,因为.call()是紧跟着foo的,因此改变的是foo()this的指向,而且.call()是会使函数当即执行的,所以打印出1,同理,它也没有调用返回的函数。
  • 第三个数字2foo().call(obj)先执行foo()时打印出来的,此时foo()this仍是指向window
  • 在执行完foo()以后,会返回一个匿名函数,而且后面使用了.call(obj)来改变这个匿名函数的this指向并调用了它,因此输出了1

咦~好像从这道题开始,变得愈来愈有意思了哈 😁

4.7 题目七

想一想咱们把call换成bind会怎么样呢?

先来回忆一下它们的区别:call是会直接执行函数的,bind是返回一个新函数,但不会执行。

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

foo()
foo.bind(obj)
foo().bind(obj)
复制代码

结果天然就是:

2
2
复制代码
  • foo()会执行没错,打印出了2
  • 可是foo.bind(obj)却不会执行,它返回的是一个新函数。
  • foo().bind(obj)只会执行前面的foo()函数,打印出2.bind(obj)只是将foo()返回的匿名函数显式绑定this而已,并无调用。

4.8 题目八

说实话,作上面这类题目,会让我有一种疑惑。

这种函数内返回的函数,它的this会和它外层的函数有关吗?

也就是内层函数它的this究竟是谁呢?

仍是那句话,谁最后调用的它,this就指向谁。

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

foo.call(obj)()
复制代码

就像是这道题,foo()函数内的this虽然指定了是为obj,可是调用最后调用匿名函数的倒是window

因此结果为:

1
2
复制代码

(请注意这个例子🌰,后面它会与箭头函数作对比)

4.9 题目九

一直都在作函数返回函数的题目,让咱们来看看把它们加到对象里,会有哪些有趣的题目吧。

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

obj.foo()()
obj.foo.call(obj2)()
obj.foo().call(obj2)
复制代码

如今,没和你玩文字游戏了,每一个foo返回的函数我都调用了,可是你能知道每次调用,打印出的都是什么吗?

  • obj.foo()天然是打印出foo: objinner: window,这个没什么疑惑的。
  • obj.foo.(obj2)()其实也没啥可疑惑的了,打印出foo: obj2inner: window(相似4.8)。
  • 那么obj.foo().call(obj2)就更没啥可疑惑的了,打印出foo: objinner: obj2

完了,都没啥可疑惑的了。就这点东西,你都掌握了...

OK,小fo子,请记住你如今的自信,等会作综合题的时候但愿你还能如此清醒 🙏。

4.10 题目十

一直作这种题目是否是没意思,让咱们加几个参数来玩玩。

[阴笑]~

var obj = {
  a: 1,
  foo: function (b) {
    b = b || this.a
    return function (c) {
      console.log(this.a + b + c)
    }
  }
}
var a = 2
var obj2 = { a: 3 }

obj.foo(a).call(obj2, 1)
obj.foo.call(obj2)(1)
复制代码

执行结果:

6
6
复制代码
  • 开始调用obj.foo(a)2传入foo函数并赋值给型参b,而且因为闭包的缘由,使得匿名函数内能访问到b,以后调用匿名函数的时候,用call()改变了this的指向,使得匿名函数内this.a3,并传入最后一个参数1,因此第一行输出的应该是3 + 2 + 1,也就是6
  • 而第二行,obj.foo.call(obj2)这里是将foo函数内的this指向了obj2,同时并无传递任何参数,因此b开始是undefined的,可是又由于有一句b = b || this.a,使得b变为了3;同时最后一段代码(1),是在调用匿名函数,且和这个匿名函数内的this应该是指向window的,所以输出也为3+2+1,为6

这道题实际上是我本身编的 😂,编完以后还以为挺好玩的,即考到了闭包,又考到了call方法,还考到了算数...

哈哈哈哈~

(小伙子,年纪轻轻就能出如此心机的题目,让你当面试官还得了?)

5. 显式绑定的其它用法

除了上面👆那几道题的用法以外,咱们还能够有一些其它的用法。

例如,咱们能够在一个函数内使用call来显式绑定某个对象,这样不管怎样调用它,其内部的this老是指向这个对象。(可见题目5.1)

5.1 题目一

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

var foo2 = function () {
  foo1.call(obj)
}

foo2()
foo2.call(window)
复制代码

这里foo2函数内部的函数foo1咱们使用call来显式绑定obj,就算后面再用call来绑定window也没有用了。

结果为:

2
2
复制代码

5.2 题目二

function foo1 (b) {
  console.log(`${this.a} + ${b}`)
  return this.a + b
}
var a = 1
var obj = {
  a: 2
}

var foo2 = function () {
  return foo1.call(obj, ...arguments)
}

var num = foo2(3)
console.log(num)
复制代码

答案:

'2 + 3'
5
复制代码

5.3 题目三

接下我想要介绍一个比较冷门的知识。

相信你们对forEach、map、filter都不陌生吧,它们是JS内置的一些函数,可是你知道它们的第二个参数也是能绑定this的吗? 😁

来看看下面👇的题目:

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) {
  console.log(i, this.a)
  return i > 2
}, obj)
复制代码

这里的答案为:

1 "obj"
2 "obj"
3 "obj"
复制代码

若是咱们没有传递第二个参数obj的话,this.a打印出来的确定就是window下的a了,可是传入了以后将obj显示绑定到第一个参数函数上。

(关于arr.filter为何也会打印出1, 2, 3,那是由于虽然咱们使用了return i > 2,不过在执行阶段filter仍是把每一项都打印出来)

总结

总结一下这部分的知识点好了:

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

6. new 绑定

好滴,让咱们来看看另外一种this的绑定形式,也就是new绑定。

使用new来调用一个函数,会构造一个新对象并把这个新对象绑定到调用函数中的this

例如第一题。

6.1 题目一

使用new来调用Person,构造了一个新对象person1并把它(person1)绑定到Person调用中的this

function Person (name) {
  this.name = name
}
var name = 'window'
var person1 = new Person('LinDaiDai')
console.log(person1.name)
复制代码

答案:

'LinDaiDai'
复制代码

6.2 题目二

构造函数中不只能够加属性,也能够加方法:

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('person1')
person1.foo1()
person1.foo2()()
复制代码

这道题的写法不得不让我想到题目4.9

var obj = {
  a: 'obj',
  foo: function () {
    console.log('foo:', this.a)
    return function () {
      console.log('inner:', this.a)
    }
  }
}
复制代码

好像都是函数包裹着函数,没错,其实它们的解法都差很少。😁

因此这道题的结果为:

'person1'
''
复制代码
  • 第一个this.name打印的确定是person1对象中的name,也就是构造person1对象时传递进去的person1字符串。
  • 第二个this.name打印的应该就是window下的name了,可是这里window对象中并不存在name属性,因此打印出的是空。

在作这道题时,我发现了一个有意思的现象。

我将这道题用浏览器打开,控制台输出的居然是:

'person1'
'window'
复制代码

咦 🤔️ ?window下明明没有name这个属性啊,它怎么会打印出window,别和我说我电脑的浏览器这么吊...给window对象自动加了一个属性值为windowname属性...

思考了一会,我想起来了,以前我在代码里确实定义了一个变量namevar name = 'window'

但是这段代码我已经删掉了啊,而且强制刷新了浏览器,难道它仍是存在于window中吗?

为了验证个人想法,我又从新定义了一个变量var name = 'LinDaiDai',而后打开浏览器刷新,以后再删除这段代码再刷新。

果真,window.name打印出来仍是LinDaiDai。它会记住这个name属性不被回收,直到你关闭此页签。

可是若是把name属性名换成别的(好比dd),它就不会有这种状况。

有兴趣的小伙伴能够去尝试一下哈,这里我就不深究了...

6.3 题目三

使用new函数建立的对象和字面量形式建立出来的对象好像没什么大的区别,若是对象中有属性是函数类型的话,而且不是箭头函数,那么解法都同样。在后面说到箭头函数的时候就有区别了,不过咱们一步一步来。

先看看下面👇这道题:

var name = 'window'
function Person (name) {
  this.name = name
  this.foo = function () {
    console.log(this.name)
    return function () {
      console.log(this.name)
    }
  }
}
var person2 = {
  name: 'person2',
  foo: function() {
    console.log(this.name)
    return function () {
      console.log(this.name)
    }
  }
}
  
var person1 = new Person('person1')
person1.foo()()
person2.foo()()
复制代码

在这道题中,person1.fooperson2就没有什么区别。

打印出来的结果为:

'person1'
'window'
'person2'
'window'
复制代码

6.4 题目四

new绑定结合显示绑定,例如call函数的话,解起来其实也不难。

来看看下面👇的题目。

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')

person1.foo.call(person2)()
person1.foo().call(person2)
复制代码

在作这类题的时候,你就把Person生成的person1脑补成:

var person1 = {
	name: 'person1',
	foo: function () {
		console.log(this.name)
		return function () {
			console.log(this.name)
		}
	}
}
复制代码

因此答案很容易就出来了:

'person2'
'window'
'person1'
'person2'
复制代码

解题分析:

  • person1.foo.call(person2)()foo()函数内的this指向了person2,因此打印出person2,而内部返回的匿名函数是由window调用的,因此打印出window。(相似题目4.9)
  • person1.foo().call(person2)是将匿名函数的this显式绑定到了person2上,因此打印出来的会是person2

7. 箭头函数绑定

终于到了期待已久的箭头函数绑定 😁。

在上面👆,咱们有学到一个诀窍:this 永远指向最后调用它的那个对象

可是对于箭头函数就不是这样咯,它里面的this是由外层做用域来决定的,且指向函数定义时的this而非执行时

它里面的this是由外层做用域来决定的啥意思呢?来看看这句话:

箭头函数中没有 this 绑定,必须经过查找做用域链来决定其值,若是箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,不然,this 为 undefined。

且指向函数定义时的this而非执行时这句话能够等会看题目7.4

读了这句话相信你已经能解决80%的题目了,让咱们看完了第一题7.1以后,再来看看箭头函数能够分为哪几类题目来讲吧,这是目录:

  • 字面量对象中普通函数与箭头函数的区别: 只有一层函数的题目
  • 字面量对象中普通函数与箭头函数的区别:函数嵌套的题目
  • 构造函数对象中普通函数和箭头函数的区别:只有一层函数的题目
  • 构造函数对象中普通函数和箭头函数的区别:函数嵌套的题目
  • 箭头函数结合.call的题目

7.1 题目一

var obj = {
  name: 'obj',
  foo1: () => {
    console.log(this.name)
  },
  foo2: function () {
    console.log(this.name)
    return () => {
      console.log(this.name)
    }
  }
}
var name = 'window'
obj.foo1()
obj.foo2()()
复制代码

这道题就很是有表明性,它明确了箭头函数内的this是由外层做用域决定的。

  • 对于obj.foo1()函数的调用,它的外层做用域是window,对象obj固然不属于做用域了(咱们知道做用域只有全局做用域window和局部做用域函数)。因此会打印出window
  • obj.foo2()(),首先会执行obj.foo2(),这不是个箭头函数,因此它里面的this是调用它的obj对象,所以打印出obj,而返回的匿名函数是一个箭头函数,它的this由外层做用域决定,那也就是函数foo2咯,那也就是它的this会和foo2函数里的this同样,就也打印出了obj

答案:

'window'
'obj'
'obj'
复制代码

作完了这道题内心是否是有点谱了,感受也不是那么难嘛...😁

让咱们来拆分一下看看区别。

7.2 题目二

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

var name = 'window'
var obj1 = {
	name: 'obj1',
	foo: function () {
		console.log(this.name)
	}
}

var obj2 = {
	name: 'obj2',
	foo: () => {
		console.log(this.name)
	}
}

obj1.foo()
obj2.foo()
复制代码

解题分析:

  • 不使用箭头函数的obj1.foo()是由obj1调用的,因此this.nameobj1
  • 使用箭头函数的obj2.foo()的外层做用域是window,因此this.namewindow

答案:

'obj1'
'window'
复制代码

7.3 题目三

字面量对象中普通函数与箭头函数的区别:函数嵌套的题目

若是用普通函数和箭头函数来作一层嵌套关系的话,一共有四种状况,让咱们把每种状况都考虑一遍 😁:

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)
    }
  }
}

obj1.foo()()
obj2.foo()()
obj3.foo()()
obj4.foo()()
复制代码

解题分析:

  • obj1.foo()()两层都是普通函数,相似于题目4.6,分别打印出obj1window
  • obj2.foo()()外层为普通函数,内层为箭头,相似于题目7.1,都是打印出obj2
  • obj3.foo()()外层为箭头函数,内层为普通函数,箭头函数的this由外层做用域决定,所以为window,内层普通函数由调用者决定,调用它的是window,所以也为window
  • obj4.foo()()两层都是箭头函数,第一个箭头函数的this由外层做用域决定,所以为window,第二个箭头函数的this也由外层做用域决定,它的外层做用域是第一个箭头函数,而第一个箭头函数的thiswindow,所以内层的this也是window

答案:

(额,题目太长,为了你好看,因此答案我会把题目复制一遍... 放心... 我这绝对不是为了凑字数... 😅)

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)
    }
  }
}

obj1.foo()() // 'obj1' 'window'
obj2.foo()() // 'obj2' 'obj2'
obj3.foo()() // 'window' 'window'
obj4.foo()() // 'window' 'window'
复制代码

哇!霖呆呆!要按你这么出题的话,得有多少"奇形怪状"的题目啊!

哈哈 😄,其实你们不用担忧,作这类题只要谨记法则,找到规律,什么题均可以解了!

是时候给你们发一波心理鸡汤了:

(Sorry~ 让大家承受了这个年纪不应有的表情包)

7.4 题目四

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

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')
person1.foo1()
person1.foo2()
person2.foo2()
复制代码

解题思路:

  • person1.foo1()是个普通函数,this由最后调用它的对象决定,即person1
  • person1.foo2()为箭头函数,this由外层做用域决定,且指向函数定义时的this而非执行时,在这里它的外层做用域是函数Person,且这个是构造函数,而且使用了new来生成了对象person1,因此此时this的指向是为person1
  • person2.foo2()字面量建立的的对象person2中的foo2是个箭头函数,因为person2是直接在window下建立的,你能够理解为它所在的做用域就是在window下,所以person2.foo2()内的this应该是window

答案:

'person1'
'person1'
'window'
复制代码

7.5 题目五

构造函数对象中普通函数和箭头函数的区别:函数嵌套的题目

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.foo1()()
person1.foo2()()
person1.foo3()()
person1.foo4()()
复制代码

解题分析:

  • person1.foo1()()两层都是普通函数,这个再也不重复说了,打印出person1window。(相似题目6.2)
  • person1.foo2()()第一层普通函数,它的this是由最后调用它的对象决定也就是person1,第二层为箭头函数,它的this由外层做用域决定,也就是foo2这个函数,所以也为person1
  • person1.foo3()()第一层为箭头函数,this由外层做用域决定,所以为person1,第二层为普通函数,由最后调用者决定,所以为window
  • person1.foo4()()两层都是箭头函数,this由外层做用域决定,因此都是person1

答案:

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.foo1()() // 'person1' 'window'
person1.foo2()() // 'person1' 'person1'
person1.foo3()() // 'person1' 'window'
person1.foo4()() // 'person1' 'person1'
复制代码

7.6 题目六

箭头函数结合.call的题目

箭头函数的this没法经过bind、call、apply直接修改,可是能够经过改变做用域中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'
}
obj1.foo1.call(obj2)()
obj1.foo1().call(obj2)
obj1.foo2.call(obj2)()
obj1.foo2().call(obj2)
复制代码

解题分析:

  • obj1.foo1.call(obj2)()第一层为普通函数,而且经过.call改变了this指向为obj2,因此会打印出obj2,第二层为箭头函数,它的this和外层做用域中的this相同,所以也是obj2
  • obj1.foo().call(obj2)第一层打印出obj1,第二层为箭头函数,使用了.call想要修改this的指向,可是并不能成功,所以.call(obj2)对箭头函数无效,仍是打印出obj1
  • obj1.foo2.call(obj2)()第一层为箭头函数,而且想要经过.call(obj2)改变this指向,可是无效,且它的外层做用域是window,因此会打印出window,第二层为普通函数,this是最后调用者window,因此也会打印出window
  • obj1.foo2().call(obj2)第一层为箭头函数,外层做用域是window,打印出window,第二层为普通函数,且使用了.call(obj2)来改变this指向,因此打印出了obj2

答案:

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'
}
obj1.foo1.call(obj2)() // 'obj2' 'obj2'
obj1.foo1().call(obj2) // 'obj1' 'obj1'
obj1.foo2.call(obj2)() // 'window' 'window'
obj1.foo2().call(obj2) // 'window' 'obj2'
复制代码

在这道题中,obj1.foo1.call(obj2)()就至关因而经过改变做用域间接改变箭头函数内this的指向。

总结

OK👌,来总结一下箭头函数须要注意的点吧:

  • 它里面的this是由外层做用域来决定的,且指向函数定义时的this而非执行时
  • 字面量建立的对象,做用域是window,若是里面有箭头函数属性的话,this指向的是window
  • 构造函数建立的对象,做用域是能够理解为是这个构造函数,且这个构造函数的this是指向新建的对象的,所以this指向这个对象。
  • 箭头函数的this是没法经过bind、call、apply直接修改,可是能够经过改变做用域中this的指向来间接修改。

优势

  • 箭头函数写代码拥有更加简洁的语法(固然也有人认为这是缺点)
  • this由外层做用域决定,因此在某些场合咱们不须要写相似const that = this这样的代码

避免使用的场景

根据箭头函数的特性,因此咱们应该避免在如下四种场景中使用它:

  1. 使用箭头函数定义对象的方法
let obj = {
    value: 'LinDaiDai',
    getValue: () => console.log(this.value)
}
obj.getValue() // undefined
复制代码
  1. 定义原型方法
function Foo (value) {
    this.value = value
}
Foo.prototype.getValue = () => console.log(this.value)

const foo1 = new Foo(1)
foo1.getValue() // undefined
复制代码
  1. 构造函数使用箭头函数
const Foo = (value) => {
    this.value = value;
}
const foo1 = new Foo(1)
// 事实上直接就报错了 Uncaught TypeError: Foo is not a constructor
console.log(foo1);
复制代码
  1. 做为事件的回调函数
const button = document.getElementById('myButton');
button.addEventListener('click', () => {
    console.log(this === window); // => true
    this.innerHTML = 'Clicked button';
});
复制代码

8. 综合题

哈哈哈~

this大法已练成,接下来到了咱们最喜欢的综合题环节。

让咱们来作些难点的题巩固巩固吧!

8.1 题目一

字面量对象中的各类场景

var name = 'window'
var person1 = {
  name: 'person1',
  foo1: function () {
    console.log(this.name)
  },
  foo2: () => console.log(this.name),
  foo3: function () {
    return function () {
      console.log(this.name)
    }
  },
  foo4: function () {
    return () => {
      console.log(this.name)
    }
  }
}
var person2 = { name: 'person2' }

person1.foo1()
person1.foo1.call(person2)

person1.foo2()
person1.foo2.call(person2)

person1.foo3()()
person1.foo3.call(person2)()
person1.foo3().call(person2)

person1.foo4()()
person1.foo4.call(person2)()
person1.foo4().call(person2)
复制代码

这里我就不写题解了,由于若是你认真看了前面的题目的话,我相信必定能作的来,其实就是将本来分散的知识点汇总在一块儿。😁

  • person1.foo1()相似题目4.5
  • person1.foo2()相似题目7.1
  • person1.foo3()相似题目7.3
  • person1.foo4()相似题目7.3

答案:

var name = 'window'
var person1 = {
  name: 'person1',
  foo1: function () {
    console.log(this.name)
  },
  foo2: () => console.log(this.name),
  foo3: function () {
    return function () {
      console.log(this.name)
    }
  },
  foo4: function () {
    return () => {
      console.log(this.name)
    }
  }
}
var person2 = { name: 'person2' }

person1.foo1() // 'person1'
person1.foo1.call(person2) // 'person2'

person1.foo2() // 'window'
person1.foo2.call(person2) // 'window'

person1.foo3()() // 'window'
person1.foo3.call(person2)() // 'window'
person1.foo3().call(person2) // 'person2'

person1.foo4()() // 'person1'
person1.foo4.call(person2)() // 'person2'
person1.foo4().call(person2) // 'person1'
复制代码

8.2 题目二

构造函数中的各类场景

var name = 'window'
function Person (name) {
  this.name = name
  this.foo1 = function () {
    console.log(this.name)
  },
  this.foo2 = () => console.log(this.name),
  this.foo3 = function () {
    return function () {
      console.log(this.name)
    }
  },
  this.foo4 = function () {
    return () => {
      console.log(this.name)
    }
  }
}
var person1 = new Person('person1')
var person2 = new Person('person2')

person1.foo1()
person1.foo1.call(person2)

person1.foo2()
person1.foo2.call(person2)

person1.foo3()()
person1.foo3.call(person2)()
person1.foo3().call(person2)

person1.foo4()()
person1.foo4.call(person2)()
person1.foo4().call(person2)
复制代码
  • person1.foo1()相似题目7.4
  • person1.foo2()相似题目7.4
  • person1.foo3()相似题目7.5
  • person1.foo4()相似题目7.5

答案:

var name = 'window'
function Person (name) {
  this.name = name
  this.foo1 = function () {
    console.log(this.name)
  },
  this.foo2 = () => console.log(this.name),
  this.foo3 = function () {
    return function () {
      console.log(this.name)
    }
  },
  this.foo4 = function () {
    return () => {
      console.log(this.name)
    }
  }
}
var person1 = new Person('person1')
var person2 = new Person('person2')

person1.foo1() // 'person1'
person1.foo1.call(person2) // 'person2'

person1.foo2() // 'person1'
person1.foo2.call(person2) // 'person1'

person1.foo3()() // 'window'
person1.foo3.call(person2)() // 'window'
person1.foo3().call(person2) // 'person2'

person1.foo4()() // 'person1'
person1.foo4.call(person2)() // 'person2'
person1.foo4().call(person2) // 'person1'
复制代码

8.3 题目三

var name = 'window'
function Person (name) {
  this.name = name
  this.obj = {
    name: 'obj',
    foo1: function () {
      return function () {
        console.log(this.name)
      }
    },
    foo2: function () {
      return () => {
        console.log(this.name)
      }
    }
  }
}
var person1 = new Person('person1')
var person2 = new Person('person2')

person1.obj.foo1()()
person1.obj.foo1.call(person2)()
person1.obj.foo1().call(person2)

person1.obj.foo2()()
person1.obj.foo2.call(person2)()
person1.obj.foo2().call(person2)
复制代码

这道题仍是蛮有意思的,能够仔细说下。

首先是定义了一个构造函数Person,不过它与前面几题的区别就是,函数是放在其中的一个叫obj的对象里面。

在这里我提醒一句:this 永远指向最后调用它的那个对象

解题分析:

  • person1.obj.foo1()()返回的是一个普通的匿名函数,调用它的是window,因此打印出window
  • person1.obj.foo1.call(person2)()中是使用.call(person2)改变第一层函数中的this,匿名函数和它不要紧,依旧是window调用的,因此打印出window
  • person1.obj.foo1().call(person2)是经过.call(person2)改变匿名函数内的this,因此绑定有效,所以打印出person2
  • person1.obj.foo2()()第一层为普通函数,第二层为匿名箭头函数。首先让咱们明确匿名箭头函数内的this是由第一层普通函数决定的,因此咱们只要知道第一层函数内的this是谁就能够了。而这里,第一层函数最后是由谁调用的呢 🤔️?是由obj这个对象,因此打印出obj
  • person1.obj.foo2.call(person2)()中使用.call(person2)改变了第一层函数中的this指向,因此第二层的箭头函数会打印出person2
  • person1.obj.foo2().call(person2)中使用.call(person2)想要改变内层箭头函数的this指向,可是失败了,因此仍是为外层做用域里的this,打印出obj

答案

var name = 'window'
function Person (name) {
  this.name = name
  this.obj = {
    name: 'obj',
    foo1: function () {
      return function () {
        console.log(this.name)
      }
    },
    foo2: function () {
      return () => {
        console.log(this.name)
      }
    }
  }
}
var person1 = new Person('person1')
var person2 = new Person('person2')

person1.obj.foo1()() // 'window'
person1.obj.foo1.call(person2)() // 'window'
person1.obj.foo1().call(person2) // 'person2'

person1.obj.foo2()() // 'obj'
person1.obj.foo2.call(person2)() // 'person2'
person1.obj.foo2().call(person2) // 'obj'
复制代码

8.4 题目四

这道题是在评论区看到的,以为挺不错的,因此加到文章中(感谢👀小伙伴提供的题目)

来看看这里会打印出什么呢?

function foo() {
  console.log( this.a );
}
var a = 2;
(function(){
 "use strict";
  foo();
})();
复制代码

答案并非undefined,也不会报错,而是打印出了2

哈哈😄,其实这里是有一个迷惑点的,那就是"use strict"

咱们知道,使用了"use strict"开启严格模式会使得"use strict"如下代码的thisundefined,也就是这里的当即执行函数中的thisundefined

可是调用foo()函数的依然是window,因此foo()中的this依旧是window,因此会打印出2

若是你是使用this.foo()调用的话,就会报错了,由于如今当即执行函数中的thisundefined

或者将"use strict"放到foo()函数里面,也会报错。

嘻嘻,作作综合题总能令人心情愉悦~

9. 几道手写题

面试中,还有一些手写函数也是常常会被考到的,好比实现newcallapplybind

我在学习过程当中发现最主要的仍是要理解你要实现的这个东西,究竟是有什么功能的,或者有什么特性,而后咱们在构建的时候看能用什么方法来达到这样的功能。

这里我会贴上一些比较好的手写方法,可是实现方式我不会说的那么具体。那是由于我发现不少大佬说的已经很好很详细了,我就不必把人家的照搬过来...

在第九小节的最后我会贴上一个连接地址...

简单点...

贴上一个连接简单点...

9.1 手写一个new实现

例如🌰,咱们要实现一个new,首先要明白它有哪些特性。

看下面这个例子:

function Person (name) {
  this.name = name
}
Person.prototype.eat = function () {
  console.log('Eatting')
}
var lindaidai = new Person('LinDaiDai')
console.log(lindaidai)
lindaidai.eat()
复制代码

使用new建立的实例:

  • 能访问到构造函数里的属性(name)
  • 能访问原型中的属性(eat)

根据特性,咱们能够这样实现:

function create () {
  // 1. 获取构造函数,而且删除 arguments 中的第一项
  var Con = [].shift.call(arguments);
  // 2. 建立一个空的对象并连接到构造函数的原型,使它能访问原型中的属性
  var obj = Object.create(Con.prototype);
  // 3. 使用apply改变构造函数中this的指向实现继承,使obj能访问到构造函数中的属性
  var ret = Con.apply(obj, arguments);
  // 4. 优先返回构造函数返回的对象
  return ret instanceof Object ? ret : obj;
}
复制代码

这里要提一嘴,第四步中为何要作这么一个判断呢?

主要是你要考虑构造函数它有没有返回值。

像咱们案例中的构造函数Person它是没有返回值的。

// 1.有返回值且为对象
function Person (name, sex) {
	this.name = name
	return {
		sex: sex
	}
}
// 2. 没有返回值
function Person (name) {
	this.name = name
}
// 3. 返回值为基本类型
function Person (name) {
	this.name = name
	return 'str'
}
复制代码
  1. 构造函数中有返回值且为对象,那么建立的实例就只能访问到返回对象中的属性,因此要判断一下ret的类型,若是是对象的话,则返回这个对象。
  2. 构造函数中没有返回值,那么建立的实例就能访问到这个构造函数中的全部属性了,此时ret就会为undefined,因此返回obj
  3. 构造函数中有返回值可是返回值是undefined之外的其它基本类型(好比字符串),这种状况当成第二种状况(没有返回值)来处理。

验证:

来验证一下可行性:

function Person (name) {
  this.name = name
}
Person.prototype.eat = function () {
  console.log('Eatting')
}
function create () {
  // 1. 获取构造函数,而且删除 arguments 中的第一项
  var Con = [].shift.call(arguments);
  // 2. 建立一个空的对象并连接到构造函数的原型,使它能访问原型中的属性
  var obj = Object.create(Con.prototype);
  // 3. 使用apply改变构造函数中this的指向实现继承,使obj能访问到构造函数中的属性
  var ret = Con.apply(obj, arguments);
  // 4. 优先返回构造函数返回的对象
  return ret instanceof Object ? ret : obj;
}
var lindaidai = create(Person, 'LinDaiDai')
console.log(lindaidai) // Person{ name: 'LinDaiDai' }
lindaidai.eat() // 'Eatting'
复制代码

9.2 手写一个call函数实现

ES3实现

function fnFactory(context) {
  var unique_fn = "fn";
  while (context.hasOwnProperty(unique_fn)) {
    unique_fn = "fn" + Math.random();
  }
  return unique_fn;
}
Function.prototype.call2 = function(context) {
  context = (context !== null && context !== undefined) ? Object(context) : window;
  var args = [];
  for (var i = 1, len = arguments.length; i < len; i++) {
    args.push("arguments[" + i + "]");
  }
  var fn = fnFactory(context);
  context[fn] = this;
  var result = eval("context[fn](" + args + ")");
  delete context[fn];
  return result;
};
复制代码

ES6实现

Function.prototype.call3 = function(context) {
  context = (context !== null && context !== undefined) ? Object(context) : window;
  var fn = Symbol();
  context[fn] = this;

  let args = [...arguments].slice(1);
  let result = context[fn](...args);

  delete context[fn];
  return result;
};
复制代码






过程分析:

function fnFactory(context) {
  var unique_fn = "fn";
  while (context.hasOwnProperty(unique_fn)) {
    unique_fn = "fn" + Math.random();
  }
  return unique_fn;
}
Function.prototype.call2 = function(context) {
  // 1. 如果传入的context是null或者undefined时指向window;
  // 2. 如果传入的是原始数据类型, 原生的call会调用 Object() 转换
  context = (context !== null && context !== undefined) ? Object(context) : window;
  // 3. 建立一个独一无二的fn函数的命名
  var fn = fnFactory(context);
  // 4. 这里的this就是指调用call的那个函数
  // 5. 将调用的这个函数赋值到context中, 这样以后执行context.fn的时候, fn里的this就是指向context了
  context[fn] = this;
  // 6. 定义一个数组用于放arguments的每一项的字符串: ['agruments[1]', 'arguments[2]']
  var args = [];
  // 7. 要从第1项开始, 第0项是context
  for (var i = 1, l = arguments.length; i < l; i++) {
    args.push("arguments[" + i + "]");
  }
  // 8. 使用eval()来执行fn并将args一个个传递进去
  var result = eval("context[fn](" + args + ")");
  // 9. 给context额外附件了一个属性fn, 因此用完以后须要删除
  delete context[fn];
  // 10. 函数fn可能会有返回值, 须要将其返回
  return result;
};
复制代码

测试代码

var obj = {
  name: "objName"
};

function consoleInfo(sex, weight) {
  console.log(this.name, sex, weight);
}
var name = "globalName";
consoleInfo.call2(obj, "man", 100); // 'objName' 'man' 100
consoleInfo.call3(obj, "woman", 120); // 'objName' 'woman' 120
复制代码

9.3 手写一个apply实现

ES3实现

function fnFactory(context) {
  var unique_fn = "fn";
  while (context.hasOwnProperty(unique_fn)) {
    unique_fn = "fn" + Math.random();
  }
  return unique_fn;
}
Function.prototype.apply2 = function(context, arr) {
  context = context ? Object(context) : window;
  var fn = fnFactory(context);
  context[fn] = this;

  var result;
  if (!arr) {
    result = context[fn]();
  } else {
    var args = [];
    for (var i = 0, len = arr.length; i < len; i++) {
      args.push("arr[" + i + "]");
    }
    result = eval("context[fn](" + args + ")");
  }
  delete context[fn];
  return result;
};
复制代码

ES6实现

Function.prototype.apply3 = function(context, arr) {
  context = context ? Object(context) : window;
  let fn = Symbol();
  context[fn] = this;

  let result = arr ? context[fn](...arr) : context[fn]();
  delete context[fn];
  return result;
};
复制代码






过程分析:

function fnFactory(context) {
  var unique_fn = "fn";
  while (context.hasOwnProperty(unique_fn)) {
    unique_fn = "fn" + Math.random();
  }
  return unique_fn;
}
Function.prototype.apply2 = function(context, arr) {
  // 1. 如果传入的context是null或者undefined时指向window;
  // 2. 如果传入的是原始数据类型, 原生的call会调用 Object() 转换
  context = context ? Object(context) : window;
  // 3. 建立一个独一无二的fn函数的命名
  var fn = fnFactory(context);
  // 4. 这里的this就是指调用call的那个函数
  // 5. 将调用的这个函数赋值到context中, 这样以后执行context.fn的时候, fn里的this就是指向context了
  context[fn] = this;

  var result;
  // 6. 判断有没有第二个参数
  if (!arr) {
    result = context[fn]();
  } else {
    // 7. 有的话则用args放每一项的字符串: ['arr[0]', 'arr[1]']
    var args = [];
    for (var i = 0, len = arr.length; i < len; i++) {
      args.push("arr[" + i + "]");
    }
    // 8. 使用eval()来执行fn并将args一个个传递进去
    result = eval("context[fn](" + args + ")");
  }
  // 9. 给context额外附件了一个属性fn, 因此用完以后须要删除
  delete context[fn];
  // 10. 函数fn可能会有返回值, 须要将其返回
  return result;
};
复制代码

9.4 手写一个bind函数实现

提示:

  1. 函数内的this表示的就是调用的函数
  2. 能够将上下文传递进去, 并修改this的指向
  3. 返回一个函数
  4. 能够传入参数
  5. 柯里化
  6. 一个绑定的函数也能使用new操做法建立对象, 且提供的this会被忽略
Function.prototype.bind2 = function(context) {
  if (typeof this !== "function") {
    throw new Error(
      "Function.prototype.bind - what is trying to be bound is not callable"
    );
  }
  var self = this;
  var args = Array.prototype.slice.call(arguments, 1);

  var fBound = function() {
    var innerArgs = Array.prototype.slice.call(arguments);
    return self.apply(
      this instanceof fNOP ? this : context,
      args.concat(innerArgs)
    );
  };

  var fNOP = function() {};
  fNOP.prototype = this.prototype;
  fBound.prototype = new fNOP();
  return fBound;
};
复制代码






过程分析

Function.prototype.bind2 = function(context) {
  // 1. 判断调用bind的是否是一个函数
  if (typeof this !== "function") {
    throw new Error(
      "Function.prototype.bind - what is trying to be bound is not callable"
    );
  }
  // 2. 外层的this指向调用者(也就是调用的函数)
  var self = this;
  // 3. 收集调用bind时的其它参数
  var args = Array.prototype.slice.call(arguments, 1);

  // 4. 建立一个返回的函数
  var fBound = function() {
    // 6. 收集调用新的函数时传入的其它参数
    var innerArgs = Array.prototype.slice.call(arguments);
    // 7. 使用apply改变调用函数时this的指向
    // 做为构造函数调用时this表示的是新产生的对象, 不做为构造函数用的时候传递context
    return self.apply(
      this instanceof fNOP ? this : context,
      args.concat(innerArgs)
    );
  };
  // 5. 建立一个空的函数, 且将原型指向调用者的原型(为了能用调用者原型中的属性)
  // 下面三步的做用有点相似于 fBoun.prototype = this.prototype 但有区别
  var fNOP = function() {};
  fNOP.prototype = this.prototype;
  fBound.prototype = new fNOP();
  // 8. 返回最后的结果
  return fBound;
};
复制代码

友情连接

关于手写实现的具体实现方式能够查看木易杨大大这里:

《木易杨前端进阶-深度解析bind原理、使用场景及模拟实现》

看这类题目的时候你们可能会有:"我怎么好像好废啊?"的感受。

来,自信点,把好像去掉。

哈哈,玩笑话,若是静下心来看看教材跟着步骤走的话是必定会有收获的!

后语

知识无价,支持原创。

参考文章:

你盼世界,我盼望你无bug。这篇文章就介绍到这里,咱们又作完了40this的题目。再次感谢你的阅读🙏。

"音响师bgm放起来!!!"

点个赞再走吧 😁

喜欢霖呆呆的小伙还但愿能够关注霖呆呆的公众号 LinDaiDai 或者扫一扫下面的二维码👇👇👇.

我会不定时的更新一些前端方面的知识内容以及本身的原创文章🎉

你的鼓励就是我持续创做的主要动力 😊.

相关推荐:

《JavaScript进阶-执行上下文(理解执行上下文一篇就够了)》

《全网最详bpmn.js教材》

《霖呆呆你来讲说浏览器缓存吧》

《怎样让后台小哥哥快速对接你的前端页面》

《建议改为: 读完这篇你还不懂Babel我给你寄口罩》

《你的掘金文章本能够这么炫(博客美化工具一波带走)》

《【建议星星】要就来45道Promise面试题一次爽到底(1.1w字用心整理)》

相关文章
相关标签/搜索