记录一道经典面试题

118 阅读2分钟

关键词: new,this,变量声明,函数,变量/函数提升,运算符优先级,原型

关于this的绑定,可以参考这篇文章:深入理解 js this 绑定 ( 无需死记硬背,尾部有总结和面试题解析 )

题目

  function foo() {
    getName = function () {
      console.log(1);
    };
    return this;
  }
  
  foo.getName = function () {
    console.log(2);
  };
  
  foo.prototype.getName = function () {
    console.log(3);
  };
  
  var getName = function () {
    console.log(4);
  };
  
  function getName() {
    console.log(5);
  }

  foo.getName(); // ?
  getName(); // ?
  foo().getName(); // ?
  getName(); // ?
  new foo.getName(); // ?
  new foo().getName(); // ?
  new new foo().getName(); // ?

解析

1. 代码在编译阶段进行变量/函数提升后的形式如下

 1   // 函数会被提升至作用域最顶部
 2   function foo() {
 3    // 这里需要注意:执行本函数时,getName会称为全局变量,
 4    // 因为函数内没有用关键字(var/let/const)声明的变量都会变为全局变量
 5    getName = function () {
 6      console.log(1);
 7    };
 8    return this;
 9  }
10  
11  // 这里的函数声明会被后面的函数表达式覆盖
12  // function getName() {
13  //   console.log(5);
14  // }
15  
16  // var getName; // 这个声明没有用,因为与上面的函数声明同名,编译器会忽略同名的变量声明
17  
18  // foo函数本身为一个对象,这里给foo增加一个getName属性,其值为一个函数
19  foo.getName = function () {
20    console.log(2);
21  };
22
23  // foo原型增加一个getName属性,值为一个函数
24  foo.prototype.getName = function () {
25    console.log(3);
26  };
27
28  // 覆盖上面的第二个函数
29  getName = function () {
30    console.log(4);
31  };

2. foo.getName()

查询foo对象的getName属性并执行,打印2第19行

3. getName()

全局作用域中,找到getName并执行,打印4第29行

4. foo().getName()

先执行foo(),结果返回this,由于foo()前面没有被一个对象被调用,因此this为默认绑定,代表全局变量window。同时,getName(第29行)会被重新赋值(第5行),所以相当于执行window.getName(),打印1第5行

5. getName()

上一步已经将getName重新赋值了,也会执行window.getName(),打印1第5行

下面的问题涉及到运算符的优先级问题,在文章最后有说明。

6. new foo.getName()

等价于new (foo.getName()),打印2,(第19行

7. new foo().getName()

等价于(new foo()).getName()

.前面的(new foo()),foo被当作构造函数创建一个新对象(给它取个名字:fo),return this第8行)返回的是新对象fo本身。fo.getName()当然会去原型链上找,正好(第24行)给foo原型上增加了getName,所以fo.getName()执行会打印3

8. new new foo().getName()

等价于new ((new foo()).getName()),打印3

优先级

运算符优先级执行顺序
.20从左向右
new ...(...) 带括号20-
func() 函数执行20从左向右
new ... 不带括号19从右向左

运算符优先级详细见:运算符优先级-MDN