JS中的作用域链与原型链

535 阅读3分钟

一、作用域链

作用域定义了变量或函数有权访问的其他数据,决定了他们各自的行为。每个作用域都有一个与之关联的变量对象(variable object),作用域中定义的所有变量和函数都保存在这个对象中。

1.作用域

作用域可分为全局作用域(_Global_)与局部作用域(_Local_)

在web浏览器中,全局作用域被认为是window对象,因为所有的全局变量和函数都称为了window对象的属性和方法。除了全局作用域,JS中每个函数都有自己的作用域。当执行流进入一个函数时,函数的环境就会被推进一个执行栈中,等到函数执行之后,栈将其环境弹出,把控制权交给父级执行环境。

2.作用域链

当代码在一个环境中执行时,会创建变量对象的一个作用域链(_scope chain)。_作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终是当前执行的代码所在环境的变量对象。作用域链中的下一个变量对象,来自包含它的外部作用域,在下一个变量对象则是来自下一个包含它的外部作用域,就这样一直延续到全局作用域,形成一个链条;全局作用域的变量对象始终都是作用域链中的最后一个对象。

<script>
var color="blue";
function changColor(){
    var anotherColor="red";
    function swapColors(){
        var tempColor=anotherColor;
         anotherColor=color;
         color=tempColor;       //这里可以访问color、anotherColor和tempColor
    }
    swapColors();        //这里可以访问color和anotherColor,但不能访问tempColor
}
changeColor();//这里只能访问color
</script>

                                                

上面的代码有三个作用域,分别是全局作用域、changeColor()的局部作用域和swapColors()的局部作用域。如图所指的矩形表示特定的作用域,其中局部作用域可以通过作用域链访问所以的外部作用域境,但是外部作用域不能访问局部作用域的任何变量和函数。这些作用域之间的联系是线性的、有序的。每个作用域都可以向上搜索作用域链,以查询变量和函数名。但任何作用域都不能向下搜索作用域链而进入下一个作用域。

二、原型链

1.prototype

function Person(name) {
    this.name = name;      
}
Person.prototype.sex = 'female'
var person1 = new Person()
var person2 = new Person()
console.log(person1.sex) //female
console.log(person2.sex)  //female

函数的prototype指向一个对象,这个对象是使用new构造函数时,创建出来的实例对象的原型,也就是person1和person2的原型。

构造函数与原型之间的关系:

        

2.proto

每一个实例对象都有一个隐藏的属性__proto__,该属性保存着指向构造函数的原型的指针。

function Person() {

}
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true

他们之间的关系图:

         

3.constructor

每个原型对象都有一个constructor属性用于指向关联该原型的构造函数。

function Person() {

}
console.log(Person===Person.prototype.constructor)  //true

关系图如下:

         

4.原型链

实例中没有的属性,会通过实例中的隐藏属性__proto__属性去寻找对象关联的原型中的属性,如果还没有,就去找原型的原型,一直找到最顶层为止。

function Person() {

}

Person.prototype.name = 'Obama';

var person = new Person();

person.name = 'Trump';
console.log(person.name) // Obama

delete person.name;
console.log(person.name) // trump

直接给实例添加一个name属性,name根据原型链的查找规则,如果当前查找的作用域有该属性,则返回当前的属性,所以一开始返回的是Obama;当我们把实例对象的属性删除之后,再一次要求输出属性时,原型链在当前作用域找不到,则会通过原型链向上一层的对象上面查找,也就是通过person.__proto__找到Person.prototype所指的对象上面查找。如果再没有找到,就继续顺着原型链向上查找,直到原型链的最顶端。

  

如上图所示,蓝色的线就是原型链。