此部份以一道题目来做诠释
题目以下:
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();
答案是:
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();//2
getName();//4
Foo().getName();//1
getName();//1
new Foo.getName();//2
new Foo().getName();//3
new new Foo().getName();//3
此题触及的知识点浩瀚,包括变量声明提拔、this指针指向、运算符优先级、原型、继续、全局变量污染、对象属性及原型属性优先级等等。
此题包括7小问,离别说下。
第一问
先看此题的上半部份做了什么,起首定义了一个叫Foo的函数,以后为Foo建立了一个叫getName的 静态属性 存储了一个匿名函数,以后为Foo的 原型对象 新建立了一个叫getName
的匿名函数。以后又经由过程 函数变量表达式 建立了一个getName
的函数,末了再 声明 一个叫getName
函数。
第一问的 Foo.getName
自然是接见Foo函数上存储的静态属性,自然是2,没什么可说的。
第二问
第二问,直接挪用 getName 函数。既然是直接挪用那末就是接见当前上文作用域内的叫getName的函数,所以跟1 2 3都没什么关系。此处有两个坑,一是变量声明提拔,二是函数表达式。
变量声明提拔
即一切声明变量或声明函数都邑被提拔到当前函数的顶部。
例以下代码:
console.log(‘x’ in window);//true
var x;
x = 0;
代码实行时js引擎会将声明语句提拔至代码最上方,变成:
var x;
console.log(‘x’ in window);//true
x = 0;
函数表达式
var getName
与function getName
都是声明语句,区分在于 var getName
是 函数表达式 ,而 function getName
是 函数声明 。关于JS中的种种函数建立体式格局能够看 大部份人都邑做错的典范JS闭包面试题 这篇文章有细致申明。
函数表达式最大的题目,在于js会将此代码拆分为两行代码离别实行。
例以下代码:
console.log(x);//输出:function x(){}
var x=1;
function x(){}
现实实行的代码为,先将 var x=1
拆分为 var x;
和 x = 1;
两行,再将 var x;
和 function x(){}
两行提拔至最上方变成:
var x;
function x(){}
console.log(x);
x=1;
所以终究函数声明的x掩盖了变量声明的x,log输出为x函数。
同理,原题中代码终究实行时的是:
function Foo() {
getName = function () { console.log(1); };
return this;
}
var getName;//只提拔变量声明
function getName() { console.log(5);}//提拔函数声明,掩盖var的声明
Foo.getName = function () { console.log(2);};
Foo.prototype.getName = function () { console.log(3);};
getName = function () { console.log(4);};//终究的赋值再次掩盖function getName声明
getName();//终究输出4
第三问
第三问的 Foo().getName();
先实行了Foo
函数,然后挪用Foo
函数的返回值对象的getName
属性函数。
Foo
函数的第一句 getName = function () { console.log(1); };
是一句函数赋值语句,注重它没有var声明,所以先向当前Foo函数作用域内寻觅getName变量,没有。再向当前函数作用域上层,即外层作用域内寻觅是不是含有getName变量,找到了,也就是第二问中的alert(4)
函数,将此变量的值赋值为 function(){alert(1)}
。
此处现实上是将外层作用域内的getName
函数修改了。
注重:此处若依旧没有找到会一向向上查找到
window
对象,若window
对象中也没有getName
属性,就在window对象中建立一个getName
变量。
以后Foo
函数的返回值是this
,而JS的this
题目博客园中已有异常多的文章引见,这里不再多说。
简朴的讲, this的指向是由地点函数的挪用体式格局决议的 。而此处的直接挪用体式格局,this
指向window
对象。
遂Foo
函数返回的是window
对象,相称于实行 window.getName()
,而window
中的getName已被修改成alert(1)
,所以终究会输出1
此处考核了两个知识点,一个是变量作用域题目,一个是this
指向题目。
第四问
直接挪用getName
函数,相称于 window.getName()
,由于这个变量已被Foo
函数实行时修改了,遂效果与第三问雷同,为1
第五问
第五问 new Foo.getName();
,此处考核的是js的运算符优先级题目。
js运算符优先级:
参考链接: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Operator_Precedence
经由过程查上表能够得知点.
的优先级高于new
操纵,遂相称因而:
new (Foo.getName)();
所以现实大将getName
函数作为了组织函数来实行,遂弹出2。
第六问
第六问 new Foo().getName()
,起首看运算符优先级()
高于new
,现实实行动
(new Foo()).getName()
遂先实行Foo
函数,而Foo
此时作为组织函数却有返回值,所以这里须要申明下js中的组织函数返回值题目。
组织函数的返回值
在传统言语中,组织函数不该该有返回值,现实实行的返回值就是此组织函数的实例化对象。
而在js中组织函数能够有返回值也能够没有。
1、没有返回值则根据其他言语一样返回实例化对象。
function F(){}
new F()
//>F {}
2、如有返回值则搜检其返回值是不是为 援用范例 。假如黑白援用范例,如基础范例(string
,number
,boolean
,null
,undefined
)则与无返回值雷同,现实返回其实例化对象。
function F(){return 1;}
new F()
//>F {}
3、若返回值是援用范例,则现实返回值为这个援用范例。
function F(){return {a:1};}
new F()
//>Object {a: 1}
原题中,返回的是this
,而this
在组织函数中本来就代表当前实例化对象,遂终究Foo
函数返回实例化对象。
以后挪用实例化对象的getName
函数,由于在Foo
组织函数中没有为实例化对象增加任何属性,遂到当前对象的原型对象(prototype
)中寻觅getName
,找到了。
遂终究输出3。
第七问
第七问, new new Foo().getName();
同样是运算符优先级题目。
终究现实实行动:
new ((new Foo()).getName)();
先初始化Foo
的实例化对象,然后将其原型上的getName
函数作为组织函数再次new
。
遂终究效果为3