JavaScript进阶之路(一)

13 阅读4分钟

1.原型(Prototype)&原型链(Prototype Chain)

1.1 原型 (Prototype)

在 JavaScript 中,每个对象都有一个内置的 [[Prototype]] 属性(可以通过 __proto__ 或 Object.getPrototypeOf() 访问),这个属性指向该对象的原型对象。

1.2 prototype及__proto__

prototype及__proto__.png

  1. prototype是函数对象的属性(非函数对象无prototype属性),用于定义通过该函数创建的实例对象的原型。
  2. __proto__是每一个对象都有的属性,用于访问和修改对象的原型
function Person(name) {
  this.name = name;
}
Person.prototype.sayHello = function () {
  console.log("Hello, my name is " + this.name);
};
let person1 = new Person("Alice");
let person2 = new Person("Bob");
person1.sayHello(); // 输出Hello, my name is Alice
person2.sayHello(); // 输出Hello, my name is Bob
console.log(Person.prototype === person.__proto__)//输出true

1.3 constructor

constructor是原型上的一个属性,原型无法指向实例,因为一个构造函数可以生成多个实例,但是原型可以通过constructor属性指向关联的构造函数。

constructor与原型的关系.png

function Person() {

}

var person = new Person();

console.log(person.__proto__ == Person.prototype) // true

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

console.log(Object.getPrototypeOf(person) === Person.prototype) // true

1.4实例与原型&&原型链

当访问一个对象的属性时,JavaScript 会:

  1. 先在对象自身属性中查找
  2. 如果找不到,则在其原型对象上查找
  3. 继续在原型的原型上查找,直到找到或到达原型链末端 (null)

2.闭包

2.1什么是闭包?

闭包是指能够访问其他函数作用域中变量的函数,即使那个外部函数已经执行完毕。

简单说:当一个函数记住并访问它所在的词法作用域,即使这个函数在其词法作用域之外执行,就产生了闭包。

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

2.1 什么是作用域

作用域是指程序源代码中定义变量的区域。
作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。
JavaScript 采用词法作用域`(lexical scoping`),也就是静态作用域。

2.2 静态作用域和动态作用域

因为 JavaScript 采用的是词法作用域,函数的作用域在函数定义的时候就决定了。

而与词法作用域相对的是动态作用域,函数的作用域是在函数调用的时候才决定的。

var value = 1;

function foo() {
    console.log(value);
}

function bar() {
    var value = 2;
    foo();
}

bar();

// 结果是 ???

因为JavaScript采用的是词法作用域即静态作用域,其在执行foo函数时,先从foo函数内部查找是否有value变量,如果没有,就根据书写位置,查找上面一层的代码,也就是value = 1,所以打印结果会是1。

假设JavaScript采用动态作用域,让我们分析下执行过程:

执行 foo 函数,依然是从 foo 函数内部查找是否有局部变量 value。如果没有,就从调用函数的作用域,也就是 bar 函数内部查找 value 变量,所以结果会打印 2。

前面我们已经说了,JavaScript采用的是静态作用域,所以这个例子的结果是 1。

看一个面试题:
// case 1
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope();

// case 2
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() 时依然有效。

但是在这里真正想让大家思考的是:

虽然两段代码执行的结果一样,但是两段代码究竟有哪些不同呢?

3.执行上下文

3.1 顺序执行

写过 JavaScript 的开发者都会有个直观的印象,那就是顺序执行:

var foo = function () {

    console.log('foo1');

}

foo();  // foo1

var foo = function () {

    console.log('foo2');

}

foo(); // foo2

那这段呢?

function foo() {

    console.log('foo1');

}

foo();  // foo2

function foo() {

    console.log('foo2');

}

foo(); // foo2

打印的结果却是两个 foo2。

这是因为 JavaScript 引擎并非一行一行地分析和执行程序,而是一段一段地分析执行。当执行一段代码的时候,会进行一个“准备工作”,那这个“一段一段”中的“段”究竟是怎么划分的呢?

到底JavaScript引擎遇到一段怎样的代码时才会做“准备工作”呢?

console.log(add2(1,1)); //输出2
function add2(a,b){
    return a+b;
}
console.log(add1(1,1));  //报错:add1 is not a function
var add1 = function(a,b){
    return a+b;
}

// 用函数语句创建的函数add2,函数名称和函数体均被提前,在声明它之前就使用它。
// 但是使用var表达式定义函数add1,只有变量声明提前了,变量初始化代码仍然在原来的位置,没法提前执行。