Home > Article > Web Front-end > Variable declaration promotion, prototype, this pointer
The question is as follows:
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();
The answer is:
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 question involves many knowledge points, including variable declaration promotion, this pointer pointing, operator priority, prototype, inheritance, global variable pollution, object attribute and prototype attribute priority etc.
This question contains 7 questions, please explain them below.
First question
Let’s first look at what was done in the first half of this question. First, we defined a function called Foo. Then we created a static property called getName for Foo and stored an anonymous function. Then we created the prototype of Foo. The object has a newly created anonymous function called getName. Then a getName function is created through function variable expression, and finally a getName function is declared.
The first question about Foo.getName is naturally to access the static properties stored on the Foo function, which is naturally 2. There is nothing to say.
Second question
Second question, call the getName function directly. Since it is called directly, it is accessing the function called getName in the current scope above, so it has nothing to do with 1 2 3. There are two pitfalls here, one is variable declaration promotion, and the other is function expression.
Variable declaration promotion
That is, all declared variables or declared functions will be promoted to the top of the current function.
For example, the following code:
console.log(‘x’ in window);//true var x;
x = 0;
When the code is executed, the js engine will raise the declaration statement to the top of the code and become:
var x; console.log(‘x’ in window);//true x = 0;
Function expression
var getName and function getName are both declaration statements, the difference is that var getName is Function expression , and function getName is a function declaration .
The biggest problem with function expressions is that js will split this code into two lines of code and execute them separately.
For example, the following code:
console.log(x);//输出:function x(){} var x=1; function x(){}
The actual executed code is: first split var x=1 into two lines: var x; and x = 1;, and then upgrade the two lines var x; and function x(){} At the top it becomes:
var x; function x(){} console.log(x); x=1;
So the x declared by the final function covers the x declared by the variable, and the log output is the x function.
Similarly, the final execution of the code in the original question is:
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
The third question
The third question Foo().getName(); first executes the Foo function, and then calls the return value object of the Foo function getName attribute function.
The first sentence of the Foo function getName = function () { console.log(1); }; is a function assignment statement. Note that it does not have a var declaration, so first look for the getName variable in the current Foo function scope, and there is none. Then look to the upper layer of the current function scope, that is, the outer scope, to see if it contains the getName variable. We found it, which is the alert(4) function in the second question. Assign the value of this variable to function(){alert(1) }.
The getName function in the outer scope is actually modified here.
Note: If it is still not found here, it will search all the way up to the window object. If there is no getName attribute in the window object, create a getName variable in the window object.
之后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 {}
原题中,返回的是this,而this在构造函数中本来就代表当前实例化对象,遂最终Foo函数返回实例化对象。
之后调用实例化对象的getName函数,因为在Foo构造函数中没有为实例化对象添加任何属性,遂到当前对象的原型对象(prototype)中寻找getName,找到了。
遂最终输出3。
第七问
第七问, new new Foo().getName(); 同样是运算符优先级问题。
最终实际执行为:
new ((new Foo()).getName)();
先初始化Foo的实例化对象,然后将其原型上的getName函数作为构造函数再次new。
遂最终结果为3