最近在看汤姆大叔的"深入理解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]();//
data[1]();//
data[2]();//

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

如下:

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

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

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

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

    JavaScript 面向对象(一) —— 基础篇 JavaScript 面向对象(二) —— 案例篇 一.json方式的面向对象 首先要知道,js中出现的东西都能够放到json中.关于json数据格 ...

  2. JavaScript 面向对象(二) —— 案例篇

    看案例前可以先看看基础篇:JavaScript 面向对象(一) —— 基础篇 案例——面向对象的选项卡:把面向过程的程序一步步改成面向对象的形式,使其能够更加的通用(但是通用的东西,一般会比较臃肿). ...

  3. JavaScript 面向对象(一) —— 基础篇

    学好JS的面向对象,能很大程度上提高代码的重用率,像jQuery,easyui等,这篇博客主要从细节上一步步讲JS中如何有效地创建对象,也可以看到常见的创建对象的方式,最后也会附上一些JS面向对象的案 ...

  4. 曲线参数化的Javascript实现(代码篇)

    在曲线参数化的Javascript实现(理论篇)中推出了曲线弧长积分的公式,以及用二分法通过弧长s来查找样条曲线上对应的u,再求Q(u)的值.弧长积分函数如下: ,其中-----公式1 Simpson ...

  5. 深入理解javascript函数系列第一篇——函数概述

    × 目录 [1]定义 [2]返回值 [3]调用 前面的话 函数对任何一门语言来说都是一个核心的概念.通过函数可以封装任意多条语句,而且可以在任何地方.任何时候调用执行.在javascript里,函数即 ...

  6. 深入理解javascript函数系列第二篇——函数参数

    × 目录 [1]arguments [2]内部属性 [3]函数重载[4]参数传递 前面的话 javascript函数的参数与大多数其他语言的函数的参数有所不同.函数不介意传递进来多少个参数,也不在乎传 ...

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

    × 目录 [1]词法 [2]动态 前面的话 大多数时候,我们对作用域产生混乱的主要原因是分不清楚应该按照函数位置的嵌套顺序,还是按照函数的调用顺序进行变量查找.再加上this机制的干扰,使得变量查找极 ...

  8. JavaScript 现状:方言篇

    导读 JavaScript 和其他编程语言有一个很大的不同,它不像单纯的一个语言,而像一个由众多方言组成大家族.从 2009 年 CoffeeScript 出现开始,近几年出现了大量基于 JavaSc ...

  9. 深入理解javascript作用域系列第二篇

    前面的话 大多数时候,我们对作用域产生混乱的主要原因是分不清楚应该按照函数位置的嵌套顺序,还是按照函数的调用顺序进行变量查找.再加上this机制的干扰,使得变量查找极易出错.这实际上是由两种作用域工作 ...

随机推荐

  1. JS区分移动端和PC

    var ua = navigator.userAgent.toLowerCase(); if (ua.match(/MicroMessenger/i) == "micromessenger& ...

  2. bootstrap之div居中

    bootstrap之div居中 偏移列 偏移是一个用于更专业的布局的有用功能.它们可用来给列腾出更多的空间.例如,.col-xs=* 类不支持偏移,但是它们可以简单地通过使用一个空的单元格来实现该效果 ...

  3. 使用系统自带的GCD的timer倒计时模板语句遇到的小坑。。

    今天折腾了下系统gcd的 但是如果不调用这句dispatch_source_cancel()那么这个timer根本不工作....解决方法如下: 实现一个倒计时用自带的gcd如此简洁.. 原因可能是如果 ...

  4. SSH整合,必出精品

    SSH:顾名思义(spring,struts2,hirbernate)  Struts(表示层)+Spring(业务层)+Hibernate(持久层) Struts是一个表示层框架,主要作用是界面展示 ...

  5. Jenkins中构建Testcomplete项目的方法介绍

    Jenkins的部署在上一篇随笔中已经和大家介绍了,下面我们介绍一下再Jenkins中构建testcomplete项目.我这里使用的是Testcomplete11,下面详细介绍一下构建步骤. 1.Je ...

  6. nginx超时重发

    最近一直遇到一个bug: 客户端会二次请求服务端,服务端多次调用remote服务. 特点是,这些请求都是模型切片相关的,耗时很长的请求,往往需要1分钟左右. 开始以为是客户端代码有问题,进行了二次请求 ...

  7. SQL Server定时自动抓取耗时SQL并归档数据发邮件脚本分享

    SQL Server定时自动抓取耗时SQL并归档数据发邮件脚本分享 第一步建库和建表 USE [master] GO CREATE DATABASE [MonitorElapsedHighSQL] G ...

  8. HTTPS工作原理

    HTTPS是什么 HTTPS全称为Hypertext Transfer Protocol over Secure Socket Layer,及以安全为目标的HTTP通道,简单说就是HTTP的安全版本. ...

  9. 表格搞定 Asp.net Web 状态管理

    最近在网上搜罗了 ASP.NET WEB 状态管理方面的一些内容,终于把这些内容整合总结了一下. 1. 希望自己通过整理,能够掌握一些,为自己投资. 2. 以便自己忘记,又要浪费时间搜罗. 3. 希望 ...

  10. Azure PowerShell (6) 设置单个Virtual Machine Endpoint

    <Windows Azure Platform 系列文章目录> 请注意: - Azure不支持增加Endpoint Range - 最多可以增加Endpoint数量为150 http:// ...