JS原型链,this指向,变量提升与覆盖一网打尽

1,067 阅读3分钟

原型链,变量提升与覆盖,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(),那么会调用哪个呢?我们知道varfunction定义的函数都会被提升,但是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()返回的thisglobal对象,而上一句代码中的全局变量则会变为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. varfuntion的声明会被提升(后面的相当于赋值),全局声明不会。

2.let不同,varfunction声明的并非块级作用域,而是函数级作用域,在函数内varfunction声明的变量在函数结束后会被销毁,而全局声明不会。

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,callnull时同1,填其他时this指向第一个参数。