笔记:javascript 深刻理解

JavaScript深刻之从原型到原型链

构造函数->原型
每一个函数都有一个 prototype 属性,指向实例的原型
原型:每个JavaScript对象(null除外)在建立的时候就会与之关联另外一个对象,这个对象就是咱们所说的原型
实例->原型
每个JavaScript对象(除了 null )都具备的一个属性,叫__proto__,这个属性会指向该对象的原型
ES5的方法,能够得到对象的原型
(Object.getPrototypeOf(person) === Person.prototype person是实例,Person是构造函数
当读取实例的属性时,若是找不到,就会查找与对象关联的原型中的属性,若是还查不到,就去找原型的原型,一直找到最顶层为止。
原型->构造函数
每一个原型都有一个 constructor 属性指向关联的构造函数
实例-> 构造函数
person.constructor === Person 从原型上面继承
Null表明什么
null 表示“没有对象”,即该处不该该有值。前端

clipboard.png

obj.__proto__ :非标准的方法访问原型,并不存在于 Person.prototype 中,并非原型上的属性,它是来自于 Object.prototype,返回了Object.getPrototypeOf(obj)
继承:
继承意味着复制操做, 默认并不会复制对象的属性,在两个对象之间建立一个关联,经过委托访问另外一个对象的属性和函数,因此与其叫继承,委托的说法反而更准确些。chrome

JavaScript深刻之词法做用域和动态做用域

做用域:定义变量的区域,肯定当前执行代码对变量的访问权限
Js: 词法做用域(lexical scoping),也就是静态做用域,函数的做用域基于函数建立的位置
静态做用域:做用域在函数定义
动态做用域:做用域是在函数调用数组

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()();

思考:结果同样,有什么不一样

JavaScript深刻之执行上下文栈

当执行一段代码的时候,会进行一个“准备工做
这个“一段一段”中的“段”到底是怎么划分的呢
Js引擎遇到一段怎样的代码时才会作“准备工做”呢?
执行到一个函数的时候,就会进行准备工做,就叫作"执行上下文(execution context)"函数

Js 的可执行代码(executable code)的类型
全局代码、函数代码、eval代码this

执行上下文栈(Execution context stack,ECS
管理建立执行上下文 ECStack = []
Js解释执行代码,最早遇到全局代码,so初始化首先就会向执行上下文栈压入一个全局执行上下文, globalContext ,只有当整个应用程序结束的时候,ECStack 才会被清空,因此程序结束以前ECStack 最底部永远有个 globalContext
当执行一个函数的时候,就会建立一个执行上下文,而且压入执行上下文栈,当函数执行完毕的时候,就会将函数的执行上下文从栈中弹出spa

函数执行结束以后,若是没有显示地返回值,默认是undefined,chrome中会把函数执行的结果打印出来prototype

JavaScript深刻之变量对象

执行上下文,都有三个重要属性code

  • 变量对象(Variable object,VO)
  • 做用域链(Scope chain)
  • this

变量对象:数据做用域,存储了在上下文中定义的变量和函数声明对象

不一样上下文变量对象不一样:全局上下文下的变量对象和函数上下文下的变量对象blog

全局上下文中的变量对象:全局对象
全局对象:

预约义的对象
访问全部其余全部预约义的对象、函数和属性
顶层 JavaScript 代码中,能够用关键字 this 引用全局对象, 全局对象是做用域链的头
全局对象是由 Object 构造函数实例化的一个对象

全局上下文中的变量对象:活动对象(activation object, AO)
活动对象和变量对象实际上是一个东西
它们其实都是同一个对象,只是处于执行上下文的不一样生命周期
变量对象:规范上的实现,不可在 JavaScript 环境中访问
活动对象:只有到当进入一个执行上下文中,这个执行上下文的变量对象才会被激活而只有被激活的变量对象也就是活动对象上的各类属性才能被访问
活动对象是在进入函数上下文时刻被建立的,它经过函数的 arguments 属性初始化。arguments 属性值是 Arguments 对象

JS 引擎在分析代码的时候,分为两个阶段:编译阶段和执行阶段

  • 编译阶段:处理声明语句,全部声明的变量添加为当前执行上下文变量对象(VO)的属性。若是是变量声明,其值暂且初始化为 undefined,若是是函数声明,它会在堆上开辟内存,并将函数定义放到堆上,函数变量只保存这指向函数定义的地址。
  • 执行阶段:编译结束后,JS 引擎会再次扫描代码,这个阶段主要的任务是根据执行语句,更新变量对象等。

当进入执行上下文时,这时候尚未执行代码,
变量对象会包括:

  1. 函数的全部形参 (若是是函数上下文)

    • 由名称和对应值组成的一个变量对象的属性被建立
    • 没有实参,属性值设为 undefined
  2. 函数声明

    • 由名称和对应值(函数对象(function-object))组成一个变量对象的属性被建立
    • 若是变量对象已经存在相同名称的属性,则彻底替换这个属性
  3. 变量声明

    • 由名称和对应值(undefined)组成一个变量对象的属性被建立;
    • 若是变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性

在代码执行阶段,会顺序执行代码,根据代码,修改变量对象的值

  1. 全局上下文的变量对象初始化是全局对象
  2. 函数上下文的变量对象初始化只包括 Arguments 对象
  3. 在进入执行上下文时会给变量对象添加形参、函数声明、变量声明等初始的属性值
  4. 在代码执行阶段,会再次修改变量对象的属性值

补充:
Arguments对象 - 调用函数时,会为其建立一个Arguments对象,并自动初始化局部变量arguments,指代该Arguments对象。全部做为参数传入的值都会成为Arguments对象的数组元素

执行上下文的生命周期

  1. 建立阶段

在这个阶段中,执行上下文会分别建立变量对象,创建做用域链,以及肯定this的指向。

  1. 代码执行阶段

建立完成以后,就会开始执行代码,这个时候,会完成变量赋值,函数引用,以及执行其余代码

AO 其实是包含了 VO 的
也就是说 AO 的确是在进入到执行阶段的时候被激活,可是激活的除了 VO 以外,还包括函数执行时传入的参数和 arguments 这个特殊对象。
AO = VO + function parameters + arguments

在进入执行上下文时,首先会处理函数声明,其次会处理变量声明,若是变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。

在进入执行上下文阶段,只会将有 `var,function修饰的变量或方法添加到变量对象中。在进行执行阶段前一刻,foo和bar方法的它们的VO中均没有a属性。在执行阶段,执行到 a= 1时,才将a变量添加到全局的变量对象中而不是在进入执行上下文阶段。因此foo方法中会报错,bar方法会打印 1。

function foo() {
    console.log(a);
    a = 1;
}
foo(); // Uncaught ReferenceError: a is not defined

function bar() {
    a = 1;
    console.log(a);
}
bar(); // 1

WT: let/const 在esc中的表现

JavaScript深刻之做用域链

当查找变量的时候,会先从当前上下文的变量对象中查找,若是没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫作做用域链

函数建立:
函数的做用域在函数定义的时候就决定了
函数有一个内部属性 [[scope]],当函数建立的时候,就会保存全部父变量对象到其中
[[scope]] 就是全部父变量对象的层级链([[scope]] 并不表明完整的做用域链!)

function foo() {
    function bar() {
        ...
    }
}

建立时
foo.[[scope]] = [
  globalContext.VO
];

bar.[[scope]] = [
    fooContext.AO,
    globalContext.VO
];

函数激活:
当函数激活时,进入函数上下文,建立 VO/AO 后,将活动对象添加到做用链的前端
这时候执行上下文的做用域链,咱们命名为 Scope:
Scope = [AO].concat([[Scope]]);
e.g.:

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

执行过程以下:
1.checkscope 函数被建立,保存做用域链到 内部属性[[scope]]

checkscope.[[scope]] = [
    globalContext.VO
];

2.执行 checkscope 函数,建立 checkscope 函数执行上下文,checkscope 函数执行上下文被压入执行上下文栈

ECStack = [
    checkscopeContext,
    globalContext
];

3.checkscope 函数并不马上执行,开始作准备工做,第一步:复制函数[[scope]]属性建立做用域链

checkscopeContext = {
    Scope: checkscope.[[scope]],
}

4.第二步:用 arguments 建立活动对象,随后初始化活动对象,加入形参、函数声明、变量声明

checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        scope2: undefined
    },
    Scope: checkscope.[[scope]],
}

5.第三步:将活动对象压入 checkscope 做用域链顶端

checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        scope2: undefined
    },
    Scope: [AO, [[Scope]]]
}

6.准备工做作完,开始执行函数,随着函数的执行,修改 AO 的属性值

checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        scope2: 'local scope'
    },
    Scope: [AO, [[Scope]]]
}

7.查找到 scope2 的值,返回后函数执行完毕,函数上下文从执行上下文栈中弹出

ECStack = [
    globalContext
];

补充:
每个函数都有本身的执行环境,函数执行的时候,会建立函数执行上下文

在源代码中当你定义(书写)一个函数的时候(并未调用),js引擎也能根据你函数书写的位置,函数嵌套的位置,给你生成一个[[scope]],做为该函数的属性存在(这个属性属于函数的)。即便函数不调用,因此说基于词法做用域(静态做用域)。而后进入函数执行阶段,生成执行上下文,执行上下文你能够宏观的当作一个对象,(包含vo,scope,this),此时,执行上下文里的scope和以前属于函数的那个[[scope]]不是同一个,执行上下文里的scope,是在以前函数的[[scope]]的基础上,又新增一个当前的AO对象构成的。函数定义时候的[[scope]]和函数执行时候的scope,前者做为函数的属性,后者做为函数执行上下文的属性。

相关文章
相关标签/搜索