最近在看汤姆大叔的"深刻理解JavaScript系列",写得真的不错,对于我而言特别是12章到19章,由于大叔研究的点,就主要是从底层来研究JavaScript为何会出现钟种特有的语言现象,因此学习了大叔的文章后,再结合《高程》,本身对JavaScript的认知也更明白了,之前好多地方是知其然而不知其因此然,你要问我JavaScript为何会出现这些现象,我也只能说这是它语言自己的特性嘛。html
如下是看了大叔Javascript系列(12到19章)的自我总结。算法
注:总结中掺杂了我的的观点以及理解层度,因此有什么错误的地方,还请不吝指教。 chrome
1、深刻理解JavaScript系列(12)之变量对象: |
在大叔这章中,大叔提到了一个概念就是‘变量对象(varibale object)’。数组
‘变量对象’,是与执行上下文有关的,由于JavaScript在执行表达式时,总得知道相应的变量存储在哪吧?否则怎么获取或改变对应的变量值呢?闭包
因此引入了一个‘变量对象’的概念。函数
都说了是对象嘛,就是以键值对的方式,存储到变量对象中咯。学习
一、 在进入执行上下文时,‘变量对象’VO:ui
(1)会将函数的全部形参(若是咱们是在函数执行上下文),以形参名和其对应的值做为变量对象的属性,若是形参没有对应的值,就是undefined咯。this
(2)会将全部函数声明,以函数名和对应的函数对象做为变量对象的属性。若是,变量对象中已经存在了相同名称的属性,就彻底替代。spa
(3)会将全部变量声明,以变量名和其对应值(undefined)做为变量对象的属性。若是变量名称与上述(1)、(2)中的形参名或函数名撞车了,则变量声明不会去影响 存在的这类属性。
且,在上面提到,变量对象与执行上下文有关,那么它们究竟什么关系呢?
分两种状况:
一种状况就是在进入任何执行上下文以前就建立的对象,此乃全局对象(Global object)global;
另外一种就是,咱们都知道在JavaScript中做用域是以函数function为基准的,因此函数的变量对象其实就是执行上下文对象;
具体见如下两幅图:
图一
图二
注:函数中的变量对象是不能访问的。
那为何全局中的变量对象能访问呢?
由于能够经过this或者window,由于window是全局变量global的一个属性,且引用了global。这也就是为何在全局变量中访问标识符时,用this或者直接访问标识符时,会比用window快的缘由。
二、 在执行代码时
变量对象VO,已经在进入上下文时,作了预处理。So,接下来就是根据具体的代码改变对应的值了。
2、深刻理解JavaScript系列(13)之This: |
说到this,简单点嘛就是由调用者决定的,谁调用的就是谁。
以下:
function fn(){ console.log(this); }; var foo = { bar: fn }; //输出的this指向foo foo.bar();
但JavaScript底层究竟是个怎样的处理机制呢?
在大叔的这章中,引入了一个‘引用类型(Reference type)’的概念。
引用类型(Reference type),使用伪代码能够将其表示为拥有两个属性的对象:
----base,即拥有属性的那个对象;
----propertyName,即属性名,从而能够获取相应的值。
以下:
且,要返回引用类型的值,只存在两种状况:
一、 处理一个标识符时;
二、 处理属性访问器( . 或 [ ] )时.
注意:只有两种状况哦,要从引用类型中获得一个属性值嘛,还须要一步,就是底层调用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
3、深刻理解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]]属性老是惟一的全局对象.
4、深刻理解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中吗?那它怎么经过名称本身调用本身的?
当解释器在代码执行阶段,遇到命名的函数表达式,解释器将建立一个辅助的特定对象,并添加到当前做用域链的最顶端。而后建立函数表达式,而后将命名函数表达式的名字添加到这个辅助的特定对象中,且值为该函数引用,当命名函数表达式在其自身调用时,它就在这个特定的对象中找到本身。最后,当命名函数表达式执行完成后,从父做用域链中移除那个辅助的特定对象。
具体算法见下:
5、深刻理解JavaScript系列(16)之闭包: |
由于做用域链,从而使得全部的函数都是闭包。
但,有一类函数比较特殊,那就是经过Function构造器建立的函数,由于其[[Scope]]只包含全局对象。
ECMAScript中,闭包指的是:
一、从理论角度:全部的函数。
由于它们都在建立的时候,就将上层上下文的数据保存起来了。哪怕是最简单的全局变量也是如此,由于函数中访问的全局变量就至关因而访问自由变量,这个时候使用最外层 的做用域。
二、从实践角度:如下函数才算闭包:
(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
6、其余: |
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]();
好了,时间也不早了,晚安~