目录
一:调用位置
1、如何查找函数的调用位置
看下面代码例子
// 找到函数的调用位置
function baz() {
debugger
// 当前调用栈是:baz
// 因此当前的调用位置是全局作用域
console.log("baz");
bar(); // bar的调用位置
}
function bar() {
debugger
// 当前调用栈是baz -> bar
// 因此,当前调用位置在baz中
console.log("bar");
foo(); // foo的调用位置
}
function foo() {
debugger
// 当前调用栈是baz -> bar -> foo
// 因此,当前调用位置在bar中
console.log("foo");
}
baz(); // baz的调用位置
这样查找是可以的,但是比较麻烦,我们用debugger的方式来查找会比较好。如下
在这个调用栈中,可以看到函数的调用位置,就是调用栈的第二个元素。
二:绑定规则
函数执行过程中调用位置如何决定this的绑定对象?
来看看下面的绑定规则
1、默认绑定
默认绑定是指函数没有任何引用进行调用的情况下运行,比如下面这个例子
function foo() {
console.log(this.a)
}
var a = 12;
foo(); // 12
函数foo是直接运行的,由于其调用位置的作用域是全局作用域,所以this就指向了全局window。
但是如果是在严格模式下this无法绑定到window。
2、隐式绑定
如果函数的调用位置有上下文对象(或者说被某个对象拥有着)的时候,函数中的this会被绑定到这个对象中。例子:
function foo() {
console.log(this.a)
}
var obj = {
a: 21,
foo: foo
}
obj.foo(); // 21
例子中,foo函数被调用的时候,是被obj对象所拥有/包含着的,所以函数的this被绑定到了obj对象上。
2.1、隐式丢失
先看一个例子
function foo() {
console.log(this.a)
}
var obj = {
a: 21,
foo: foo
}
function foo2(fn) {
fn()
}
var a = 'global';
foo2(obj.foo) // global
这个例子中,foo2函数的参数也是一个函数,而在foo2的实际调用位置中的实参(obj.foo),它的引用还是foo函数,实际上是将foo函数放在foo2函数中运行而已,当foo2函数调用的时候,由于是在全局作用域下调用,所以它的this指向了全局window,所以最后输出了global。obj.foo的值,是foo函数。
3、显式绑定
call 和 apply,这两个方法是可以修改函数的this指向的,并且是显而易见的。例子:
function foo() {
console.log(this.a)
}
var obj = {
a: 10
}
foo(); // undefined
foo.call(obj); // 10
这个例子中,如果foo函数不调用call方法,直接运行的话,this是默认绑定到全局作用域上的,但是使用call方法来显式绑定this的指向,foo的this指向就被绑定到了obj对象上了。
apply的使用方法和call基本一样,第一个参数都是绑定this的对象,第二个参数是它们唯一不同的地方,具体有什么不一样,可以看这篇文章。
3.1、硬绑定
例子
function foo() {
console.log(this.a)
}
var obj = {
a: 10,
foo: foo
}
var bar = function() {
foo.call(obj);
}
var test = bar;
test(); // 10
test.call(window); // 10
可以看到,当在bar函数内部将foo的this绑定到obj对象上,之后无论如何调用bar函数,foo的this总是指向到obj对象的,这种绑定是一种显示的强制绑定。
另外一种硬绑定方法
例子:
function foo(arg) {
console.log(this.a, arg)
return this.a + arg;
}
// 辅助函数
function bind(fn, obj) {
return function() {
return fn.apply(obj, arguments)
}
}
var obj = {
a: 2
}
var bar = bind(foo, obj);
var b = bar(3); // 2 3
console.log(b) // 5
这个例子中,先看 var bar = bind(foo, obj);这一行代码执行过后,实际上bar是等于
/*
function () {
return foo.apply(obj, arguments)
}
*/
此时的bar,是不是很像上一个硬绑定的例子,在函数里边对foo函数进行显示绑定,这里也就是说,无论现在你怎么执行bar函数,this都被绑定到了obj上面了。
所以,当bar(3)执行的时候(实际上是运行foo函数),this指向obj,所以输出了一个2,因为传入了一个参数:3,所以最后是输出2和3。
而b是bar函数(实际上是foo函数)运行之后的一个返回值,2 + 3 = 5。
这种实现方式就是ES5的内置方法Function.prototype.bind的实现原理,下面是内置函数bind的用法:
function foo(arg) {
console.log(this.a, arg)
return this.a + arg;
}
var obj = {
a: 2
}
var bar = foo.bind(obj);
var b = bar(3); // 2 3
console.log(b) // 5
bind方法实际上是返回了一个 包含着 原始函数的硬绑定 的函数,它会把你指定的参数设置为this的上下文对象,并且调用原始函数。
4、new绑定
例子
function foo() {
this.f1 = 'test';
}
var b = new foo();
console.log(b.f1); // test
使用new来调用foo()时,会构造一个新对象,并把它绑定到foo()调用中的this上。new是最后一种可以影响函数调用时this绑定行为的方法。
使用new操作符调用一个函数的时候,会执行下面4个操作:
1、创建一个新的对象
2、将构造函数的作用域赋值给新对象(因此,this就指向了这个新对象)
3、运行构造函数的代码
4、返回这个新对象