深入之执行上下文,深入之call和apply的模拟实现

JavaScript 深入之执行上下文

2017/05/18 · JavaScript
·
执行上下文

原文出处: 冴羽   

JavaScript 深入之call和apply的模拟实现

2017/05/25 · JavaScript
· apply,
call

原文出处: 冴羽   

JavaScript 深入之bind的模拟实现

2017/05/26 · JavaScript
· bind

原文出处: 冴羽   

前言

在《JavaScript深入之执行上下文栈》中讲到,当JavaScript代码执行一段可执行代码(executable
code)时,会创建对应的执行上下文(execution context)。

对于每个执行上下文,都有三个重要属性:

  • 变量对象(Variable object,VO)
  • 作用域链(Scope chain)
  • this

然后分别在《JavaScript深入之变量对象》、《JavaScript深入之作用域链》、《JavaScript深入之从ECMAScript规范解读this》中讲解了这三个属性。

阅读本文前,如果对以上的概念不是很清楚,希望先阅读这些文章。

因为,这一篇,我们会结合着所有内容,讲讲执行上下文的具体处理过程。

call

一句话介绍 call:

call() 方法在使用一个指定的 this
值和若干个指定的参数值的前提下调用某个函数或方法。

举个例子:

var foo = { value: 1 }; function bar() { console.log(this.value); }
bar.call(foo); // 1

1
2
3
4
5
6
7
8
9
var foo = {
    value: 1
};
 
function bar() {
    console.log(this.value);
}
 
bar.call(foo); // 1

注意两点:

  1. call 改变了 this 的指向,指向到 foo
  2. bar 函数执行了

bind

一句话介绍 bind:

bind() 方法会创建一个新函数。当这个新函数被调用时,bind()
的第一个参数将作为它运行时的
this,之后的一序列参数将会在传递的实参前传入作为它的参数。(来自于 MDN
)

由此我们可以首先得出 bind 函数的两个特点:

  1. 返回一个函数
  2. 可以传入参数

思考题

在《JavaScript深入之词法作用域和动态作用域》中,提出这样一道思考题:

var scope = “global scope”; function checkscope(){ var scope = “local
scope”; function f(){ return scope; } return f(); } checkscope();

1
2
3
4
5
6
7
8
9
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope();

var scope = “global scope”; function checkscope(){ var scope = “local
scope”; function f(){ return scope; } return f; } checkscope()();

1
2
3
4
5
6
7
8
9
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
checkscope()();

两段代码都会打印’local
scope’。虽然两段代码执行的结果一样,但是两段代码究竟有哪些不同呢?

紧接着就在下一篇《JavaScript深入之执行上下文栈》中,讲到了两者的区别在于执行上下文栈的变化不一样,然而,如果是这样笼统的回答,依然显得不够详细,本篇就会详细的解析执行上下文栈和执行上下文的具体变化过程。

模拟实现第一步

那么我们该怎么模拟实现这两个效果呢?

试想当调用 call 的时候,把 foo 对象改造成如下:

var foo = { value: 1, bar: function() { console.log(this.value) } };
foo.bar(); // 1

1
2
3
4
5
6
7
8
var foo = {
    value: 1,
    bar: function() {
        console.log(this.value)
    }
};
 
foo.bar(); // 1

这个时候 this 就指向了 foo,是不是很简单呢?

但是这样却给 foo 对象本身添加了一个属性,这可不行呐!

不过也不用担心,我们用 delete 再删除它不就好了~

所以我们模拟的步骤可以分为:

  1. 将函数设为对象的属性
  2. 执行该函数
  3. 删除该函数

以上个例子为例,就是:

// 第一步 foo.fn = bar // 第二步 foo.fn() // 第三步 delete foo.fn

1
2
3
4
5
6
// 第一步
foo.fn = bar
// 第二步
foo.fn()
// 第三步
delete foo.fn

fn 是对象的属性名,反正最后也要删除它,所以起成什么都无所谓。

根据这个思路,我们可以尝试着去写第一版的 call2 函数:

// 第一版 Function.prototype.call2 = function(context) { //
首先要获取调用call的函数,用this可以获取 context.fn = this;
context.fn(); delete context.fn; } // 测试一下 var foo = { value: 1 };
function bar() { console.log(this.value); } bar.call2(foo); // 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 第一版
Function.prototype.call2 = function(context) {
    // 首先要获取调用call的函数,用this可以获取
    context.fn = this;
    context.fn();
    delete context.fn;
}
 
// 测试一下
var foo = {
    value: 1
};
 
function bar() {
    console.log(this.value);
}
 
bar.call2(foo); // 1

正好可以打印 1 哎!是不是很开心!(~ ̄▽ ̄)~

返回函数的模拟实现

从第一个特点开始,我们举个例子:

var foo = { value: 1 }; function bar() { console.log(this.value); } //
返回了一个函数 var bindFoo = bar.bind(foo); bindFoo(); // 1

1
2
3
4
5
6
7
8
9
10
11
12
var foo = {
    value: 1
};
 
function bar() {
    console.log(this.value);
}
 
// 返回了一个函数
var bindFoo = bar.bind(foo);
 
bindFoo(); // 1

关于指定 this 的指向,我们可以使用 call 或者 apply 实现,关于 call 和
apply
的模拟实现,可以查看《JavaScript深入之call和apply的模拟实现》。我们来写第一版的代码:

// 第一版 Function.prototype.bind2 = function (context) { var self =
this; return function () { self.apply(context); } }

1
2
3
4
5
6
7
8
// 第一版
Function.prototype.bind2 = function (context) {
    var self = this;
    return function () {
        self.apply(context);
    }
 
}

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注