`
abruzzi
  • 浏览: 452719 次
  • 性别: Icon_minigender_1
  • 来自: 西安
社区版块
存档分类
最新评论

JavaScript内核系列 第10章 深入核心概念

阅读更多

 

第十章 核心概念深入

在前半部分章节中,涉及到一些重要的概念,在当时章节上下文中,限于内容,没有展开讨论,这些内容可能较难理解,因此都集中在这个章节进行讨论。具体涉及到的内容有原型链,执行期上下文,活动对象,作用域链以及this值。这部分内容可以结合之前章节中相关部分一起参考。

10.1原型链

10.1.1原型对象与原型链

正如第三章提到的,JavaScript对象是一个属性的集合,另外有一个隐式的对象:原型对象。原型的值可以是一个对象或者null。一般的引擎实现中,JS对象会包含若干个隐藏属性,对象的原型由这些隐藏属性之一引用,我们在本文中讨论时,将假定这个属性的名称为"__proto__"(事实上,SpiderMonkey内部正是使用了这个名称,但是规范中并未做要求,因此这个名称依赖于实现)

由于原型对象本身也是对象,根据上边的定义,它也有自己的原型,而它自己的原型对象又可以有自己的原型,这样就组成了一条链,这个链就是原型链。

JavaScritp引擎在访问对象的属性时,如果在对象本身中没有找到,则会去原型链中查找,如果找到,直接返回值,如果整个链都遍历且没有找到属性,则返回undefined.原型链一般实现为一个链表,这样就可以按照一定的顺序来查找。

 

结合下边的例子:

 

var base = {
    name : "base",
    getInfo : function(){
       return this.name;
    }
}
 
var ext1 = {
    id : 0,
    __proto__ : base
}
 
var ext2 = {
    id : 9,
    __proto__ : base
}
 
print(ext1.id);
print(ext1.getInfo());
print(ext2.id);
print(ext2.getInfo());
 

 

 

可以得到:

 

0
base
9
base

 

 


上例中对象的原型链

 

可以看到,当执行ext1.id时,引擎在ext1对象本身中就找到了id属性,因此返回其值0,当执行ext1.getInfo时,ext1对象中没有找到,因此在其原型对象base中查找,找到之后,执行这个函数,得到输出”base”

 

我们将上例中的ext1对象稍加修改,为ext1对象加上name属性:

 

var base = {
    name : "base",
    getInfo : function(){
       return this.name;
    }
}
 
var ext1 = {
    id : 0,
    name : "ext1",   
    __proto__ : base
}
 
print(ext1.id);
print(ext1.getInfo());
 

 

 

可以看到:

 

0
ext1

 

 

 

这个运行效果同样验证了原型链的运行机制:从对象本身出发,沿着__proto__查找,直到找到属性名称相同的值(没有找到,则返回undefined)

 

我们对上例再做一点修改,来更好的演示原型链的工作方式:

 

var base = {
    name : "base",
    getInfo : function(){
       return this.id + ":" + this.name;
    }
}
 
var ext1 = {
    id : 0,
    __proto__ : base
}
 
print(ext1.getInfo());
 

 

我们在getInfo函数中加入this.id,这个idbase对象中没有定义。同时,删掉了ext1对象中的name属性,执行结果如下:

 

0:base

 

应该注意的是,getInfo函数中的this表示原始的对象,而并非原型对象。上例中的id属性来自于ext1对象,而name来自于base对象。这个特性的机制在10.3小节再做讨论。如果对象没有显式的声明自己的”__proto__”属性,这个值默认的设置为Object.prototype,Object.prototype”__proto__”属性的值为”null”,标志着原型链的终结。

 

10.1.2构造器

我们在来讨论一下构造器,除了上边提到的直接操作对象的__proto__属性的指向以外,JavaScript还支持构造器形式的对象创建。构造器会自动的为新创建的对象设置原型对象,此时的原型对象通过构造器的prototype属性来引用。

 

我们以例子来说明,将Task函数作为构造器,然后创建两个实例task1, task2

 

function Task(id){
    this.id = id;
}
 
Task.prototype.status = "STOPPED";
Task.prototype.execute = function(args){
    return "execute task_"+this.id+"["+this.status+"]:"+args;
}
 
var task1 = new Task(1);
var task2 = new Task(2);
 
task1.status = "ACTIVE";
task2.status = "STARTING";
 
print(task1.execute("task1"));
print(task2.execute("task2"));
 

 

运行结果如下:

 

execute task_1[ACTIVE]:task1
execute task_2[STARTING]:task2

 

 

 

构造器会自动为task1,task2两个对象设置原型对象Task.prototype,这个对象被Task(在此最为构造器)prototype属性引用,参看下图中的箭头指向。




构造器方式的原型链

 

由于Task本身仍旧是函数,因此其”__proto__”属性为Function.prototype, 而内建的函数原型对象的”__proto__”属性则为Object.prototype对象。最后Obejct.prototype”__proto__”值为null.

10.2执行期上下文

执行器上下文的概念贯穿于JavaScript引擎解释代码的全过程,这个概念是一个运行期的概念,执行器上下文一般实现为一个栈。按照ECMAScript的规范,一共有三种类型的代码,全局代码(游离于任何函数体之外),函数代码,以及eval代码(eval接受字符串,并对这个字符串求值,通常来讲,函数式编程都会提供这个函数或者类似的机制)。这三种代码均在自身的执行期上下文中求值。全局上下文仅有一个,函数上下文和eval上下文则可能有多个。

引擎在调用一个函数时,进入该函数上下文,并执行函数体,与其他程序设计语言类似,函数体内可以有递归,也可以调用其他函数(进入另外一个上下文,此时调用者被阻塞,直至返回)。调用eval会有类似的情况。

 

引擎在初始化之后,将golbal的上下文对象压入栈:




执行期上下文栈(初始状态)

 

 

(function(name){
    print("hello, "+name);
})("jack");

 

 

 

执行上边代码的时候,进入函数执行期上下文,将匿名函数执行期上下文压入栈中:

 




进入函数执行期上下文

 

执行完成之后,弹出该上下文对象。既然执行期上下文栈中存放的是执行期上下文对象,那么我们来详细看看这个对象的结构。上文提到,ECMAScript代码有三类,对应的执行期上下文对象也有三类,每个上下文对象都有一些必须的属性用以为执行于其上的代码服务,以及记录/跟踪代码执行状态等。

 

一个典型的上下文对象的结构如下:

 




上下文对象结构

 

当然,根据不同的实现,这个对象可以包含任意其他的属性(上图中的<properties>部分)。每个上下文对象所需要包含的有变量对象,作用域链以及this.这三个属性在不同类型的上下文对象中意义可能不同。

变量对象只是一个抽象概念,在全局上下文中,变量对象是全局变量自身。而在函数上下文中,变量对象表现为活动对象,活动对象将在下一小节展开,对于eval上下文,eval可能使用全局的变量对象,也可能使用函数的变量对象,这取决于其调用的位置。因此可以说,变量对象有两类,全局的变量对象(全局变量global本身或者活动对象,eval使用这两者之一)

 

结合执行器上下文栈和原型对象,我们可以得到下列的示意图:

 




执行器上下文栈及变量对象,原型链示意图

10.3活动对象

JavaScript中,当一个函数被调用的时候,就会产生一个特殊的对象:活动对象。这个对象中包含了参数列表和arguments对象等属性。由于活动对象是变量对象的特例,因此它包含变量对象所有的属性如变量定义,函数定义等。

 

我们来看一个实例:

 

function func(handle, message){
    var id = 0;
    function doNothing(x){
       return x;
    }
    handle(message);
}
 
func(print, "hello");
 

 

当代码执行到func(print, “hello”)时,活动对象被创建,这个活动对象的图形示意如下:




上例中函数调用时的活动对象

 

10.4作用域链

作用域链与原型链类似,也是一个对象组成的链,用以在上下文中查找标识符(变量,函数等)。查找时也与原型链类似,如果激活对象本身具有该变量,则直接使用变量的值,否则向上层搜索,一次类推,知道查找到或者返回undefined。作用域链的主要作用是用以查找自由变量,所谓自由变量是指,在函数中使用的,非函数内部局部变量,也非函数内部定义的函数名,也非形式参数的变量。这些变量通常来自于函数的“外层”或者全局作用域,比如,我们在函数内部使用的window对象及其属性。

 

关于作用域链及自由变量,我们可以来看下面一个例子:

 

 

var topone = "top-level";
 
(function outter(){
    var middle = "mid-level";
   
    (function inner(){
       var bottom = "bot-level";
      
       print(topone+">"+middle+">"+bottom);
    })();
})();

  

 

在函数inner之中,print语句中出现的topone, middle变量就是自由变量。

 



 

上例中的作用域链

 

根据上图我们可以看出,内部函数的作用域链,由两部分:内部函数自身的活动对象,内部函数的一个属性”[[scope]]”,”[[scope]]”的值为其外部函数outter的活动对象,其更外部的全局global对象的变量对象。这样,如果在inner中要使用外部的自由变量,显然可以很方便的沿着作用域链上溯。

事实上,函数的属性”[[scope]]”会在函数对象创建的时候被创建,这个特性在下一小节中讨论,而不论函数的嵌套层次有多深,它的”[[scope]]”总会引用所有的位于其外层的上下文中的变量对象(在函数中,为活动对象)

 

10.5 this

this在之前的章节中做过讨论,在ECMAScript的规范中对this的定义为:this是一个特殊的对象,与执行期上下文相关,因此可以称之为上下文对象。this是执行期上下文对象的一个属性,参见本章10.2小节的图。

由于this是执行期上下文对象的属性,因此在代码中使用this,其值直接从上下文对戏那个中获得,而无需查找作用域链,其值在进入上下文的那个时刻被确定。

 

在全局上下文中,this是全局对象本身:

 

var attribute = "attribute";
 
print(attribute);
print(this.attribute);

 

执行结果为:

 

attribute
attribute

 

 

 

而在函数上下文中,不同的调用方式可以有不同的值。

10.5.1 词法作用域

 

JavaScript中,函数对象的创建和函数本身的执行是完全不同的两个过程:

 

function func(){
    var x = 0;
    print("function func");
}

  

是为函数的创建,而下面这条语句:

 

func();
  

才是函数的执行。

 

所谓词法作用域(静态作用域)是指,在函数对象的创建时,作用域”[[scope]]”就已经建立,而并非到执行时,因为函数创建后可能永远不会被执行,但是作用域是始终存在的。

 

比如在上例中,如果在程序中使用没有调用func(),那么,func对象仍旧是存在的,在内存的结构可能是这样的:

 

func.["[[scope]]"] = global.["variable object"];
  

而当函数执行时,进入函数执行期上下文,函数的活动对象被创建,此时的作用域链是活动对象和”[[scope]]”属性的合成。

 

10.5.2 this的上下文

this值是执行期上下文对象的一个属性(执行期上下文对象包括变量对象,作用域链以及this)。执行期上下文对象有三类,当进入不同的上下文时,this的值会确定下来,并且this的值不能更改。结合前面小节讨论的内容,在执行全局代码时,控制流会进入全局执行期上下文,而在执行函数时,又会有函数执行期上下文。我们来看下面一个例子:


var global = this;
var tom = {
    name : "Tom",
    home : "desine",
    getInfo : function(){
       print(this.name + ", from "+this.home);
    }
};
 
tom.getInfo();
 
var jerry = {
    name : "Jerry",
    getInfo : tom.getInfo
}
 
jerry.getInfo();
 
global.getInfo = tom.getInfo;
global.getInfo();
 

 

执行结果为:

 

Tom, from desine
Jerry, from undefined
undefined, from undefined

  

tom对象本身具有namehome属性,因此在执行tom.getInfo时,会打印tom对象上的这两个属性值。当将global.getInfo属性设置为tom.getInfo时,getInfo中的this值,在运行时,事实上是global对象(还记得在全局执行期上下文对象中,global的变量对象的this值吗?)的变量对象中的this就是自身。而global.nameglobal.home都没有定义,因此会得到上边的结果。

 

从上例中还可以看到,在函数getInfo调用时,在getInfo之前的对象(tom,jerry,global)会被作为this来执行。当然global可以省略,这时仍然是全局对象作为this。应该记住的是,this的值取决于调用函数的方式(当然这里的this指函数上下文中的this,全局上下文的我们已经讨论过了)

 

  • 大小: 8.5 KB
  • 大小: 25.9 KB
  • 大小: 4.5 KB
  • 大小: 3.5 KB
  • 大小: 2.7 KB
  • 大小: 15.6 KB
  • 大小: 3.5 KB
  • 大小: 10.6 KB
分享到:
评论
25 楼 kanny87929 2011-11-16  
楼主的javascript对象都有原型属性这个概念是有问题的

楼主自己都说了,javascript对象分几种,一种是普通的javascript对象,一种是函数对象
这里的函数对象才有一个隐式的prototype属性,这个隐式的prototype属性通常是一个对象。

而普通的javascript对象是没有这个隐式的prototype属性的

比如

function jQuery() {}这个有名的函数,他的对象名称是jQuery,因为他是一个javascript函数对象所以jQuery对象有这个隐式的prototype属性,并且这个属性引用了一个对象,不过这个最初引用的是什么对象我还不清楚。



var j = new jQuery();
以后的这个新j对象,也就是一个普通的javascript对象是没有这个隐式的prototype属性的。
j.prototype或是new jQuery().prototype是undefined

当调用普通j对象的某一个属性或是方法的时候。他就会在jQuery函数对象体内找对应的方法或者属性。因为是jQuery函数对象通过,new 操作符和 自己本身的构造器 jQuery() 实例化出的这个普通j对象
当找到就调用,如果找不到,就去jQuery函数对象隐式的prototype属性上看看,如果这个属性指向了一个新的对象,那么又会去那个新的对象中找,如果指向的对象是一个javascript函数对象,就会还有prototype属性,如果只是一个普通的javascript对象就没有prototype,这样就无法继续持续查找下去。
24 楼 mwei 2011-09-26  
能否将后续的文章整理加到《JavaScript_Core_-_V0.pdf》,非常感谢。
23 楼 stringwave 2011-08-02  
请教个问题,见以下代码:

function A(){}
A.prototype = {
   a:1,
   b:2,
   c:[9]
}

var a1 = new A();
var a2 = new A();

console.log(a1.hasOwnProperty("a"));   //false;  a是从原型继承的属性,非对象自身属性

A.prototype.a = 111;      
console.log(A.prototype.a, a1.a, a2.a); 
// 111 111 111;   说明a1,a2的__proto__都指向A.prototype

a1.a = "a1.a";  //这里不是从原型里找a,而是覆盖掉原型里的属性 ???
                //如果从原型继承的是引用类型的属性,则不会覆盖
a2.a = "a2.a";
console.log(A.prototype.a, a1.a, a2.a); // 111 a1.a a2.a
console.log(a1.hasOwnProperty("a"));   //true;
22 楼 xici_magic 2011-05-07  
不错 追楼主看。
21 楼 kyfxbl 2011-05-06  
受益匪浅,支持
20 楼 lucianovaleria 2011-05-05  
期待楼主将文章继续写下去,学习中。
19 楼 yolio2003 2011-05-05  
楼主的系列不错,期待里程碑版本,到时候一并学习了。支持楼主
18 楼 abruzzi 2011-05-05  
forgetableBoy 写道
楼主是专门研究前端技术的吗 怎么把js研究的这么深入

深入倒谈不上,标题上的“深入”是相对于这个系列之前章节中零散的讨论而言的。我其实是一个后端的开发人员,工作内容主要是Linux下的C语言开发,不过个人非常喜欢动态语言如js,python而已。
17 楼 forgetableBoy 2011-05-05  
楼主是专门研究前端技术的吗 怎么把js研究的这么深入
16 楼 DmYang 2011-05-04  
abruzzi 写道
DmYang 写道
楼主这个系列的文章对我理解js的概念性的东西帮助很大,1-10全看完了,谢谢分享啊。。。。
另外扯个题外话,看到lz所在城市是昆明,我也是云南的,在外读书不了解昆明的情况,想问问在昆明有没有做前端(html/css/js)的呢?如果合适的话毕业后想回去昆明发展。。


我是在这边上的学,所以接触的公司也不多,Jinfonet在做DHTML报表的,其他的就不是很清楚了。


呵呵,这样子啊,那我还得考虑考虑了,谢谢啦。
15 楼 abruzzi 2011-05-04  
DmYang 写道
楼主这个系列的文章对我理解js的概念性的东西帮助很大,1-10全看完了,谢谢分享啊。。。。
另外扯个题外话,看到lz所在城市是昆明,我也是云南的,在外读书不了解昆明的情况,想问问在昆明有没有做前端(html/css/js)的呢?如果合适的话毕业后想回去昆明发展。。


我是在这边上的学,所以接触的公司也不多,Jinfonet在做DHTML报表的,其他的就不是很清楚了。
14 楼 victor71 2011-05-04  
类必须用 function 关键字来定义吗? 还有实例是不是没有 prototype 对象?
13 楼 DmYang 2011-05-03  
楼主这个系列的文章对我理解js的概念性的东西帮助很大,1-10全看完了,谢谢分享啊。。。。
另外扯个题外话,看到lz所在城市是昆明,我也是云南的,在外读书不了解昆明的情况,想问问在昆明有没有做前端(html/css/js)的呢?如果合适的话毕业后想回去昆明发展。。
12 楼 fisherhe 2011-05-03  
前十章内容很赞,后面是否有讲javascript性能和执行效率相关内容?
11 楼 fisherhe 2011-05-03  
LZ,补充一点,针对于初、中级而言,理解javascript内核可能仅停留在思想上,还缺乏一定的实际运用,上面我也提出来了最好有个实际的demo将这些概念贯穿起来,这样可能对具体实践上会有一定帮助。
10 楼 winting 2011-05-03  
xyc1025126 写道
lz用什么工具画的图呢


abruzzi 写道
winting 写道
你好,想请教一下,你那个图是用 什么工具绘画.很漂亮.

你好,图使用graphviz画的,在后续的版本发布的时候,我会把dot脚本也一同发上来。
9 楼 xyc1025126 2011-05-03  
lz用什么工具画的图呢
8 楼 abruzzi 2011-05-03  
fisherhe 写道
速度精华,前九章的内核系列我都看过了,希望楼主下一期配合内核系列并发点实际项目中运用的例子。


好的,现在事实上只完成了一个脚本化Java的实际项目示例,其他的客户端还没有好的实例。我会将你的意见考虑进去。
7 楼 abruzzi 2011-05-03  
Rooock 写道
晕咧. 刚下了1~9章, 才看了一半. 第十章就出来了. 咋不整到一起去..

不好意思,由于这个系列大都是自己在工作之余以及空闲时候做的,不可能一下子就完成,是分章节的。第10,11,12都会是依次整理出来,最后合并为一个里程碑版本(目前是v0.3alpha)。1-9的那个第0版出来的时候,后续的章节还没有写完。
6 楼 fisherhe 2011-05-03  
速度精华,前九章的内核系列我都看过了,希望楼主下一期配合内核系列并发点实际项目中运用的例子。

相关推荐

    Android基础教程

     《Android基础教程(第3版·修订版)》是一部关于Android开发的基础教程,采用Pragmatic系列图书一贯由浅入深、循序渐进的方式讲解了Android程序设计的核心概念和技术。书中不仅结合数独游戏开发案例形象生动地...

    Beginning.Android.4

    #### 一、核心概念 **第一章:大局观** - **Android 生态系统概述**:本章首先介绍了 Android 的整体生态系统,包括其历史背景、发展现状以及未来趋势。 - **Android 设备类型**:讨论了 Android 设备的多样性,...

    Chromium OS 技术分析程序全攻略(程式代碼)

    《Chromium OS 技术分析程序全攻略》一书涵盖了Chromium OS的深入技术解析,主要涉及了操作系统的核心原理、JavaScript编程以及与之相关的应用程序开发。这本书通过一系列的实例代码来帮助读者理解Chromium OS的工作...

    基于PHP的八零学院内核网站系统php版源码.zip

    【标题】"基于PHP的八零学院内核网站系统php版源码.zip" 提供的是一套使用PHP语言开发的在线教育平台的核心源代码。PHP(Hypertext Preprocessor)是一种广泛使用的开源脚本语言,尤其适合Web开发,可嵌入到HTML中...

    开源应用程序架构 二(The Architecture of Open Source Applications 2)

    本章详细介绍了Puppet的工作原理、核心概念以及如何使用它来管理大规模服务器集群。对于系统管理员和DevOps工程师来说,这是一个非常有用的工具。 **19. PyPy** - **作者:** Benjamin Peterson - **内容概览:** ...

    面试大全新-148P.docx

    前端开发是现代互联网应用的核心组成部分,涉及到一系列的技术和概念。以下是一些重要的前端开发知识点,主要涵盖HTML、CSS、JavaScript、AJAX以及新兴框架如React、Vue和Angular等。 1. HTML篇: - Web标准理解:...

    symfony cookbook 2.4

    Bundle 是 Symfony 的核心概念之一,用于组织相关的代码、配置和资源。通过 Composer,可以很容易地安装和使用来自社区的第三方 Bundle。 #### 四、最佳实践与 Bundle 结构 ##### 4.1 如何使用最佳实践构建 Bundle...

    WebKit和Chromium源码及原理剖析.pdf

    #### 第10篇 Chromium的智能指针/引用计数/Callback/Bind - **智能指针**: - 智能指针是C++中的一个重要概念,用于管理内存资源。 - Chromium使用智能指针来避免内存泄漏,确保资源的有效释放。 - **引用计数**...

    面试题总结.docx

    根据给定文件的信息,我们可以提炼出一系列与前端开发相关的知识点,包括但不限于技术概念、编码实践、面试技巧等。下面将围绕这些方面展开详细介绍。 ### 一、箭头函数与普通函数的区别 箭头函数和普通函数的主要...

    giftems.rar

    下面我们将深入探讨Android开发的一些核心概念和关键点。 1. **Android系统架构**:Android系统分为四个主要层次:Linux内核、硬件抽象层(HAL)、系统运行库和应用程序框架。Linux内核为Android提供了基础支持,...

    ChromiumFX依赖包

    ChromiumFX是Chromium Embedded Framework (CEF) 的C#封装,而CEF则是一个跨平台的库,用于将Chromium内核集成到各种应用程序中。 在"ChromiumFX依赖包"中,我们可以探讨以下几个关键知识点: 1. **C#编程语言**:...

    班级课表 (1)(1).pdf

    操作系统实验让学生亲手实践操作系统的核心概念,比如通过编写简单的系统调用来理解内核工作,或者通过模拟器进行进程调度和内存分配的模拟。 这些课程涵盖了计算机科学与技术专业的重要领域,为学生提供了全面的...

    android开发的webapp--资源分析系统apk

    首先,我们需要了解WebAPP的基本概念。WebAPP是运行在移动设备浏览器中的应用,它们不需要通过应用商店下载安装,而是通过浏览器访问网址即可使用。在Android平台上,WebAPP可以通过WebView组件来实现,WebView是...

    Android 谷歌开源项目

    8. **Android开源社区**:Android的开源性吸引了大量开发者和社区参与,例如XDA Developers论坛、LineageOS等,他们贡献了大量的第三方ROM、应用和修改,丰富了Android的生态多样性。 9. **Android NDK**:对于需要...

    web前端面试题

    - **Webkit**: Safari 和 Chrome 的核心内核。 **6. 渲染引擎与 JS 引擎的理解** - **渲染引擎**: 负责解析网页内容并计算显示方式,如 HTML、CSS 等。 - **JS 引擎**: 解析和执行 JavaScript 代码,实现网页动态...

    Thinkphp5 大型程序员交流博客系统源码.zip

    4. **库目录(Library)**:Thinkphp5的框架核心和第三方类库。 5. **路由文件(Route.php)**:定义了URL到控制器的映射规则。 6. **入口文件(index.php)**:项目的主入口,所有请求都将通过这个文件转发到相应...

Global site tag (gtag.js) - Google Analytics