目录
一、执行上下文
1.执行上下文
2.执行上下文栈
3.闭包
1)定义
2)形成条件
3)例子
(1)例子1:简单闭包
(2)例子2:闭包与循环
(3)例子3:使用闭包模拟私有变量
二、原型链
1.定义
2.原型(Prototype)与构造函数(Constructor)
3.原型链使用
1)工作原理
2)使用
(1)设置原型对象
(2)原型链的继承
一、执行上下文
在JavaScript中,每当代码执行时,都会创建一个执行上下文(Execution Context)。执行上下文是JavaScript代码执行时的环境,它决定了代码如何被解析和执行。执行上下文是JavaScript引擎的基础,用于管理代码执行期间的变量、函数等。
1.执行上下文
1)全局执行上下文:在全局作用域中定义的代码会在全局执行上下文中执行。在浏览器中,全局执行上下文是window对象。
2)函数执行上下文:每当调用函数时,都会创建一个新的函数执行上下文,这个上下文包含了函数的局部变量、参数等信息。
2.执行上下文栈
JavaScript引擎使用执行上下文栈(也称调用栈)来管理执行上下文。
当代码执行时,新的执行上下文会被推入栈中;当执行完成后,该上下文会从栈中弹出。
函数调用时会创建一个新的执行上下文,并将其推入栈中,当函数执行完毕后,其执行上下文就会从栈中弹出,继续之前的执行上下文。
例子1:
function foo() {
let x = 1;
console.log(x);
}
let y = 2;
foo(); // 输出: 1
console.log(y); // 输出: 2
在这个例子中,首先会创建全局执行上下文,并在其中定义变量y,接着foo()函数调用,创建新的执行上下文,并定义变量x,当函数执行完毕后,其执行上下文会从栈中弹出,继续全局执行上下文,进行后续代码。
例子2:
let globalVar = 'I am global';
function testGlobalContext() {
let localVar = 'I am local';
console.log(globalVar); // 访问全局变量
console.log(localVar); // 访问局部变量
}
testGlobalContext(); // 输出: I am global, I am local
console.log(globalVar); // 输出: I am global
例子3:
var outerVar = 'I am outside';
function outerFunction() {
var innerVar = 'I am inside';
function innerFunction() {
console.log(outerVar); // 访问外部函数作用域的变量
console.log(innerVar); // 访问自身作用域的变量
}
innerFunction(); // 调用内部函数
}
outerFunction(); // 输出: I am outside, I am inside
这个例子中,outerVar是在全局作用域中定义的,而innerVar 是在outerFunction的函数作用域中定义的。innerFunction能够访问到 outerVar,是因为它位于outerFunction的作用域之内(即 outerFunction的执行上下文在 innerFunction执行时仍然存在于调用栈上)。
例子4:
function first() {
second();
console.log('First function');
}
function second() {
third();
console.log('Second function');
}
function third() {
console.log('Third function');
}
first(); // 调用顺序:first -> second -> third -> second -> first
结果:
3.闭包
1)定义
闭包(Closure)是一个函数值,它引用了其外部的作用域(词法作用域)中的变量。即使函数是在其外部作用域之外执行,它仍然可以访问那些变量。
-
封装私有变量,变量只能通过特定的函数接口进行访问和修改,有助于保护数据不被外部随意访问。
-
创建模块:闭包可以模拟模块的概念,实现模块的封装和隐藏实现细节。通过这种方式,可以将相关的函数和数据组织在一起,只暴露必要的接口给外部使用。
-
回调函数和异步编程:在JavaScript中,闭包常用于处理回调函数和异步编程。
通俗解释:
现在有一个大房间(外部函数),里面有一个小盒子(局部变量),小盒子里放了一些东西(变量的值),还有一个能够进入这个大房间的门(外部函数的调用),但是这个房间里面的东西(包括小盒子)我们不能直接从房间外面看到或拿到。
这时,在房间里设置一个窗口(内部函数),通过这个窗口,可以把小盒子里的东西展示给房间外面的人看,或者允许修改小盒子里的东西。这个窗口加上它能看到的小盒子里的东西,以及它所在的房间,就构成了一个闭包。
2)形成条件
(1)函数嵌套
(2)内部函数引用了外部函数的变量
(3)内部函数被执行
3)例子
(1)例子1:简单闭包
// 外部函数,大房间
function outerFunction() {
var secret = "我是房间里的小盒子放的东西";
// 内部函数,窗口
function innerFunction() {
// 通过内部函数(窗口)可以获取到小盒子里的东西
console.log(secret);
}
return innerFunction; // 返回内部函数,形成闭包
}
var myClosure = outerFunction(); // 调用外部函数,此时不执行内部函数
myClosure(); // 通过闭包访问外部函数的局部变量,输出: 我是房间里的小盒子放的东西
(2)例子2:闭包与循环
// 使用闭包来捕获每次循环的i值
function createFunctionsFixed() {
let result = [];
for (let i = 0; i < 5; i++) {
result.push((function(x) {
return function() {
return x; // 使用闭包捕获的x值
};
})(i)); // 立即执行函数表达式,将当前的i值作为参数x传递给内部函数
}
return result;
}
let functionsFixed = createFunctionsFixed();
for (let k = 0; k < functionsFixed.length; k++) {
console.log(functionsFixed[k]()); // 输出0, 1, 2, 3, 4
}
解析:
(function(x) {
return function() {
return x; // 使用闭包捕获的x值
};
})(i)
这个函数是一个立即执行函数表达式(IIFE)。
(立即执行函数表达式,具体参考:
JavaScript之深入理解立即调用函数表达式(IIFE),你对它的理解,决定了它的出镜率(系列六)-CSDN博客)
它的作用是立即执行并传递当前循环的 i 值作为参数 x 给内部函数,它创建了一个新的作用域,其中 x 是作为参数传入的 i 值的副本。
内部函数function使用闭包捕获x的值(即 i 值),使得可以在作用域外访问到 i 的值。
结果:
(3)例子3:使用闭包模拟私有变量
function Counter() {
let privateCount = 0; // 私有变量
function changeCount(value) {
privateCount += value; // 修改私有变量的值
}
return {
increment: function() {
changeCount(1); // 增加计数
return privateCount;
},
decrement: function() {
changeCount(-1); // 减少计数
return privateCount;
},
getCount: function() {
return privateCount; // 获取当前计数值
}
};
}
let myCounter = Counter(); // 创建一个计数器实例
console.log(myCounter.getCount()); // 输出: 0
myCounter.increment(); // 增加计数
console.log(myCounter.getCount()); // 输出: 1
myCounter.decrement(); // 减少计数
console.log(myCounter.getCount()); // 输出: 0
这个例子中,Counter函数创建了一个包含私有变量privateCount的作用域,它返回了一个对象,该对象包含三个方法:increment,decrement,getCount,这些方法都可以被访问和修改。
二、原型链
1.定义
原型链是由多个原型对象通过__proto__属性连接起来形成的一种链式结构,它描述了对象之间或函数之间的原型继承关系,实现了继承和共享属性,使得对象能够继承其父对象或更上层父对象的属性和方法。
2.原型(Prototype)与构造函数(Constructor)
- 每个函数都有一个prototype属性,这个属性是一个普通的对象,即函数的原型对象。
- 通过new关键字创建的实例对象,会包含一个指向其构造函数原型对象的内部链接(通常通过__proto__属性访问,但__proto__不是标准属性,应使用Object.getPrototypeOf()方法)。
3.原型链使用
1)工作原理
(1)查找属性:当访问一个属性或方法时,js会首先在对象本身中查找,如果对象本身没有该属性,则会沿着原型链向上查找,直到找到原型链的顶端(通常是0bject.prototype,它的__proto__属性为null)。
(2)继承属性:原型链允许对象继承另一个对象的属性和方法,这是通过将对象的__proto__属性指向另一个对象的prototype属性来实现的。当访问对象的属性时,如果对象本身没有该属性,则会到原型对象中查找。
2)使用
(1)设置原型对象
在构造函数中,可以通过设置其prototype属性来定义实例对象可以继承的属性和方法。
例子:
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
console.log('Hello, my name is ' + this.name);
};
let person1 = new Person('Alice'); // 继承属性
// person1 的 __proto__ 指向 Person.prototype
// 因此在person1中本身没有这个方法,但通过原型链向上查找,可以访问 Person.prototype 上的 greet 方法
person1.greet(); // 输出: Hello, my name is Alice
该例子中的原型链是:person1 -> Person.prototype -> Object.prototype -> null
(2)原型链的继承
如果一个对象A的原型(prototype )是另一个对象B,那么A就能继承B的属性和方法。
可以通过将子类的prototype 设置为父类的一个实例来实现继承。
例子:
// B
function Parent() {
this.parentProperty = true;
}
// B的原型prototype设置
Parent.prototype.getValue = function() {
return this.parentProperty;
};
// A
function Child() {
this.childProperty = false;
}
// 继承SuperType,A的原型是B对象(子类的原型prototype是父类的一个实例),A可以访问B的方法
Child.prototype = new Parent();
let instance = new Child();
console.log(instance.getValue()); // true
补充:
原型链条的使用以及使用原型链条操作数组的方法_原型链使用-CSDN博客