从面试题谈谈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

以上就是两道关于原型,作用域闭包,变量提升,以及运算符优先级的问题

posted @ 2017-07-20 21:49  百兽凯多  阅读(390)  评论(0编辑  收藏  举报