深入理解JS基础(1)

48 阅读4分钟

1.原型和原型链

先上一段代码

    function Person(){}
    const person = new Person()
    Person.prototype.name = "pub"
    console.log(person.__proto__ === Person.prototype)
    console.log(Person.prototype.constructor === Person)
    console.log(Object.getPrototypeOf(person) === Person.prototype)
    console.log(person.name)

上面的代码中通过new关键字创建了一个构造函数Persond的实例对象.然后打印了一些内容,其中出现的关键字有prototype , __proto__ , constructor

1.1 prototype__proto__constructor

每一个函数都会有一个属性 prototype,而这个 prototype 就是原型对象。普通的对象是没有prototype的。

image.png

每一个对象都会有一个[[Prototype]]内置属性。它的作用就是指向该对象的关联对象.Object.getPrototypeOf()  静态方法返回指定对象的原型(即内部 [[Prototype]] 属性的值)。所以Object.getPrototypeOf(person) === Person.prototype为true

image.png

而当我们访问对象的__proto__时,就会从这个内置属性中去查找。而这个__proto__就是其构造函数的原型对象prototype。 所以在代码中的打印 person.__proto__ === Person.prototype 为true。

每个对象都会有个constructor,它始终指向的是该对象的构造函数 。所以代码中的Person.prototype.constructor === Person是true,person.__proto__.constructor === Person也是true

1.2 原型链(链式查找)

当需要查找对象上的属性时,如果没有该属性,则会从原型对象上去查找,如果原型对象上没有,那么就继续去原型对象的原型对象上去查找。直到查找到null才结束。这样就构成了一个原型链 image.png

所以上面的代码中console.log(person.name)就是pub

2.作用域

2.1 什么是作用域?

作用域:代码中的程序定义变量的区域。作用域中定义了如何找到对应变量,在执行代码中(运行在作用域中),获取变量的访问权限。

2.2 词法作用域

作用域又分为词法作用域(静态作用域)和动态作用域

  • 词法(静态)作用域:作用域在定义时就确定的
  • 动态作用域:作用域是在调用时决定的
const name = 'pub'
function foo() {
    console.log(name)
}
function bar(){
    const name = '张三'
    foo()
}
bar()

JS是词法作用域,在定义的时候就确定了。所以当执行到foo的时候,先从当前函数体内查找name,没有在继续查找父级查找变量,所以打印结果是: pub

3.变量提升

4.执行上下文

JS中可执行代码分为三种:全局代码函数代码eval代码。 而当程序执行到三种代码的时候,都会生成一个执行上下文。并且会通过一个的方式去管理,又叫执行上下文栈

function fun3() {
    console.log('fun3')
}

function fun2() {
    fun3();
}

function fun1() {
    fun2();
}

fun1();

上面的代码用图的方式表示出对应的执行上下文栈如下:

image.png

执行完之后再出栈

执行上下文 有三个重要的属性:

  • 变量对象(VO)
  • 作用域链
  • this

4.1.变量对象

就是在执行上下文中变量或函数的声明。 在不同的上下文中,VO是什么

  • 全局上下文中的变量对象(variable object 简称VO)
// 全局上下文中
console.log(this) // window
  • 函数上下文中变量对象又被叫做活动对象:activiation object (简称AO)。因为函数中的变量对象只有在被调用的时候才会被激活。
function foo(a) {
    var b = 2
    function c(){}
    var d = function(){}
    b=3
}
foo(1)

上面的代码在执行上下文的分析阶段它的AO,这个阶段就执行了变量的初始化、变量提升

AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: undefined,
    c: reference to function c(){},
    d: undefined
}

执行阶段它的AO如下

AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: 2,
    c: reference to function c(){},
    d: reference to FunctionExpression d
}

面试题1:

function foo(){
    console.log(a)
    a = 1
}
foo()

上面的代码会报错:a is not defined。首先会生成一个foo的执行上下文,执行打印,当前上下文没有a,去全局VO中查找a仍然没找到,所以会报错

面试题2:

function foo(){
    console.log(a)
    var a = 1
}
foo()

打印 undefined

4.2.作用域链

先从当前上下文中找,找不到,到词法作用域的上一级找,直到找到全局上下文中的变量对象为止

4.3 this

ECMAScript 的类型分为语言类型和规范类型: Reference 规范类型中的一种。 Reference 构成: 1. base value 2. referenced name 3. strict reference

它是JavaScript 引擎在访问变量或属性时的底层机制

var foo = 1
// 上面的foo定义时,转化为规范类型就是这种形式
fooReference = {
    base: 'EnvironmentRecord',
    name: 'foo',// 属性名称
    strict: false,// 是否严格模式
}
var foo = {
    bar: function(){
        return this
    }
}
foo.bar() 

barReference = {
    base: 'foo',
    name: 'bar',
    strict: false
}

5.闭包

能够访问自由变量的函数。

自由变量:在函数中使用的,但是不是函数的参数,也不是函数内部的局部变量的这些变量

闭包 = 函数 + 能访问自由变量

var a = 'hello'
function foo(){
    console.log(a)
}
foo()

分析一下上面的代码,foo 中访问了a,但是a既不是函数内部定义的变量,也不是函数的参数,所以a就是一个自由变量,理论上讲这也是一个闭包。

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

var foo = checkscope();
foo();

image.png