原来是严格模式

808 阅读3分钟

先来看这样几段代码

function func() {
  function f() {
    return this
  }
  console.log(f())
  return f
}

func()()
let obj = {
  func() {
  	function f() {
      return this
    }
    console.log(f())
    return f
  }
}

obj.func()()
function T() {
	this.func = function() {
		function f() {
      return this
    }
    console.log(f())
    return f
	}
}

let obj = new T()
obj.func()()
function T() {}
T.prototype.func = function() {
  function f() {
    return this
  }
  console.log(f())
  return f
}

let obj = new T()
obj.func()()
class T {
  func() {
    function f() {
      return this
    }
    console.log(f())
    return f
  }
}

let obj = new T()
obj.func()()
class T {
  static func() {
    function f() {
      return this
    }
    console.log(f())
    return f
  }
}

T.func()()

一一运行后会发现:除了ES6 class写法的两个例子外,其余的例子,嵌套函数内部函数的this都是指向全局对象window,而ES6 class写法的两个例子中thisundefined

上述的几个例子展示了大部分函数调用的方法,我们想探究的是嵌套函数内部函数的this指向什么。

在探究之前我们已知如下知识点

  • 函数内部的this总是在运行时决定指向
  • 直接调用的函数或自执行函数的内部this指向全局对象

那么ES6的class例子的行为理应和其他例子保持一致,为什么会出现指向undefined的情况?那么一定是class内部有什么不同。

在搜索ES6 class的源码时发现这么一个仓库:里面有一个es6 class转换成普通函数书写的转码器

我们看到实际上class可以这样转义:

// Classes
class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  get name() {
    // Template strings
    return `${this.firstName} ${this.lastName}`;
  }

  toString() {
    return this.name;
  }
}

// Normal Function
var $__Object$defineProperties = Object.defineProperties;

var Person = function() {
  "use strict";

  function Person(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  $__Object$defineProperties(Person.prototype, {
    name: {
      get: function() {
        // Template strings
        return "" + this.firstName + " " + this.lastName + "";
      },

      enumerable: true,
      configurable: true
    },

    toString: {
      value: function() {
        return this.name;
      },

      enumerable: false,
      writable: true
    }
  });

  return Person;
}();

那么我们的例子转义后的结果将是:

var $__Object$defineProperties = Object.defineProperties;

// instance method
var T = function() {
  "use strict";

  function T() {}

  $__Object$defineProperties(T.prototype, {
    func: {
      value: function() {
        function f() {
          return this
        }
        console.log(f())
        return f
      },

      enumerable: false,
      writable: true
    }
  });

  return T;
}();

let obj = new T()
obj.func()()

// static method
var T = function() {
  "use strict";

  function T() {}

  $__Object$defineProperties(T, {
    func: {
      value: function() {
        function f() {
          return this
        }
        console.log(f())
        return f
      },

      enumerable: false,
      writable: true
    }
  });

  return T;
}();

T.func()()

实际上和我们上面普通function写法别无二致,唯一不同的就是这个"use strict"。首先正常在控制台执行两个转义后代码的例子,结果仍然是this指向undefined;而后我们尝试删掉"use strict"this确实又指向了window

原来一切都是因为严格模式啊。

关于严格模式的说明,大家可以参照阮老师Javascript 严格模式详解这篇文章,文章中提到的一点正解释了这个问题的现象。

image-20210227221640669

最后顺便简单总结一下使用严格模式后产生的影响:

  • 全局变量必须显式声明
  • 禁止使用with语句
  • 创设eval作用域
  • 禁止this关键字指向全局对象
  • 禁止在函数内部遍历调用栈,限制arguments的使用
  • 禁止删除变量
  • 对象属性、函数参数重名报错
  • function函数必须声明在顶层
  • 新增保留字