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

prototype
是函数对象的属性(非函数对象无prototype属性),用于定义通过该函数创建的实例对象的原型。__proto__
是每一个对象都有的属性,用于访问和修改对象的原型
javascript
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属性指向关联的构造函数。

javascript
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 会:
- 先在对象自身属性中查找
- 如果找不到,则在其原型对象上查找
- 继续在原型的原型上查找,直到找到或到达原型链末端 (
null
)
2.闭包
2.1什么是闭包?
闭包是指能够访问其他函数作用域中变量的函数,即使那个外部函数已经执行完毕。
简单说:当一个函数记住并访问它所在的词法作用域,即使这个函数在其词法作用域之外执行,就产生了闭包。
2.词法作用域和动态作用域
2.1 什么是作用域
go
作用域是指程序源代码中定义变量的区域。
作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。
JavaScript 采用词法作用域`(lexical scoping`),也就是静态作用域。
2.2 静态作用域和动态作用域
因为 JavaScript 采用的是词法作用域,函数的作用域在函数定义的时候就决定了。
而与词法作用域相对的是动态作用域,函数的作用域是在函数调用的时候才决定的。
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。
看一个面试题:
JavaScript
// 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 的开发者都会有个直观的印象,那就是顺序执行:
JavaScript
var foo = function () {
console.log('foo1');
}
foo(); // foo1
var foo = function () {
console.log('foo2');
}
foo(); // foo2
那这段呢?
JavaScript
function foo() {
console.log('foo1');
}
foo(); // foo2
function foo() {
console.log('foo2');
}
foo(); // foo2
打印的结果却是两个 foo2。
这是因为 JavaScript 引擎并非一行一行地分析和执行程序,而是一段一段地分析执行。当执行一段代码的时候,会进行一个"准备工作",那这个"一段一段"中的"段"究竟是怎么划分的呢?
到底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,只有变量声明提前了,变量初始化代码仍然在原来的位置,没法提前执行。