从面试题谈谈js的闭包,原型
最近群里有小伙伴分享了两道面试题,这里我谈谈自己的理解,废话不多说,上第一题:
var n = 10; var obj = { n:20, fn:(function(){ this.n += 2; n *= 3; return function(){ this.n *= 2; n += 1; console.log(n) } })(n) } var fn = obj.fn; fn(); obj.fn() console.log(n,obj.n)
这个题目中,定义的obj对象的fn属性是个自执行的函数,任何自执行的函数,其内部this指向全局变量window,所以定义对象obj时,全局变量n由于自执行函数的影响,加上自执行函数内部没有定义n,所以会执行window.n*=2,接着执行window.n+=3,此时全局变量n已经被修改为36;
接下来
var fn = obj.fn; fn();
这两步其实就是在全局空间定义一个变量fn,赋值为obj的fn属性,也就是那个自执行函数,然后再执行它本身,此时由于自执行函数返回的函数(闭包)中没有定义n,会向上一级作用域查找,就是全局变量n,之前说过,已经被修改成36,所以在执行时
this.n *= 2; //36*2=72 n += 1; //72+1=73 console.log(n)
fn()执行的结果就是打印73;
下面obj.fn(),fn()作为obj的方法执行,其this值指向obj本身,此时自执行函数返回的函数(闭包)中,this.n就修改了obj.n,使其值变为40,因为返回的函数没有定义n,会向上一级作用域查找,就是全局变量n,所以n+1=73+1=74,所以打印结果为74;
再接着打印全局变量n,值为74,obj.n上一步说了,是40。
ok,继续说下一道题目:
function Foo(){ getName=function(){alert(1);} return this; } Foo.getName=function(){alert(2);} Foo.prototype.getName=function(){alert(3);} var getName=function(){alert(4);} function getName(){alert(5);} Foo.getName(); getName(); Foo().getName(); getName(); new Foo.getName(); new Foo().getName(); new new Foo().getName();
首先看这个题目开始做了些什么事情:首先定义了一个Foo函数,之后为Foo创建了一个名为getName的静态属性,并存储了一个匿名函数,之后为Foo的原型对象创建了一个叫getName的匿名函数。之后又通过函数变量表达式创建了一个getName的函数,最后再声明一个叫getName函数。
1.Foo.getName()
Foo.getName 访问Foo函数上存储的静态属性,即为2。
2.getName()
首先要知道的是,所有声明变量或声明函数都会被提升到当前函数的顶部,而函数表达式不会提前,所以题目的代码实际是下面这个样子
function Foo() { getName = function () { alert (1); }; return this; } var getName;//提升变量声明 function getName() { alert (5);}//提升函数声明,覆盖var的声明 Foo.getName = function () { alert (2);}; Foo.prototype.getName = function () { alert (3);}; getName = function () { alert (4);};//赋值再次覆盖function getName声明 getName();//最终结果输出4
3.Foo().getName()
Foo().getName(); 先执行Foo函数,然后调用Foo函数的返回值this的getName属性函数。Foo函数的第一句 getName = function () { alert (1); }; 是一句函数赋值语句,它没有var声明,所以先向当前Foo函数作用域内寻找getName变量声明,没有找到。继续向当前函数作用域上层,即外层作用域内寻找是否含有getName变量,找到了,也就是第二问中的alert(4)函数,将此变量的值赋值为 function(){alert(1)}。Foo函数返回的this是window对象,相当于执行 window.getName() ,而window中的getName已经被修改为alert(1),所以最终会输出1
4.getName()
直接调用getName函数,相当于执行 window.getName() ,因为这个变量已经被第三问Foo函数执行时修改了,所以结果与第三问相同,为1
5.
new Foo.getName(); new Foo().getName(); new new Foo().getName();
关于以上三个含有new操作符的式子,一起说一下,这里主要是js中运算符优先级的问题,其实执行顺序依次如下
new (Foo.getName)(); //点号优先级高于new,将getName函数作为了构造函数来执行,遂弹出2 (new Foo()).getName(); //括号优先级高于new,先执行Foo函数,此时返回的是this,而this在构造函数中本来就代表当前实例化对象,所以最终Foo函数返回实例化对象。
之后调用实例化对象的getName函数,因为在Foo构造函数中没有为实例化对象添加任何属性,遂到当前对象的原型对象(prototype)中寻找getName
,找到了,是3 new ((new Foo()).getName)(); //先初始化Foo的实例化对象,然后将其原型上的getName函数作为构造函数再次new。 最终结果为3
以上就是两道关于原型,作用域闭包,变量提升,以及运算符优先级的问题