new运算符和变量提升详解:一道多考点的面试题
话不多说 , 先看题 .
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();
你需要的知识:
- 作用域
- new的用法
- new的优先级
- 原型链
- 变量提升
关于 作用域,原型链 的详尽知识小伙伴们要是还是没有摸透, 赶紧回去看高程啦~这里主要讲一下其他几个比较容易忽视的知识点.
new运算符
new运算符具体干了什么?发现其实很简单,就干了三件事情.
var obj = {};
obj.__proto__ = F.prototype;
F.call(obj);
第一行,我们创建了一个空对象obj;
第二行,我们将这个空对象的__proto__成员指向了F函数对象prototype成员对象;
第三行,我们将F函数对象的this指针替换成obj,然后再调用F函数.
我们可以这么理解: 以 new 操作符调用构造函数的时候,函数内部实际上发生以下变化:
1、创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型。
2、属性和方法被加入到 this 引用的对象中。
3、新创建的对象由 this 所引用,并且最后隐式的返回 this.
new运算符优先级
MDN运算符优先级 这里只截下来new的优先级 , 其他的太长了 可以自行复习哦.
我们看到new运算符的优先级是很高的 , 而且带参数和无参数是有区别的 , 但是 "."的优先级是比 "new"高
那好我们先分析后面三个表达式
new Foo.getName();
new Foo().getName();
new new Foo().getName();
秒解方法: new匹配最近的函数执行的括号 , 如同左括号匹配右括号一样 .
new Foo.getName()//Foo.getName这个函数在执行new
new Foo().getName();// Foo函数在执行new 如同(new Foo()).getName()
new new Foo().getName();// 先 Foo函数执行new 返回值的getName方法再执行new函数
//如同 new (new Foo()).getName()
变量提升
我们看看变量提升的规则 , 有两种提升
- 函数声明 如:
foo(){};
- 变量声明 如:
var a
= 1; 或var foo = function(){};
注意: 变量声明的第二个例子 , 它是属于变量声明的 .
具体是怎么提升呢?
-
变量声明: 把声明和赋值拆解 , 声明提升到作用域最前面 , 赋值保留在原位
-
函数声明: 把函数声明 如同剪切一般, 整个提升到作用域最前面(在变量声明后面).
其中 , 变量声明是先于函数声明的 看一个例子你就明白
var getName = function(){
console.log(2);
}
function getName (){
console.log(1);
}
getName();//输出2
经过拆解 它应该是这样的
var getName;//拆解第一步:变量提升至最前面
function getName (){
console.log(1);
} //声明函数整块剪切
getName = function(){
console.log(2);
}//拆解第二步:赋值,把函数的声明覆盖了
getName();//输出2
注意: if 中变量声明也同样会提升 , 函数声明是不会提升的
函数的形参是一种特殊的情况 , 函数形参在声明时 , 就已经完成了对参数的赋值 . 这个赋值是在声明函数变量提升之前的 .
function fn(n) {
function n(){
console.log(2);
} ;
n() //
}
fn();//虽然没传参但是不报错 , 打印出来 2 .
fn(1);// 2
fn(()=>{console.log(1)}) //2
准备工作做好了
开始解题
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();
先解决变量提升
var getName;
function Foo(){
getName = function{alert(1);}
return this;
}
function getName(){alert(5);}
Foo.getName = function(){alert(2);}
Foo.prototype.getName = function(){alert(3)}
getName = function(){alert(4);};
Foo.getName();//2
getName();//4
Foo().getName();//1
//Foo函数 `return this` this指向什么? 因为Foo在全局作用域声明 , 所以它是由`window`调用
//在非严格模式下 , this指向 `window` , 就是在执行`window.getName()`
//但是注意了这里有个小坑 , Foo执行中把全局的getName函数更改了
//因为内部没有定义getName函数, 所以向作用域链找,找到了全局的的getName并更改了.所以此处是1不是4
getName();//1
new Foo.getName();//2
new Foo().getName();//1 3 new Foo() 返回 this 是一个实例 , 这个实例找到了原型链上的getName方法
new new Foo().getName();//1 3 同第六个