You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
需要注意的是,设置的 thisArg 值并不一定是该函数执行时真正的 this 值,如果这个函数处于非严格模式下,则指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中就是 window 对象),同时值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象。
引言
JS
系列暂定 27 篇,从基础,到原型,到异步,到设计模式,到架构模式等。本篇是
JS
系列中第 5 篇,文章主讲 JS 中call
、apply
、bind
、箭头函数以及柯里化,着重介绍它们之间的区别、对比使用,深入了解call
、apply
、bind
。一、Function.prototype.call()
call()
方法调用一个函数, 其具有一个指定的this
值和多个参数(参数的列表)。它运行
func
,提供的第一个参数thisArg
作为this
,后面的作为参数。1. func 与 func.call
先看一个例子:
他们都调用的是
func
,参数是1
,2
和3
。唯一的区别是
func.call
也将this
设置为obj
。需要注意的是,设置的 thisArg 值并不一定是该函数执行时真正的
this
值,如果这个函数处于非严格模式下,则指定为null
和undefined
的this
值会自动指向全局对象(浏览器中就是 window 对象),同时值为原始值(数字,字符串,布尔值)的this
会指向该原始值的自动包装对象。2. func.call 绑定上下文
例如,在下面的代码中,我们在对象的上下文中调用
sayWord.call(bottle)
运行sayWord
,并bottle
传递为sayWord
的this
:3. 使用 func.call 时未指定 this
非严格模式
严格模式
4. call 在 JS 继承中的使用: 构造继承
基本思想:在子类型的构造函数内部调用父类型构造函数。
注意:函数只不过是在特定环境中执行代码的对象,所以这里使用 apply/call 来实现。
使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
5. 解决 var 作用域问题
打印结果:
在上面例中的
for
循环体内,我们创建了一个匿名函数,然后通过调用该函数的call
方法,将每个数组元素作为指定的this
值立即执行了那个匿名函数。这个立即执行的匿名函数的作用是打印出bottle[i]
对象在数组中的正确索引号。二、Function.prototype.apply()
apply()
方法调用一个具有给定this
值的函数,以及作为一个数组(或[类似数组对象)提供的参数。它运行
func
设置this = context
并使用类数组对象args
作为参数列表。例如,这两个调用几乎相同:
两个都运行
func
给定的参数是1,2,3
。但是apply
也设置了this = context
。call
和apply
之间唯一的语法区别是call
接受一个参数列表,而apply
则接受带有一个类数组对象。需要注意:Chrome 14 以及 Internet Explorer 9 仍然不接受类数组对象。如果传入类数组对象,它们会抛出异常。
1. call、apply 与 扩展运算符
我们已经知道了JS 基础之: var、let、const、解构、展开、函数 一章中的扩展运算符
...
,它可以将数组(或任何可迭代的)作为参数列表传递。因此,如果我们将它与call
一起使用,就可以实现与apply
几乎相同的功能。这两个调用结果几乎相同:
如果我们仔细观察,那么
call
和apply
的使用会有一些细微的差别。...
允许将 可迭代的参数列表
作为列表传递给call
。apply
只接受 类数组一样的参数列表
。2. apply 函数转移
apply
最重要的用途之一是将调用传递给另一个函数,如下所示:wrapper
通过anotherFunction.apply
获得了上下文this
和anotherFunction
的参数并返回其结果。当外部代码调用这样的
wrapper
时,它与原始函数的调用无法区分。3. apply 连接数组
array.push.apply
将数组添加到另一数组上:4. apply 来链接构造器
5. apply 和内置函数
它相当于:
但是:如果用上面的方式调用
apply
,会有超出 JavaScript 引擎的参数长度限制的风险。更糟糕的是其他引擎会直接限制传入到方法的参数个数,导致参数丢失。所以,当数据量较大时
三、Function.prototype.bind()
JavaScript 新手经常犯的一个错误是将一个方法从对象中拿出来,然后再调用,希望方法中的
this
是原来的对象(比如在回调中传入这个方法)。如果不做特殊处理的话,一般this
就丢失了。例如:
问题一的 this.nickname 是 undefined ,原因是 this 指向是在运行函数时确定的,而不是定义函数时候确定的,再因为 sayHi 中 setTimeout 在全局环境下执行,所以 this 指向 setTimeout 的上下文:window。
问题二的 this.nickname 是 undefined ,是因为 setTimeout 仅仅只是获取函数 bottle.sayHello 作为 setTimeout 回调函数,this 和 bottle 对象分离了。
问题二可以写为:
那么怎么解决这两个问题喃?
解决方案一: 缓存 this 与包装
首先通过缓存 this 解决问题一
bottle.sayHi();
:那问题二
setTimeout(bottle.sayHello, 1000);
喃?这样看似解决了问题二,但如果我们在
setTimeout
异步触发之前更新bottle
值又会怎么样呢?bottle.sayHello()
最终打印为Hi, haha!
,那么怎么解决这种事情发生喃?解决方案二: bind
bind()
最简单的用法是创建一个新绑定函数,当这个新绑定函数被调用时,this
键值为其提供的值,其参数列表前几项值为创建时指定的参数序列,绑定函数与被调函数具有相同的函数体(ES5中)。所以,从原来的函数和原来的对象创建一个绑定函数,则能很漂亮地解决上面两个问题:
问题一,可以通过
bind
或箭头函数完美解决。最终更新
bottle
后,setTimeout(sayHello, 1000);
打印依然是Hello, bottle!
, 问题二完美解决!1. bind 与 new
再看一个例子:
上面例子中,运行结果
this.nickname
输出为undefined
,这不是全局nickname
, 也不是bottle
对象中的nickname
,这说明bind
的this
对象失效了,new
的实现中生成一个新的对象,这个时候的this
指向的是sayHello
。注意 :绑定函数也可以使用
new
运算符构造:这样做就好像已经构造了目标函数一样。提供的 this 值将被忽略,而前置参数将提供给模拟函数。2. 二次 bind
输出依然是
Hello, Bottle
,这是因为func.bind(...)
返回的外来的绑定函数对象仅在创建的时候记忆上下文(如果提供了参数)。一个函数不能作为重复绑定。
2. 偏函数
当我们确定一个函数的一些参数时,返回的函数(更加特定)被称为偏函数。我们可以使用
bind
来获取偏函数:当我们不想一遍又一遍重复相同的参数时,偏函数很方便。
3. 作为构造函数使用的绑定函数
四、柯里化
在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。这个技术由 Christopher Strachey 以逻辑学家 Haskell Curry 命名的,尽管它是 Moses Schnfinkel 和 Gottlob Frege 发明的。
这里定义了一个
add
函数,它接受一个参数并返回一个新的函数。调用add
之后,返回的函数就通过闭包的方式记住了add
的第一个参数。所以说bind
本身也是闭包的一种使用场景。柯里化是将
f(a,b,c)
可以被以f(a)(b)(c)
的形式被调用的转化。JavaScript 实现版本通常保留函数被正常调用和在参数数量不够的情况下返回偏函数这两个特性。五、扩展:箭头函数
1. 没有 this
报错是因为
Hello, undefined
是因为运行时this=Window
,Window.nickname
为undefined
。但箭头函数就没事,因为箭头函数没有
this
。在外部上下文中,this
的查找与普通变量搜索完全相同。this
指向定义时的环境。2. 不可 new 实例化
不具有
this
自然意味着另一个限制:箭头函数不能用作构造函数。他们不能用new
调用。3. 箭头函数 vs bind
箭头函数
=>
和正常函数通过.bind(this)
调用有一个微妙的区别:.bind(this)
创建该函数的 “绑定版本”。=>
不会创建任何绑定。该函数根本没有this
。在外部上下文中,this
的查找与普通变量搜索完全相同。4. 没有 arguments 对象
箭头函数也没有
arguments
变量。因为我们需要用当前的
this
和arguments
转发一个调用,所有这对于装饰者来说非常好。例如,
defer(f, ms)
得到一个函数,并返回一个包装函数,以毫秒
为单位延迟调用:没有箭头功能的情况如下所示:
在这里,我们必须创建额外的变量
args
和ctx
,以便setTimeout
内部的函数可以接收它们。5. 总结
The text was updated successfully, but these errors were encountered: