JavaScript之自我总结篇

最近在看汤姆大叔的"深入理解JavaScript系列",写得真的不错,对于我而言特别是12章到19章,因为大叔研究的点,就主要是从底层来研究JavaScript为什么会出现钟种特有的语言现象,所以学习了大叔的文章后,自己对JavaScript的认知也更明白了,以前好多地方是知其然而不知其所以然,你要问我JavaScript为什么会出现这些现象,我也只能说这是它语言本身的特性嘛。

以下是我初次看了大叔Javascript系列(12到19章)一时不能理解的点,但经过细细品味后才豁然开朗。

所以,自我总结如下:

注:总结中掺杂了个人的观点以及理解层度,所以有什么错误的地方,还请不吝指教。

一、深入理解JavaScript系列(12)之变量对象: 

在大叔这章中,大叔提到了一个概念就是‘变量对象(varibale object)’

‘变量对象’,是与执行上下文有关的,因为JavaScript在执行表达式时,总得知道相应的变量存储在哪吧?不然怎么获取或改变对应的变量值呢?

所以引入了一个‘变量对象’的概念。

都说了是对象嘛,就是以键值对的方式,存储到变量对象中咯。

1、 在进入执行上下文时,‘变量对象’VO

(1)会将函数的所有形参(如果我们是在函数执行上下文),以形参名和其对应的值作为变量对象的属性,如果形参没有对应的值,就是undefined咯。

(2)会将所有函数声明,以函数名和对应的函数对象作为变量对象的属性。如果,变量对象中已经存在了相同名称的属性,就完全替代。

(3)会将所有变量声明,以变量名和其对应值(undefined)作为变量对象的属性。如果变量名称与上述(1)、(2)中的形参名或函数名撞车了,则变量声明不会去影响        存在的这类属性。

且,在上面提到,变量对象与执行上下文有关,那么它们究竟什么关系呢?

分两种情况:

一种情况就是在进入任何执行上下文之前就创建的对象,此乃全局对象(Global object)global;

另一种就是,我们都知道在JavaScript中作用域是以函数function为基准的,所以函数的变量对象其实就是执行上下文对象;

具体见以下两幅图:

图一

图二

注:函数中的变量对象是不能访问的。

那为什么全局中的变量对象能访问呢?

因为可以通过this或者window,因为window是全局变量global的一个属性,且引用了global。这也就是为什么在全局变量中访问标识符时,用this或者直接访问标识符时,会比用window快的原因。

2、 在执行代码时

变量对象VO,已经在进入上下文时,做了预处理。So,接下来就是根据具体的代码改变对应的值了。

二、深入理解JavaScript系列(13)之This:

说到this,简单点嘛就是由调用者决定的,谁调用的就是谁。

如下:

function fn(){
    console.log(this);
};
var foo = {
    bar: fn
};
//输出的this指向foo
foo.bar();

但JavaScript底层到底是个怎样的处理机制呢?

在大叔的这章中,引入了一个‘引用类型(Reference type)’的概念。

引用类型(Reference type),使用伪代码可以将其表示为拥有两个属性的对象:

----base,即拥有属性的那个对象;

----propertyName,即属性名,从而可以获取相应的值。

如下:

且,要返回引用类型的值,只存在两种情况:

1、  处理一个标识符时;

2、  处理属性访问器( . 或 [ ] )时.

注意:只有两种情况哦,要从引用类型中得到一个属性值嘛,还需要一步,就是底层调用GetValue方法,从‘引用类型’对象中得到对应属性值的值。

咦,讲了这么多和this有什么相关?

直接摘至大叔:

好了,如果理解了上面的流程,下面的几个例子中this也就OK啦。

‘use strict‘;
var foo = {
    bar: function(){
        console.log(this);
    }
};
foo.bar();//this为foo(foo.bar)();//this为foo
(foo.bar = foo.bar)();//this为undefined
(false || foo.bar)();//this为undefined
(foo.bar, foo.bar)();//this为undefined
三、深入理解JavaScript系列(14)之作用域链:

作用域链是上下文所有‘变量对象(varibale object)’的列表,提到‘变量对象’,so此链用来变量查询。且函数上下文的作用域链在函数调用时创建,包含活动对象和这个函数内部的[[scope]]属性,而这个[[scope]]属性是所有父变量对象的层级链,在函数创建时存在其中,且不会改变,即,函数一旦创建了,无论你调或不调用,[[scope]]已存储在函数对象中了。

当函数被调用时,进入执行上下文activeExecutionContext,其中包含Scope属性,即作用域链。

作用域链(Scope)在上下文中具体见下:

activeExecutionContext = {
    VO:{...},//or AO
    this: thisValue,
    Scope/*Scope chain*/: [
        AO + [[Scope]]
    ]
}

Scope又包含变量对象和函数的 [[scope]]属性。

当我们解析一个标识符(标识符是变量名,函数名,函数参数名和全局对象中未识别的属性名)时,解析过程将沿着这条链(Scope)去查找,查到就返回它的值,如果在Scope这条链中,没有找到相应的值,就会沿着全局对象这条原型链去查找,因为函数活动对象没有原型。

例子如下:

‘use strict‘;
function foo(){
    function bar(){
        console.log(x);
    };    bar();
};
Object.prototype.x = 10;
this.__proto__.x = 200;
foo();//x为200

另外:通过函构造函数创建的函数的[[scope]]属性总是唯一的全局对象.

四、深入理解Javascript系列(15)之函数:

函数声明与函数表达式的区别:

函数声明,在‘变量对象’章中已经知道,在进入执行上下文时,会将所有的函数声明以键值对的形式存储到变量对象VO中。

但,

函数表达式不会添加到变量对象VO中,且在代码执行阶段创建,用完后立刻销毁。命名函数表达式也一样哦,因为它是表达式嘛。

例如:

<!DOCTYPE html>
    <head>
        <title>JavaScript</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    </head>
    <body>
        <script>
            ‘use strict‘;
            (function foo(x){
                console.log(x);
            }(1));
            foo(2);//此时会报错:foo is not defined
        </script>
    </body>
</html>

执行以上代码,chrome效果图如下:

和函数表达式不一样么,用完即刻销毁。那命名函数表达式有什么用呢?

命名函数表达式,可以通过名称递归自己嘛。

但,刚才不是说命名函数表达式和函数表达式都不会添加到变量对象VO中吗?那它怎么通过名称自己调用自己的?

当解释器在代码执行阶段,遇到命名的函数表达式,解释器将创建一个辅助的特定对象,并添加到当前作用域链的最顶端。然后创建函数表达式,然后将命名函数表达式的名字添加到这个辅助的特定对象中,且值为该函数引用,当命名函数表达式在其自身调用时,它就在这个特定的对象中找到自己。最后,当命名函数表达式执行完成后,从父作用域链中移除那个辅助的特定对象。

具体算法见下:

五、深入理解JavaScript系列(16)之闭包:

因为作用域链,从而使得所有的函数都是闭包。

但,有一类函数比较特殊,那就是通过Function构造器创建的函数,因为其[[Scope]]只包含全局对象。

ECMAScript中,闭包指的是:

1、从理论角度:所有的函数。

因为它们都在创建的时候,就将上层上下文的数据保存起来了。哪怕是最简单的全局变量也是如此,因为函数中访问的全局变量就相当于是访问自由变量,这个时候使用最外层     的作用域。

2、从实践角度:以下函数才算闭包:

(1)、即时创建它的上下文已经销毁,它任然存在(比如,内部函数从父函数中返回);

(2)、在代码中引用了自由变量。

给出一段经典的代码:

var data = [];
for( var k = 0; k < 3; k++){
    data[k] = function(){
        alert(k);
    };
};
data[0]();// 3,而不是0
data[1]();// 3,而不是1
data[2]();// 3,而不是2

常用的解决方法是,通过闭包,如下:

var data = [];
for( var k = 0; k < 3; k++){
    data[k] = (function _helper(x){
        return function(){
            alert(x);
        };
    })(k);//传入"k"值
};
//现在结果正确了
data[0]();// 0
data[1]();// 1
data[2]();// 2

除了闭包,我们还可以怎么解决呢?

如下:

var data = [];
for( var k = 0; k < 3; k++){
    (data[k] = function(){
        alert(arguments.callee.x);
    }).x = k;//将k作为函数的一个属性
};
//结果也是对的
data[0]();// 0
data[1]();// 1
data[2]();// 2

但是arguments.callee在ECMAScript5中的严格模式下是不能用的,所以我们可以用命名函数表达式来做。

如下:

var data = [];
for( var k = 0; k < 3; k++){
    (data[k] = function foo(){
        alert(foo.x);
    }).x = k;//将k作为函数的一个属性
};
//结果也是对的
data[0]();// 0
data[1]();// 1
data[2]();// 2
六、其他:

ECMAScript中将对象作为参数传递的策略——按共享传递:修改参数的属性将会影响到外部,而重新赋值将不会影响到外部对象。

其实不仅是参数传递,其他都是这样的策略(对象都是赋予地址值)。

如下:

var foo = {};
//b其实是添加到function对象中的,而不是foo.a中的,因为foo.a只是引用了function,即得到了它的地址而已
(foo.a = function(){

}).b = 10;
//得到10
console.log(foo.a.b);
//将foo.a赋值予变量c
var c = foo.a;
//改变foo.a的值,如一个空对象
foo.a = {};
//输出c.b,还是得到10
console.log(c.b);

数组也是一个对象,所以如果我将数组中的元素指向带有this的函数,那么其指向的是数组对象。

//声明变量arr,并赋值为数组
var arr = [];
//给arr数组的第一二个元素赋值
arr[0] = function(){
    console.log(this);
    //因为this指向的是arr对象,所以this[‘1‘]或this[1],就相当于arr[1]
    this[‘1‘]();
};
arr[1] = function(){
    console.log("I‘m 1 ");
};
//this指向的是arr对象
arr[0]();

好了,时间也不早了,晚安~

时间: 2024-10-01 03:04:14

JavaScript之自我总结篇的相关文章

深入理解JavaScript系列(结局篇)(转载)

深入理解JavaScript系列(结局篇) 介绍 最近几个月忙得实在是不可开交,终于把<深入理解JavaScript系列>的最后两篇“补全”了,所谓的全是不准确的,因为很多内容都没有写呢,比如高性能.Ajax安全.DOM详解.JavaScript架构等等.但因为经历所限,加上大叔希望接下来写点其它东西,所以此篇文字就暂且当前完结篇的总结吧,以后有时间的话,可以继续加上一些未涉及的专题内容. 网络文章来源 本系列文章参考了大量的互联网网站,在此向各位网站拥有者.博主.提到的以及未提到的作者们说一

JavaScript 面向对象(三) —— 高级篇

JavaScript 面向对象(一) —— 基础篇 JavaScript 面向对象(二) —— 案例篇 一.json方式的面向对象 首先要知道,js中出现的东西都能够放到json中.关于json数据格式这里推荐一篇博客:JSON 数据格式 先看下json创建的简单对象:相比基础篇中的构造函数.原型等的创建方式,json方式简单方便:但是缺点很明显,如果想创建多个对象,那么会产生大量重复代码,不可取. JSON方式适用于只创建一个对象的情况,代码简介又优雅. 1 <!DOCTYPE html>

ArcGIS API for JavaScript中Symbol符号篇

ArcGIS API for JavaScript中Symbol简介: Symbol定义了arcgis中graphics图层内几何图形(geometry)的显示样式 ArcGIS API for JavaScript中Symbol分为四大类:继承关系如下图所示: 1.点符号(MarkerSymbol):点符号(point)的显示样式 2.线符号(LineSymbol):线符号(line)的显示样式 3.面符号(FillSymbol):面符号(polygon)的显示样式 4.文本符号(TextSy

JavaScript 现状:方言篇

导读 JavaScript 和其他编程语言有一个很大的不同,它不像单纯的一个语言,而像一个由众多方言组成大家族.从 2009 年 CoffeeScript 出现开始,近几年出现了大量基于 JavaScript 语言,或者叫方言,例如 ES6.TypeScript.Elm 等等.它们都有自己的优势,且都可以被完美编译成标准 JavaScript. 所以,继上周的前端框架篇,今天带来 JavaScript 现状之方言篇,看一下大家对于 JavaScript 的方言是怎么选择的. [success]

深入理解javascript作用域系列第二篇——词法作用域和动态作用域

× 目录 [1]词法 [2]动态 前面的话 大多数时候,我们对作用域产生混乱的主要原因是分不清楚应该按照函数位置的嵌套顺序,还是按照函数的调用顺序进行变量查找.再加上this机制的干扰,使得变量查找极易出错.这实际上是由两种作用域工作模型导致的,作用域分为词法作用域和动态作用域,分清这两种作用域模型就能够对变量查找过程有清晰的认识.本文是深入理解javascript作用域系列第二篇——词法作用域和动态作用域 词法作用域 第一篇介绍过,编译器的第一个工作阶段叫作分词,就是把由字符组成的字符串分解成

深入理解javascript作用域系列第一篇——内部原理

× 目录 [1]编译 [2]执行 [3]查询[4]嵌套[5]异常[6]原理 前面的话 javascript拥有一套设计良好的规则来存储变量,并且之后可以方便地找到这些变量,这套规则被称为作用域.作用域貌似简单,实则复杂,由于作用域与this机制非常容易混淆,使得理解作用域的原理更为重要.本文是深入理解javascript作用域系列的第一篇——内部原理 内部原理分成编译.执行.查询.嵌套和异常五个部分进行介绍,最后以一个实例过程对原理进行完整说明 编译 以var a = 2;为例,说明javasc

javascript之Array基础篇

整理了 Array 中很基础的要掌握的知识点,希望可以帮助初学者,也希望自己以后多用多融会贯通. 创建数组 使用Array构造函数: var a=new Array();//创建一个空数组 var a=new Array(20);//创建给定数值的项数的数组 var a=new Array("a","b","c");//包含3个字符串值的数组 var a=Array();//可以省略new关键字 var a=Array(3); 给构造函数传递一个

数据结构与算法javascript描述笔记--数组篇

数组的定义: JavaScript 中的数组是一种特殊的对象,用来表示偏移量的索引是该对象的属性,索引可能是整数.然而,这些数字索引在内部被转换为字符串类型,这是因为 JavaScript 对象中的属性名必须是字符串.在内部被归类为数组.由于 Array 在 JavaScript 中被当作对象,因此它有许多属性和方法可以在编程时使用. 使用数组: 1.创建数组 ① 使用 [] 操作符 ,var arr=[] ,该方法效率最高. ② 调用 Array 的构造函数创建数组,var myArr=new

翻阅《数据结构与算法javascript描述》--数组篇

导读: 这篇文章比较长,介绍了数组常见的操作方法以及一些注意事项,最后还有几道经典的练习题(面试题). 数组的定义: JavaScript 中的数组是一种特殊的对象,用来表示偏移量的索引是该对象的属性,索引可能是整数.然而,这些数字索引在内部被转换为字符串类型,这是因为 JavaScript 对象中的属性名必须是字符串.在内部被归类为数组.由于 Array 在 JavaScript 中被当作对象,因此它有许多属性和方法可以在编程时使用. 使用数组: 1.创建数组 使用 [] 操作符 ,var a