JavaScript的深入了解

55 阅读5分钟

1 原型和原型链

原型就是构造函数的prototype属性指向的对象,然后具体构造函数的prototype指向谁呢?就是由构造函数new出来实例的原型,也就是实例的__proto__,那么这么说的话就是构造函数的prototype===实例的__proto__,这样说有点乱。举一个例子:

//构造函数
function Person(){}
//构造函数实例
const person = new Person()
//那么由上面就可以知道person.__proto__===Person.prototype
console.log(person.__proto__===Person.prototype)
//结果为true

我们在浏览器控制台中验证下结果:

image.png

原型链就直接举例子:

//构造函数
function Person(){}
//构造函数的原型上挂载一个name属性
Person.prototype.name = 'real hiphop'
//构造函数实例
const person = new Person()
//实例上给一个name属性
person.name = 'faker hiphop'
//当访问person上的name属性时
console.log(person.name) //'faker hiphop'
//删除person实例上的name
delete person.name
//再次访问person实例上的name
console.log(person.name) //'real hiphop'

下面就到浏览器控制台验证下结果:

image.png

由上面的例子,总结一下,原型链就是实例上可以通过.__proto__一直往上去调用这个属性,直到.到null,形成的整条链就是原型链(其中最后一级是Object.prototype,在Object的原型上再找原型时就是null了,表示到底了),然后我们在实例上调用某个属性的时候,如果本身实例上没有找到,就沿着刚刚所说的那条原型链去找。

注意: 如上图,我们第一次person.name的时候访问到了faker hiphop是因为person上带了这个属性,然后我们用delet删除掉了person实例上的name属性,再访问name的时候他就访问到了原型上的real hiphop

浏览器上一直取原型的过程:

image.png

2 词法作用域和动态作用域

首先看下《JavaScript权威指南》中的经典例子:

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope();
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
checkscope()();

可以想想这两段代码的输出分别是什么。 其实这里两段代码输出的都是"local scope"。

原因也很简单,因为JavaScript采用的是词法作用域,函数的作用域基于函数创建的位置。 《JavaScript权威指南》的回答是:

JavaScript 函数的执行用到了作用域链,这个作用域链是在函数定义的时候创建的。嵌套的函数 f() 定义在这个作用域链里,其中的变量 scope 一定是局部变量,不管何时何地执行函数 f(),这种绑定在执行 f() 时依然有效。

总结: 词法作用域和动态作用域的区别就是词法作用域和函数创建的位置有关系,而动态作用域和函数调用的位置有关系。JavaScript采用的是静态作用域也就是我们说的词法作用域.

那是不是这两个代码就是没有区别的呢,答案是No,他们的执行顺序有区别,也就是我们说的执行上下文栈活动的顺序有区别。

3 执行上下文栈

首先第一个代码块中,执行上下文栈中函数出入栈的顺序是: checkscope入栈->f函数入栈->f函数执行完出栈->checkscope执行完出栈 第二个代码块中,执行上下文栈中函数出入栈的顺序是: checkscope入栈->checkscope执行完出栈->f函数入栈->f函数执行完出栈

对于执行上下文包含了3个方面的内容,VO变量对象,this,[[scope]]作用域链

4 变量对象,作用域链,this

  1. 首先执行JavaScript代码的3种环境:全局环境,函数环境,eval环境

一般我们讨论的比较多就是全局上下文和函数上下文了

在全局上下文中,变量对象就是我们全局对象,一般在浏览器中也就是window了; 然后函数上下文中,变量对象就是我们说的活动对象,其实变量对象和活动对象是一个东西,由于变量对象是规范上或者说是引擎上的,不可以被JavaScript环境访问。

注意:所以只有当进入一个执行上下文中的时候,执行上下文中的变量对象被激活,变成我们所谓的活动对象这样,活动对象上的属性也就可以被访问了。

  1. 作用域链:查找变量的时候,会从当前上下文的变量对象中查找,如果没有,就会从上一级执行上下文中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。

  2. 这里就通过this指向的4种情况来进行描述吧: 3.1 作为对象函数调用this指向 指向该对象 3.2 作为普通函数被调用 指向window 3.3 构造函数调用 指向new出来的实例 3.4 使用call()apply()bind()等 改变this指向方法

5 闭包

阮一峰老师的解释:闭包就是能够读取其他函数内部变量的函数。由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。

闭包说的通俗⼀点就是打通了⼀条在函数外部访问函数内部作⽤域的通道。正常情况下函数外部是访问不到函数内部作⽤域变量的,表象判断是不是闭包:函数嵌套函数,内部函数被return 内部函数调⽤外层函数的局部变量。

作用:定义私有变量,隔离作用域,不污染全局 缺点:变量贮存,内存泄漏(变量用完手动为null)