你盼世界,我盼望你无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
会绑定到undefined
)obj.foo()
的调用方式, foo
内的this
指向obj
)call()
或者apply()
方法直接指定this
的绑定对象, 如foo.call(obj)
)this
的指向由外层做用域决定的)先介绍一种最简单的绑定方式吧:默认绑定。
也就是咱们常说的:在非严格模式下this
指向的是全局对象window
,而在严格模式下会绑定到undefined
。
老规矩,来看个最基本的案例:
var a = 10; function foo () { console.log(this.a) } foo(); 复制代码
咱们知道在使用var
建立变量的时候(不在函数里),会把建立的变量绑定到window
上,因此此时a
是window
下的属性。
而函数foo
也是window
下的属性。
所以上面的代码其实就至关因而这样:
window.a = 10; function foo() { console.log(this.a) } window.foo(); 复制代码
在这里,调用foo()
函数的是window
对象,且又是在非严格模式下,因此foo()
中this
的指向是window
对象,所以this.a
会输出10
。
答案:
10
复制代码
改造下题目一,看看在严格模式下。
(想要开启严格模式,只要在须要开启的地方写上"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 复制代码
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
复制代码
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.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
复制代码
(我知道有的小伙伴不满这种题目,这吖的不就是和人玩文字游戏吗?有什么技术含量,可是现实就是这样,不少面试官会喜欢问这种细节题来考察你细心不细心。在没能力改变这种状况的前提下,你只能试着接受它...)
OK👌,介绍完了默认绑定以后,让咱们来看看第二种隐式绑定。
其实大佬 sunshine小小倩 教了咱们一个简单的规则,this 永远指向最后调用它的那个对象。
谁最后调用的函数,函数内的this
指向的就是谁(不考虑箭头函数)。
(了解上下文对象的小伙伴应该都知道,这与上下文对象以及做用域有关,想要了解的小伙伴能够看个人这篇文章《JavaScript进阶-执行上下文栈和变量对象(一周一更)》
下面你能够用这个规则来作题啦 😁。
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
更后面一点。
隐式绑定的基本概念你们应该都清楚了,不过其实有一个关于隐式绑定的经常使用考点,那就是隐式丢失问题。
隐式丢失其实就是被隐式绑定的函数在特定的状况下会丢失绑定对象。
有两种状况容易发生隐式丢失问题:
咱们一种一种来看哈。
使用另外一个变量来给函数取别名会发生隐式丢失。
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()
,若是你不相信的话,能够看下面一题👇。
让咱们在一个新的变量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
再就是若是你把一个函数当成参数传递时,也会被隐式赋值,发生意想不到的问题。
来看看这道题目:
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()
,不信你看下一题。
如今咱们不用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 复制代码
功能如其名,就是强行使用某些方法,改变函数内this
的指向。
经过call()、apply()
或者bind()
方法直接指定this
的绑定对象, 如foo.call(obj)
。
这里有几个知识点须要注意:
.call()
或者.apply()
的函数是会直接执行的bind()
是建立一个新的函数,须要手动调用才会执行.call()
和.apply()
用法基本相似,不过call
接收若干个参数,而apply
接收的是一个数组(其实这应该是常常听到的知识点了吧 😅)
来看看题目一。
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
都使用了call
或apply
来改变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
复制代码
了解了显式绑定的基本使用以后,让咱们来看看它的妙用。
首先,是这个例子🌰:
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
呢?在这个函数里,设置了一个定时器,并要求咱们打印出this
和this.a
。
想一想我前面说过的话,谁调用的函数,函数内的this
指向的就是谁。
而对于setTimeout
中的函数,这里存在隐式绑定的隐式丢失,也就是当咱们将函数做为参数传递时会被隐式赋值,回调函数丢失this
绑定,所以这时候setTimeout
中的函数内的this
是指向window
的。
(以前呆呆一直认为的是定时器里的函数和定时器是有关系的,因此有一些错误的理解,感谢掘友朝游夕宴和l.jx的指出)
因此最终的结果是:
2
Window{...}
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()
也是能够的,由于定时器里的函数在时间到了以后本就是会自动执行的。
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
复制代码
其实在实际面试中,面试官喜欢以这样的方式考你:
看看这道题,会输出什么呢 🤔️?
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()
的区别了,一个是针对于函数,一个是针对于函数的返回值。
下面我会用更多的例题来帮助你们了解这类题目的解法。
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()()
,这样才算是调用匿名函数。1
是foo.call(obj)
输出的,因为.call()
是紧跟着foo
的,因此改变的是foo()
内this
的指向,而且.call()
是会使函数当即执行的,所以打印出1
,同理,它也没有调用返回的函数。2
是foo().call(obj)
先执行foo()
时打印出来的,此时foo()
内this
仍是指向window
。foo()
以后,会返回一个匿名函数,而且后面使用了.call(obj)
来改变这个匿名函数的this
指向并调用了它,因此输出了1
。咦~好像从这道题开始,变得愈来愈有意思了哈 😁
想一想咱们把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
而已,并无调用。说实话,作上面这类题目,会让我有一种疑惑。
这种函数内返回的函数,它的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
复制代码
(请注意这个例子🌰,后面它会与箭头函数作对比)
一直都在作函数返回函数的题目,让咱们来看看把它们加到对象里,会有哪些有趣的题目吧。
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: obj
和inner: window
,这个没什么疑惑的。obj.foo.(obj2)()
其实也没啥可疑惑的了,打印出foo: obj2
和inner: window
(相似4.8
)。obj.foo().call(obj2)
就更没啥可疑惑的了,打印出foo: obj
和inner: obj2
。完了,都没啥可疑惑的了。就这点东西,你都掌握了...
OK,小fo子,请记住你如今的自信,等会作综合题的时候但愿你还能如此清醒 🙏。
一直作这种题目是否是没意思,让咱们加几个参数来玩玩。
[阴笑]~
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.a
为3
,并传入最后一个参数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
方法,还考到了算数...
哈哈哈哈~
(小伙子,年纪轻轻就能出如此心机的题目,让你当面试官还得了?)
除了上面👆那几道题的用法以外,咱们还能够有一些其它的用法。
例如,咱们能够在一个函数内使用call
来显式绑定某个对象,这样不管怎样调用它,其内部的this
老是指向这个对象。(可见题目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
复制代码
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 复制代码
接下我想要介绍一个比较冷门的知识。
相信你们对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
的好滴,让咱们来看看另外一种this
的绑定形式,也就是new
绑定。
使用new
来调用一个函数,会构造一个新对象并把这个新对象绑定到调用函数中的this
。
例如第一题。
使用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' 复制代码
构造函数中不只能够加属性,也能够加方法:
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
对象自动加了一个属性值为window
的name
属性...
思考了一会,我想起来了,以前我在代码里确实定义了一个变量name
,var name = 'window'
。
但是这段代码我已经删掉了啊,而且强制刷新了浏览器,难道它仍是存在于window
中吗?
为了验证个人想法,我又从新定义了一个变量var name = 'LinDaiDai'
,而后打开浏览器刷新,以后再删除这段代码再刷新。
果真,window.name
打印出来仍是LinDaiDai
。它会记住这个name
属性不被回收,直到你关闭此页签。
可是若是把name
属性名换成别的(好比dd
),它就不会有这种状况。
有兴趣的小伙伴能够去尝试一下哈,这里我就不深究了...
使用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.foo
和person2
就没有什么区别。
打印出来的结果为:
'person1' 'window' 'person2' 'window' 复制代码
当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
。终于到了期待已久的箭头函数绑定 😁。
在上面👆,咱们有学到一个诀窍:this 永远指向最后调用它的那个对象。
可是对于箭头函数就不是这样咯,它里面的this
是由外层做用域来决定的,且指向函数定义时的this而非执行时。
它里面的this是由外层做用域来决定的
啥意思呢?来看看这句话:
箭头函数中没有 this 绑定,必须经过查找做用域链来决定其值,若是箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,不然,this 为 undefined。
而且指向函数定义时的this而非执行时
这句话能够等会看题目7.4
。
读了这句话相信你已经能解决80%的题目了,让咱们看完了第一题7.1
以后,再来看看箭头函数能够分为哪几类题目来讲吧,这是目录:
.call
的题目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' 复制代码
作完了这道题内心是否是有点谱了,感受也不是那么难嘛...😁
让咱们来拆分一下看看区别。
字面量对象中普通函数与箭头函数的区别: 只有一层函数的题目
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.name
为obj1
。obj2.foo()
的外层做用域是window
,因此this.name
为window
。答案:
'obj1' '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()() obj2.foo()() obj3.foo()() obj4.foo()() 复制代码
解题分析:
obj1.foo()()
两层都是普通函数,相似于题目4.6
,分别打印出obj1
和window
。obj2.foo()()
外层为普通函数,内层为箭头,相似于题目7.1
,都是打印出obj2
。obj3.foo()()
外层为箭头函数,内层为普通函数,箭头函数的this
由外层做用域决定,所以为window
,内层普通函数由调用者决定,调用它的是window
,所以也为window
。obj4.foo()()
两层都是箭头函数,第一个箭头函数的this
由外层做用域决定,所以为window
,第二个箭头函数的this
也由外层做用域决定,它的外层做用域是第一个箭头函数,而第一个箭头函数的this
是window
,所以内层的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~
让大家承受了这个年纪不应有的表情包)
构造函数对象中普通函数和箭头函数的区别:一层函数的题目
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' 复制代码
构造函数对象中普通函数和箭头函数的区别:函数嵌套的题目
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()()
两层都是普通函数,这个再也不重复说了,打印出person1
和window
。(相似题目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' 复制代码
箭头函数结合.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
这样的代码避免使用的场景
根据箭头函数的特性,因此咱们应该避免在如下四种场景中使用它:
let obj = { value: 'LinDaiDai', getValue: () => console.log(this.value) } obj.getValue() // undefined 复制代码
function Foo (value) { this.value = 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', () => { console.log(this === window); // => true this.innerHTML = 'Clicked button'; }); 复制代码
哈哈哈~
this
大法已练成,接下来到了咱们最喜欢的综合题环节。
让咱们来作些难点的题巩固巩固吧!
字面量对象中的各类场景
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' 复制代码
构造函数中的各类场景
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' 复制代码
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' 复制代码
这道题是在评论区看到的,以为挺不错的,因此加到文章中(感谢👀小伙伴提供的题目)
来看看这里会打印出什么呢?
function foo() { console.log( this.a ); } var a = 2; (function(){ "use strict"; foo(); })(); 复制代码
答案并非undefined
,也不会报错,而是打印出了2
。
哈哈😄,其实这里是有一个迷惑点的,那就是"use strict"
。
咱们知道,使用了"use strict"
开启严格模式会使得"use strict"
如下代码的this
为undefined
,也就是这里的当即执行函数中的this
是undefined
。
可是调用foo()
函数的依然是window
,因此foo()
中的this
依旧是window
,因此会打印出2
。
若是你是使用this.foo()
调用的话,就会报错了,由于如今当即执行函数中的this
是undefined
。
或者将"use strict"
放到foo()
函数里面,也会报错。
嘻嘻,作作综合题总能令人心情愉悦~
面试中,还有一些手写函数也是常常会被考到的,好比实现new
、call
、apply
、bind
。
我在学习过程当中发现最主要的仍是要理解你要实现的这个东西,究竟是有什么功能的,或者有什么特性,而后咱们在构建的时候看能用什么方法来达到这样的功能。
这里我会贴上一些比较好的手写方法,可是实现方式我不会说的那么具体。那是由于我发现不少大佬说的已经很好很详细了,我就不必把人家的照搬过来...
在第九小节的最后我会贴上一个连接地址...
简单点...
贴上一个连接简单点...
例如🌰,咱们要实现一个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' } 复制代码
ret
的类型,若是是对象的话,则返回这个对象。ret
就会为undefined
,因此返回obj
。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' 复制代码
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 复制代码
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; }; 复制代码
提示:
this
表示的就是调用的函数this
的指向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
。这篇文章就介绍到这里,咱们又作完了40
道this
的题目。再次感谢你的阅读🙏。
"音响师bgm放起来!!!"
点个赞再走吧 😁
喜欢霖呆呆的小伙还但愿能够关注霖呆呆的公众号 LinDaiDai
或者扫一扫下面的二维码👇👇👇.
我会不定时的更新一些前端方面的知识内容以及本身的原创文章🎉
你的鼓励就是我持续创做的主要动力 😊.
相关推荐: