原型链,变量提升与覆盖,this指向一网打尽
作者:GrayRabbit 时间:2021.11.1
前端萌新第一次投稿,不足之处多多见谅。
问题:
下面的代码会打印什么?
function Foo() {
getName = function() {
console.log(0);
}
return this;
}
var getName = function() {
console.log(1);
}
Foo.getName = function() {
console.log(2);
}
function getName() {
console.log(3);
}
Foo.prototype.getName = function() {
console.log(4);
}
Foo.getName();
getName();
new Foo().getName();
getName();
Foo().getName();
答案:21400
有没有做对呢,下面是解析。
解析:
先看第一个函数调用
Foo.getName()
Foo此时当成对象看待,我们调用了这个对象的getName()
方法。首先看Foo自身有没有这个方法,如果没有就去原型链找
路径Foo->Function.prototype->Object.prototype->null
。箭头代表引用该对象的__proto__
属性。
在源代码中,这段代码给Foo加上了getName方法,
Foo.getName = function() {
console.log(2);
}
所以第一个函数调用打印了2。再看第二个函数调用
getName()
源代码中,共定义了两个getName()
,那么会调用哪个呢?我们知道var
和function
定义的函数都会被提升,但是function
定义的会被优先提升,然后后面的var
就变成了赋值,所以下面两个代码相同
原:
//.........
var getName = function() {
console.log(1);
}
//.........
function getName() {
console.log(3);
}
//.........
getName();
等价于
var getName = function() {
console.log(3);
}
var getName;
//...........
getName= function() {
console.log(1);
}
//...........
getName();
不难看出,会打印1,function
定义的函数被覆盖掉了。再看第三个
new Foo().getName()
这是一个Foo实例化的对象(下文称为foo),来调用自己的getName
方法,首先看foo自身有没有这个方法,如果没有就去原型链找
路径foo->Foo.prototype->Object.prototype->null
。箭头代表引用该对象的__proto__
属性。
显然我们可以看到第一个被访问到的getName
应该是
Foo.prototype.getName = function() {
console.log(4);
}
所以会打印4,再看第四个。
getName()
这个明明和第二个一样,为什么结果不一样呢,很简单,在上一句代码中new Foo()
调用了Foo
这个函数,而Foo
这个函数又全局定义了一个叫getName
的变量并赋值打印0的函数,覆盖了之前var
赋值的函数。
function Foo() {
getName = function() {
console.log(0);
}
return this;
}
所以现在getName()
会打印0。
Foo().getName()
全局作用域下,Foo()
返回的this
是global
对象,而上一句代码中的全局变量则会变为global
的一个方法。
所以也会打印0。
知识总结:
1.原型链:
一个函数的属性和方法既可以使用来自自己的,也可以使用继承自原型链的,除了Object.create(null)
创造的对象外,所有的对象都继承自Object.prototype
这个对象,当访问一个对象的属性或方法时,会按照原型链从自身开始一直按照__proto__
属性链接的原型链找下去,直到找到这个属性或方法,这也就意味着原型靠前的同名方法或属性会覆盖靠后的方法或属性(或者称隐藏,它还在,只是访问不到)。
几种常见的原型链:
1. 函数的原型链,以function Foo
为例,它的原型链是
Foo->Function.prototype->Object.prototype->null
2. new出来的对象的原型链,以foo = new Foo
为例,它的原型链是
foo->Foo.prototype->Object.prototype->null
2.var,funtion,全局声明的不同
1. var
和funtion
的声明会被提升(后面的相当于赋值),全局声明不会。
2. 与let
不同,var
和function
声明的并非块级作用域,而是函数级作用域,在函数内var
和function
声明的变量在函数结束后会被销毁,而全局声明不会。
3. 虽然全局作用域下var和全局声明都会变成global对象的属性,但通过var
语句添加的全局变量有一个configurable
属性,其默认值为false
(不可以delete
),而全局定义的configurable
属性是true
。
3.this指向问题
有下面几种情况:
1.普通调用 this
指向global
对象,严格模式下是undefine
。
2.对象方法调用 this
指向这个对象。(但是函数的方法并不绑定this
为这个对象)
3.setTimeout() 和 setInterval() 第一个参数,即回调函数中的this
默认指向window
。
4.new 指向实例化的对象。
5.bind,apply,call 填null
时同1,填其他时this
指向第一个参数。