面试中经常碰到这种题目,主要考察面试者对变量定义提升、this 指针指向、运算符优先级、原型、继承、全局变量污染、对象属性及原型属性优先级等知识的理解
// q1
function Foo() {
getName = function() {
alert(1);
};
return this;
}
// q2
Foo.getName = function() {
alert(2);
};
// q3
Foo.prototype.getName = function() {
alert(3);
};
// q4
var getName = function() {
alert(4);
};
// q5
function getName() {
alert(5);
}
// 请写出输出结果
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();
分析代码: q1: 定义一个叫 Foo 函数 q2: 为 Foo 创建一个叫 getName 静态属性,存储一个匿名函数 q3: 为 Foo 原型对象上创建一个叫 getName 的匿名函数 q4: 函数表达式创建一个 getName 函数 q5: 声明一个 getName 函数
第一题Foo.getName()
直接访问 Foo 上的静态属性,直接调用 q2,输出 2
第二题getName();
考察函数提升,函数生明在运行时会提升到作用域最前面,函数表达式则是在运行时赋值 所以在运行时 q5 函数会提升,而运行到函数表达式 q4 时,会给生明提前的 q5 的 getName 赋值为 q4,输出 4
第三题Foo().getName();
考察变量作用域和 this 指向问题
先执行了 Foo 函数,然后调用 Foo 函数的返回值对象的 getName 属性函数。
Foo 函数的第一句 getName = function () { alert (1); };是一句函数赋值语句,注意它没有 var 声明,所以先向当前 Foo 函数作用域内寻找 getName 变量,没有。再向当前函数作用域上层,即外层作用域内寻找是否含有 getName 变量,找到了,也就是第二问中的 alert(4)函数,将此变量的值赋值为 function(){alert(1)}。
this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定 this 到底指向谁,实际上 this 的最终指向的是那个调用它的对象,可以理解为window.Foo().getName();->window.getName(),所以最终会输出 1
第四题getName();
直接调用 getName 函数,相当于 window.getName(),因为这个变量已经被 Foo 函数执行时修改了,遂结果与第三问相同,为 1,也就是说 Foo 执行后把全局的 getName 函数给重写了一次,所以结果就是 Foo()执行重写的那个 getName 函数
第五题new Foo.getName();
考察的是 JS 的运算符优先级问题,点的优先级比 new 无参数列表优先级高,当点运算完后又因为有个括号(),此时就是变成 new 有参数列表,所以直接执行 new getName 函数作为了构造函数来执行,输出 2 Foo()执行重写的那个 getName 函数
第六题new Foo().getName();
首先 new 有参数列表跟点的优先级是同级,同级的话按照从左向右的执行顺序,所以先执行 new 有参数列表再执行点的优先级,最后再函数调用,相当于(new Foo()).getName()
由于new Foo()返回的是 this,而 this 在构造函数中本来就代表当前实例化对象,最终 Foo 函数返回实例化对象。之后调用实例化对象的 getName 函数,因为在 Foo 构造函数中没有为实例化对象添加任何属性,当前对象的原型对象(prototype)中寻找 getName 函数。输出 3
第七题new new Foo().getName();
同样是运算符优先级问题,这是已经要崩溃了,new 有参数列表遇到 new 有参数列表,从左向右执行,相当于 new ((new Foo()).getName)(),先初始化 Foo 的实例化对象,然后将其原型上的 getName 函数作为构造函数再次 new,最终输出 3