关键词: 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