该文章是直接翻译国外一篇文章,关于JS函数调用和"this"的处理。
都是基于原文处理的,其余的都是直接进行翻译可能有些生硬,因此为了行文方便,就作了一些简单的本地化处理。
若是想直接根据原文学习,能够忽略此文。javascript
关于JS函数是如何调用的困惑了不少年,尤为是在JS函数中this
的语法机制很让人头疼。java
在我看来,若是理解核心函数的调用机制,同时验证一些以核心函数为基础的其余实现方式的运行机制,关于上述所说的问题就会迎刃而解。bash
首先,让咱们来解析一些核心函数的调用机制的重点---Function
对象的call
方法。 call
函数的调用过程以下:app
parameters
第二个值到最后一个剥离出来并从新构建一个新的参数列表(argList
)thisValue
thisValue
赋值给this
,argList
做为函数的参数列表(argument list)示例以下:函数
function hello(thing){
console.log(this+ "says hello"+ thing);
}
hello.call("北宸南蓁","world");
//输出结果:北宸南蓁 says hello world
复制代码
正如实践以后所获得的结果,咱们调用hello()
的时候,将this
的值赋值为北宸南蓁
同时将world
做为hello
运行时的参数list。上述的处理流程就是JS函数调用的核心机制。你能够这样粗略的认为:其余函数的调用机制就是在核心机制的基础上进行了封装/简化处理(desugar)。学习
很显然,利用call
调用函数看起来,不是一个很聪明的亚子。因此,JS运行利用简单语法 hello("world")
来直接调用函数。ui
function hello(thing){
console.log("Hello" + thing);
}
//简化的语法(封装以后的语法)
hello("world")
//核心语法
hello.call(window,"world");
复制代码
NOTE:在ECMAScript 5的严格模式下有些许的不一样:this
//简化语法
hello("world");
//核心语法
hello(undefined,"world");
复制代码
综上所述:能够将简单函数fn(...args)
的调用汇总为fn.call(window [ES5-strict:undefined],...args)
。spa
NOTEprototype
上述的调用公式一样也适应于:
(funciton(){})()
==>(function(){}).call(window [ES5-strict:undefined])
在js的应用场景中,函数做为对象的属性也是很常见的情景。在这种情景下,会发生以下的简化处理:
var person ={
name:"北宸南蓁",
hello:function(){
console.log(this + "says hello" + thing);
}
}
//简化的语法
person.hello("world");
//核心语法/
person.hello.call(person,"world");
复制代码
Note: 上述的简化过程不受成员函数的赋值和定义方式的影响的。例如上面的例子中,成员函数是直接定义在对象中。若是动态的对成员函数进行赋值,最后的简化结果也是同样的。
function hello(thing){
console.log(this + "says hello" + thing);
}
person = { name:"北宸南蓁"};
person.hello= hello;
//简化语法
person.hello("world") //该种的核心语法也是 `person.hello.call(person,'world')`
hello("world") //"[object DOMWindow]world"
复制代码
Note:上述全部的函数中this
的值都不是肯定的。this
的值由调用函数的所在的 做用域决定。
Function.prototype.bind
对做用域进行绑定在某些应用场景中,须要将函数中的this
值进行绑定到指定的环境中。就须要额外的借助一个函数进行特定环境的绑定。
var person ={
name:"北宸南蓁",
hello:function(thing){
console.log(this + "says hello " + thing);
}
}
var boundHello = function(thing){
return person.hello.call(person,thing);
}
boundHello("world");
复制代码
虽然在boundHello("world")
调用的时候,被脱糖(desugar)为boundHello.call(window,"world")
。可是在函数中,是将person对象的成员函数hello
进行this
值的处理,指向hello
函数应该在的做用域中。
或者咱们能够将boundXX
函数变得更加通用。
var bind = function(func,thisValue){
return function(){
return func.apply(thisValue,arguments);
}
}
var boundHello = bind(person.hello,person);
boundHello("world");
复制代码
其中实现的原理这里就再也不赘述了。
因为这种场景不少,ES5在Function
对象中新增了bind
方法,用于对一个函数指定特定的this
值。
var boundHello = person.hello.bind(person);
boundHello("world");
复制代码
这种处理方式颇有用,好比,你将一个成员函数做为callback
:
var person = {
name: "北宸南蓁",
hello: function() { console.log(this.name + " says hello world"); }
}
$("#some-div").click(person.hello.bind(person));
复制代码
在上文中,为了可以在现有的规范语法中解释清楚函数调用的核心机制。经过func.call
来阐述函数底层是如何实现一系列的数据操做的。实际上,实现函数调用的核心语法另有其人[[Call]]
(这是一个内部属性),可是他是func.call
、[obj.]func()
实现的基础零件。
咱们来了解一下func.call
的定义
func
不是一个函数,直接抛出错误。argList
arg1
到参数结尾的全部参数的值做为一个新值,赋值给argList
.[[Call]]
internal method of func, providing thisArg as the this value and argList as the list of arguments.(这个话真的很差翻译,感受仍是原文的语句更加贴切)正如上面的定义所知,func.call
的内部实现,都是基于[[Call]]
的操做来实现。也就是说,函数调用的核心就是**[[Call]]**的实现。可是这个方法的实现方式和func.call
的处理过程是同样的。因此,经过类比来模拟出函数的调用过程。