js预解析
js引擎在代码正式执行之前会做一个与处理的工作:
1、收集变量
2、收集函数
依据:
将变量通过var提前到当前作用域最前面声明但不赋值var username = undefined
函数提前到当前作用域最前面定义
1 console.log(username); //undefined,不会报错,因为会提前声明 2 var username = 'shelly'; 3 console.log(username) //shelly 4 5 fun(); //输出fun(),不会报错,因为函数会被就提前定义 6 function fun(){ 7 console.log('fun()'); 8 }
上边的代码经过预解析之后就是这样
1 var username; 2 function fun(){ 3 console.log('fun()'); 4 } 5 console.log(username); //undefined,不会报错,因为会提前声明 6 username = 'shelly'; 7 console.log(username) //shelly 8 fun(); //输出fun(),不会报错,因为函数会被就提前定义
案例一:
1 function Foo(){ 2 getName = function(){ alert(1);}; 3 return this; 4 } 5 Foo.getName = function(){ alert(2);}; 6 Foo.prototype.getName = function(){ alert(3);}; 7 var getName = function(){ alert(4);}; 8 function getName(){ alert(5);}; 9 10 // 请写出下列的输出结果 11 Foo.getName(); 12 getName(); 13 Foo().getName(); 14 getName(); 15 new Foo.getName(); 16 new Foo().getName(); 17 new new Foo().getName();
按代码执行顺序,先进行预解析,即变量和函数的提升。
1 function Foo(){ 2 getName = function(){ alert(1);}; 3 return this; 4 } 5 var getName; //该getName会被第六行的getName覆盖 6 function getName(){ alert(5);}; //该getName会被第9行的getName覆盖 7 Foo.getName = function(){ alert(2);}; 8 Foo.prototype.getName = function(){ alert(3);}; 9 getName = function(){ alert(4);};
解析完后的代码
1 function Foo(){ 2 getName = function(){ alert(1);}; 3 return this; 4 } 5 Foo.getName = function(){ alert(2);}; 6 Foo.prototype.getName = function(){ alert(3);}; 7 getName = function(){ alert(4);};
第一问:Foo.getName(); //2
找到Foo这个构造函数(也是个对象),调用它的静态方法
第二问:getName(); //4
预解析后 getName = function(){ alert(4);};
第三问:Foo().getName(); //1
调用Foo这个构造函数,调用就会执行Foo内部的代码,该局部作用域也会进行预解析,执行到getName时,发现他没有进行声明,就会去全局下找有没有这个getName,
如果没有就声明一个,如果有就会重新赋值。所以此时,getName = function(){ alert(1);};
return this, 函数内部的this具体指谁,要看调用它的是谁就是看.前面是谁。Foo()其实省略了window.Foo();因此this指向window
最后变成了window.getName(); 也就是被重新赋值后的getName 。是1
第四问: getName(); //1
经过第三问的分析,此时window.getName = function(){ alert(1);};
第五问: new Foo.getName(); //2
整理下优先级:new (Foo.getName()); 先调用Foo的静态方法,就变成 new (function(){ alert(2);};)()
了解下new过程会做些什么
1、创建空对象;var obj = {};
2、设置新对象的constructor属性为构造函数的名称,设置新对象的__proto__属性指向构造函数的prototype对象;
obj.__proto__ = ClassA.prototype;
3、使用新对象调用函数,函数中的this被指向新实例对象:
ClassA.call(obj); //{}.构造函数(); 执行构造函数内的代码,并把this指向obj
4、返回新对象obj
总而言之,new的过程会执行一次函数内的代码,也就是弹出个2
第六问: new Foo().getName(); //3
整理下优先级 (new Foo()).getName(); new Foo()会得到一个Foo的实例对象,再调用实例对象上的getName方法,
实例对象上的方法会先搜索实例本身看有没有该方法,没有则搜索原型上有没有,没有才会搜索构造函数本身。明显,原型上有,所以会弹出3
第七问: new new Foo().getName(); //3
整理下优先级:new ((new Foo()).getName());参照第五问和第六问的原理,会弹出3