上面这张图来自于mdn,分别展示了栈、堆和队列,其中栈就是我们所说的执行上下文栈;堆是用于存储对象这种复杂类型,我们复制对象的地址引用就是这个堆内存的地址;队列就是异步队列,用于event loop的执行。
JS代码在引擎中是以“一段一段”的方式来分析执行的,而并非一行一行来分析执行。而这“一段一段”的可执行代码无非为三种:Global code、Function Code、Eval code。这些可执行代码在执行的时候又会创建一个一个的执行上下文(Execution context)。例如,当执行到一个函数的时候,JS引擎会做一些“准备工作”,而这个“准备工作”,我们称其为执行上下文。
那么随着我们的执行上下文数量的增加,JS引擎又如何去管理这些执行上下文呢?这时便有了执行上下文栈。
这里我用一段贯穿全文的例子来讲解执行上下文栈的执行过程:
var scope = 'global scope';
function checkscope(s) {
var scope = 'local scope';
function f() {
return scope;
}
return f();
}
checkscope('scope');
当JS引擎去解析代码的时候,最先碰到的就是Global code,所以一开始初始化的时候便会将全局上下文推入执行上下文栈,并且只有在整个应用程序执行完毕的时候,全局上下文才会推出执行上下文栈。
这里我们用ECS来模拟执行上下文栈,用globalContext来表示全局上下文:
ESC = [
globalContext, // 一开始只有全局上下文
]
然后当代码执行checkscope函数的时候,会创建checkscope函数的执行上下文,并将其压入执行上下文栈:
ESC = [
checkscopeContext, // checkscopeContext入栈
globalContext,
]
接着代码执行到return f()的时候,f函数的执行上下文被创建:
ESC = [
fContext, // fContext入栈
checkscopeContext,
globalContext,
]
f函数执行完毕后,f函数的执行上下文出栈,随后checkscope函数执行完毕,checkscope函数的执行上下文出栈:
// fContext出栈
ESC = [
// fContext出栈
checkscopeContext,
globalContext,
]
// checkscopeContext出栈
ESC = [
// checkscopeContext出栈
globalContext,
]
每一个执行上下文都有三个重要的属性:
这一节我们先来说一下变量对象(Variable object,这里简称VO)。
变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明。并且不同的执行上下文也有着不同的变量对象,这里分为全局上下文中的变量对象和函数执行上下文中的变量对象。
全局上下文中的变量对象其实就是全局对象。我们可以通过this来访问全局对象,并且在浏览器环境中,this === window;在node环境中,this === global。
在函数上下文中的变量对象,我们用活动对象来表示(activation object,这里简称AO),为什么称其为活动对象呢,因为只有到当进入一个执行上下文中,这个执行上下文的变量对象才会被激活,并且只有被激活的变量对象,其属性才能被访问。
在函数执行之前,会为当前函数创建执行上下文,并且在此时,会创建变量对象:
还是以刚才的代码为例:
var scope = 'global scope';
function checkscope(s) {
var scope = 'local scope';
function f() {
return scope;
}
return f();
}
checkscope('scope');
在执行checkscope函数之前,会为其创建执行上下文,并初始化变量对象,此时的变量对象为:
VO = {
arguments: {
0: 'scope',
length: 1,
},
s: 'scope', // 传入的参数
f: pointer to function f(),
scope: undefined, // 此时声明的变量为undefined
}
随着checkscope函数的执行,变量对象被激活,变相对象内的属性随着代码的执行而改变:
VO = {
arguments: {
0: 'scope',
length: 1,
},
s: 'scope', // 传入的参数
f: pointer to function f(),
scope: 'local scope', // 变量赋值
}
其实也可以用另一个概念“函数提升”和“变量提升”来解释:
function checkscope(s) {
function f() { // 函数提升
return scope;
}
var scope; // 变量声明提升
scope = 'local scope' // 变量对象的激活也相当于此时的变量赋值
return f();
}
每一个执行上下文都有三个重要的属性:
这一节我们说一下作用域链。
当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级执行上下文的变量对象中查找,一直找到全局上下文的变量对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。
下面还是用我们的例子来讲解作用域链:
var scope = 'global scope';
function checkscope(s) {
var scope = 'local scope';
function f() {
return scope;
}
return f();
}
checkscope('scope');
首先在checkscope函数声明的时候,内部会绑定一个[[scope]]的内部属性:
checkscope.[[scope]] = [
globalContext.VO
];
接着在checkscope函数执行之前,创建执行上下文checkscopeContext,并推入执行上下文栈:
// -> 初始化作用域链;
checkscopeContext = {
scope: checkscope.[[scope]],
}
// -> 创建变量对象
checkscopeContext = {
scope: checkscope.[[scope]],
VO = {
arguments: {
0: 'scope',
length: 1,
},
s: 'scope', // 传入的参数
f: pointer to function f(),
scope: undefined, // 此时声明的变量为undefined
},
}
// -> 将变量对象压入作用域链的最顶端
checkscopeContext = {
scope: [VO, checkscope.[[scope]]],
VO = {
arguments: {
0: 'scope',
length: 1,
},
s: 'scope', // 传入的参数
f: pointer to function f(),
scope: undefined, // 此时声明的变量为undefined
},
}
接着,随着函数的执行,修改变量对象:
checkscopeContext = {
scope: [VO, checkscope.[[scope]]],
VO = {
arguments: {
0: 'scope',
length: 1,
},
s: 'scope', // 传入的参数
f: pointer to function f(),
scope: 'local scope', // 变量赋值
}
}
与此同时遇到f函数声明,f函数绑定[[scope]]属性:
checkscope.[[scope]] = [
checkscopeContext.VO, // f函数的作用域还包括checkscope的变量对象
globalContext.VO
];
之后f函数的步骤同checkscope函数。
再来一个经典的例子:
var data = [];
for (var i = 0; i < 6; i++) {
data[i] = function () {
console.log(i);
};
}
data[0]();
// ...
很简单,不管访问data几,最终console打印出来的都是6,因为在ES6之前,JS都没有块级作用域的概念,for循环内的代码都在全局作用域下。
在data函数执行之前,此时全局上下文的变量对象为:
globalContext.VO = {
data: [pointer to function ()],
i: 6, // 注意:此时的i值为6
}
每一个data匿名函数的执行上下文链大致都如下:
data[n]Context = {
scope: [VO, globalContext.VO],
VO: {
arguments: {
length: 0,
}
}
}
那么在函数执行的时候,会先去自己匿名函数的变量对象上找i的值,发现没有后会沿着作用域链查找,找到了全局执行上下文的变量对象,而此时全局执行上下文的变量对象中的i为6,所以每一次都打印的是6了。
JavaScript这门语言是基于词法作用域来创建作用域的,也就是说一个函数的作用域在函数声明的时候就已经确定了,而不是函数执行的时候。
改一下之前的例子:
var scope = 'global scope';
function f() {
console.log(scope)
}
function checkscope() {
var scope = 'local scope';
f();
}
checkscope();
因为JavaScript是基于词法作用域创建作用域的,所以打印的结果是global scope而不是local scope。我们结合上面的作用域链来分析一下:
首先遇到了f函数的声明,此时为其绑定[[scope]]属性:
// 这里就是我们所说的“一个函数的作用域在函数声明的时候就已经确定了”
f.[[scope]] = [
globalContext.VO, // 此时的全局上下文的变量对象中保存着scope = 'global scope';
];
然后我们直接跳过checkscope的执行上下文的创建和执行的过程,直接来到f函数的执行上。此时在函数执行之前初始化f函数的执行上下文:
// 这里就是为什么会打印global scope
fContext = {
scope: [VO, globalContext.VO], // 复制f.[[scope]],f.[[scope]]只有全局执行上下文的变量对象
VO = {
arguments: {
length: 0,
},
},
}
然后到了f函数执行的过程,console.log(scope),会沿着f函数的作用域链查找scope变量,先是去自己执行上下文的变量对象中查找,没有找到,然后去global执行上下文的变量对象上查找,此时scope的值为global scope。
在这里this绑定也可以分为全局执行上下文和函数执行上下文:
总结起来就是,谁调用了,this就指向谁。
这里,根据之前的例子来完整的走一遍执行上下文的流程:
var scope = 'global scope';
function checkscope(s) {
var scope = 'local scope';
function f() {
return scope;
}
return f();
}
checkscope('scope');
首先,执行全局代码,创建全局执行上下文,并且全局执行上下文进入执行上下文栈:
globalContext = {
scope: [globalContext.VO],
VO: global,
this: globalContext.VO
}
ESC = [
globalContext,
]
然后随着代码的执行,走到了checkscope函数声明的阶段,此时绑定[[scope]]属性:
checkscope.[[scope]] = [
globalContext.VO,
]
在checkscope函数执行之前,创建checkscope函数的执行上下文,并且checkscope执行上下文入栈:
// 创建执行上下文
checkscopeContext = {
scope: [VO, globalContext.VO], // 复制[[scope]]属性,然后VO推入作用域链顶端
VO = {
arguments: {
0: 'scope',
length: 1,
},
s: 'scope', // 传入的参数
f: pointer to function f(),
scope: undefined,
},
this: globalContext.VO,
}
// 进入执行上下文栈
ESC = [
checkscopeContext,
globalContext,
]
checkscope函数执行,更新变量对象:
// 创建执行上下文
checkscopeContext = {
scope: [VO, globalContext.VO], // 复制[[scope]]属性,然后VO推入作用域链顶端
VO = {
arguments: {
0: 'scope',
length: 1,
},
s: 'scope', // 传入的参数
f: pointer to function f(),
scope: 'local scope', // 更新变量
},
this: globalContext.VO,
}
f函数声明,绑定[[scope]]属性:
f.[[scope]] = [
checkscopeContext.VO,
globalContext.VO,
]
f函数执行,创建执行上下文,推入执行上下文栈:
// 创建执行上下文
fContext = {
scope: [VO, checkscopeContext.VO, globalContext.VO], // 复制[[scope]]属性,然后VO推入作用域链顶端
VO = {
arguments: {
length: 0,
},
},
this: globalContext.VO,
}
// 入栈
ESC = [
fContext,
checkscopeContext,
globalContext,
]
f函数执行完成,f函数执行上下文出栈,checkscope函数执行完成,checkscope函数出栈:
ESC = [
// fContext出栈
checkscopeContext,
globalContext,
]
ESC = [
// checkscopeContext出栈,
globalContext,
]
到此,一个整体的执行上下文的流程就分析完了。
原文来自:https://segmentfault.com/a/1190000019239879
classList是一个DOMTokenList的对象,用于在对元素的添加,删除,以及判断是否存在等操作。以及如何兼容操作
js的原型链,得出了一个看似很简单的结论。对于一个对象上属性的查找是递归的。查找属性会从自身属性(OwnProperty)找起,如果不存在,就查看prototype中的存在不存在。
arguments是什么?在javascript 中有什么样的作用?讲解JavaScript 之arguments的使用总结,包括arguments.callee和arguments.calle属性介绍。
WebSocket是HTML5下一种新的协议,为解决客户端与服务端实时通信而产生的技术。其本质是先通过HTTP/HTTPS协议进行握手后创建一个用于交换数据的TCP连接
HTML文档在浏览器解析后,会成为一种树形结构,我们需要改变它的结构,就需要通过js来对dom节点进行操作。dom节点(Node)通常对应的是一个标题,文本,或者html属性。
javascript中基本类型指的是那些保存在栈内存中的简单数据段,即这种值完全保存在内存中的一个位置。 引用类型指那些保存在堆内存中的对象,意思是变量中保存的实际上只是一个指针,这个指针指向内存中的另一个位置,该位置保存对象。
apply 、 call 、bind 三者都是用来改变函数的this对象的指向的;第一个参数都是this要指向的对象,也就是想指定的上下文;都可以利用后续参数传参;bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用 。
在js中有句话叫一切皆对象,而几乎所有对象都具有__proto__属性,可称为隐式原型,除了Object.prototype这个对象的__proto__值为null。Js的prototype属性的解释是:返回对象类型原型的引用。每个对象同样也具有prototype属性,除了Function.prototype.bind方法构成的对象外。
Js支持“=”、“==”和“===”的运算符,我们需要理解这些 运算符的区别 ,并在开发中小心使用。它们分别含义是:= 为对象赋值 ,== 表示两个对象toString值相等,=== 表示两个对象类型相同且值相等
js的变量分为2种类型:局部变量和全局变量。主要区别在于:局部变量是指只能在变量被声明的函数内部调用,全局变量在整个代码运行过程中都可以调用。值得注意的js中还可以隐式声明变量,而隐式声明的变量千万不能当做全局变量来使用。
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!