frontend-notes

This

从底层原理分析

JavaScript深入之从ECMAScript规范解读this, 下面梳理下这篇文章的一些知识点:

ECMAScript 5.1 规范

ECMAScript 的类型分为语言类型和规范类型。

没懂?没关系,我们只要知道在 ECMAScript 规范中还有一种只存在于规范中的类型,它们的作用是用来描述语言底层行为逻辑。

今天我们要讲的重点是便是其中的 Reference 类型。它与 this 的指向有着密切的关联。

Reference

要理解this,先要理解Reference,它与this的指向有密切的关系。Reference由三部分组成

而且规范中还提供了获取Reference组成部分的方法,比如 GetBaseIsPropertyReference。

规范中还有一个用于从Reference类型获取对应值的方法: GetValue

如何确定this值

  1. 计算 MemberExpression 的结果赋值给 ref MemberExpression就是()左边的部分 ```
    • PrimaryExpression // 原始表达式 可以参见《JavaScript权威指南第四章》
    • FunctionExpression // 函数定义表达式
    • MemberExpression [ Expression ] // 属性访问表达式
    • MemberExpression . IdentifierName // 属性访问表达式
    • new MemberExpression Arguments // 对象创建表达式 ```
  2. 判断 ref 是不是一个 Reference 类型
    • 判读方法: ```
      1. 如果 ref 是 Reference,并且 IsPropertyReference(ref) 是 true, 那么 this 的值为 GetBase(ref)
      2. 如果 ref 是 Reference,并且 base value 值是 Environment Record, 那么this的值为 ImplicitThisValue(ref)
      3. 如果 ref 不是 Reference,那么 this 的值为 undefined ```
    • 例子:
        var value = 1;
      
        var foo = {
        value: 2,
        bar: function () {
                return this.value;
            }
        }
      
        // 示例1
        console.log(foo.bar());
        // MemberExpression 为 foo.bar,foo.bar是一个Reference,将 foo.bar 赋值给 ref
        // IsPropertyReference(ref) 为 true,那么 this 的值为 GetBase(ref) ,而 GetBase 返回的是 reference 的 base value。
        // 所以 GetBase(ref) => GetBase(foo.bar) => foo.bar的base value => foo
        // 所以 this 为 foo !!!
        // 所以结果为 2
      
      
        //示例2
        console.log((foo.bar)());
        // () 并没有对 MemberExpression 进行计算,所以其实跟示例 1 的结果是一样的
        // 结果为 2
      
        //示例3
        console.log((foo.bar = foo.bar)());
        // 有赋值操作符,使用了 GetValue,所以返回的值不是 Reference 类型
        // this 为 undefined,非严格模式下,this 的值为 undefined 的时候,其值会被隐式转换为全局对象。
        // 结果为 1
      
        //示例4
        console.log((false || foo.bar)());
        // 同上,使用了 || 操作符, this 为 undefined
        // 结果为 1
      
        //示例5
        console.log((foo.bar, foo.bar)());
        // 同上,使用了 , 操作符, this 为 undefined
        // 结果为 1
      
        function foo() {
            console.log(this)
        }
      
        foo();
      
        // MemberExpression 是 foo,foo是一个Reference,将 foo 赋值给 ref
        // base value 是 EnvironmentRecord,则IsPropertyReference(ref)为false
        // 根据上面的判断方法2,this的值为 ImplicitThisValue(ref)
        // ImplicitThisValue 始终返回 undefined,所以 this 为 undefined
      

从调用场景分析

JavaScript 的 this 原理 - 阮一峰有内存数据结构和调用环境的讲解,比较简单易懂

call 和 apply

callapply都可以改变函数的执行上下文,调用 callapply 的对象,必须是一个函数 Function

call

特征:

使用场景

模拟实现

Function.prototype.call = function (context) {
  context = context || window;
  context.fn = this;

  let args = [...arguments].slice(1);
  let result = context.fn(...args);

  delete context.fn
  return result;
}

apply

特征:

使用场景

模拟实现

Function.prototype.apply = function (context, arr) {
    context = context || window;
    context.fn = this;

    let result;
    if (!arr) {
        result = context.fn();
    } else {
        result = context.fn(...arr);
    }

    delete context.fn
    return result;

bind

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

bind vs (call & apply) call()apply() 返回函数应该返回的值,bind() 返回一个经过硬绑定的新函数。 call()apply() 一经调用则立即执行函数,而 bind() 则只是完成了函数的 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 fNOP = function () {};

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
}

箭头函数

MDN

语法

高级语法

//支持剩余参数和默认参数
(param1, param2, ...rest) => { statements }
(param1 = defaultValue1, param2, …, paramN = defaultValueN) => {
statements }

//同样支持参数列表解构
let f = ([a, b] = [1, 2], {x: c} = {x: a + b}) => a + b + c;
f();  // 6

// 使用三元运算符
var simple = a => a > 15 ? 15 : a;

箭头函数与普通函数的区别

参考