Showing posts with label JavaScript. Show all posts
Showing posts with label JavaScript. Show all posts

August 18, 2007

OOP 诡异教程(下)

这是最终确定的 JavaScript 基于消息传递编程风格的文章“OOP 诡异教程(上)”的下篇。原文地址:http://let-in.blogspot.com/2007/06/oop.html。原来的想法是以风格开头,谈到 JavaScript 的内部机制,但作者 lichray 迟迟没有动键盘,认为不如利用已有的风格做一套机制出来,这样可能更有意义。于是,就有了这个更加“诡异”的下篇。

四. 扩展的实现
上文最后给出了一个“看上去很美”的基于消息传递的编程风格,比如构造一个 People 类的代码类似:

function People () {
  var money = 0
  function setMoney (dollars) {
    money = dollars
  }
  function pay (dollars) {
    money -= dollars
  }
  return (function (verb) {
    return eval(verb)
  })
}

有了这样的语法我们就可以描述不少句子了。但是存在一个问题:现实中的 Objects 之间是存在关系的——比如,forrest 是个 IQ 为 75 的傻子,傻子是 People 的一种。而我们仅仅是生搬硬套了一种语法而割裂了这种 "is-a" 关系。现在我们的工作,目的之一就是让这样一个“真切”的世界从我们已有的编程风格的地基上拔地而起。
到底应该怎样做才能使 Fool 产生的对象都能响应 People 的消息呢?我们要给 Fool 产生的对象(也就是返回的那个匿名函数啦)都添加这样一种能力:如果在 Fool 中响应不了消息,那就反馈给 People 响应。

function Fool (iq) {
  var IQ = iq || 0
  function init (iq) {
    IQ = iq
  }
  return (function (verb) {
    try {
      return eval(verb)
    } catch (e) {
      return People()(verb)
    }
  })
}

js> forrest = Fool()
js> forrest('init')(75)
js> forrest('IQ')
75
js> forrest('money')
0

五. 语法扩展和代码生成
这下代码量增加了很多,强迫潜在的使用者们在创建每个类时都这样写那实在是令人抓狂。本来这篇文章应该不提此类问题的解决,但考虑到有益于读者理解“机制”这个抽象概念,这里给出一个可行的方案——把普通的类代码用 Function() 函数重编译为可用的 JavaScript 函数。也就是说,我们能给出类扩展的代码并指定被扩展的类来获取类似上文的代码:

Fool = extend('People()', function (iq){
  var IQ = iq || 0
  function init (iq) {
    IQ = iq
  }
})

为了方便字符串操作,我们希望编译后的代码的参数部分(如 People())都集中出现在一个位置且尽可能便于定位。在函数头添加一句

var origin = People()

当然是可行的,这样还能使 Fool 内部显式引用到其超类。但这样还不够漂亮。我们修改编译后的样例代码为:

function () {
  return (function (origin) {
    var IQ = 0
    function init (iq) {
      IQ = iq
    }
    return (function (verb) {
      try {
        return eval(verb)
      } catch (e) {
        return origin(verb)
      }
    })
  })(People())
}

这个利用参数传递变量的小技巧不值得学习,实际效率不高。但在这篇文章中,这样绑定特殊变量的技术是标准方案。
那么,extend() 函数的实现为:

function extend (originc, code) {
  function argsArea (code) {
    // 题外话,正则表达式也有不值得使用的时候
    return code.slice(code.indexOf('(')+1, code.indexOf(')'))
  }
  function bodyCode (code) {
    // 不用 trim() 了,没事儿找事儿
    return code.slice(code.indexOf('{')+1, code.lastIndexOf('}'))
  }
  function format (body) {
    var objc = bodyCode(function () {
      return (function (verb) {
        try {
          return eval(verb)
        } catch (e) {
        return origin(verb)
        }
      })
    }.toString())
    return 'return (function (origin) {'+body+objc+'})('+originc+')'
  }
  var $ = code.toString()
  return Function(argsArea($), format(bodyCode($)))
}

这样前文提到过的 extend 的实例代码就可以正常运行了,测试代码不再重复。

六. 机制完备化
这样,我们的基于消息传递编程风格的一套面向对象机制就确定下来了。机制是宪法,是语言的根本大法,有了它,我们就可以通过修改代码生成器,很快地给这套机制进行完备化。
想法有很多,例子只举两个。
第一个例子:类的定义中应该能直接引用到将产生的对象 self。答案只有一句话:把返回的那个作为对象的匿名函数命名为 self。
第二个例子:既然是单继承模式,应当存在一个顶层类 AbsObj,使没有指定继承的类自动继承它。答案也只有一句话:在 extend 函数体第一行添加代码:

if (arguments.length == 1) {
  code = originc
  originc = 'AbsObj()'
}

然后手工构造设计 AbsObj 类,为空也无所谓。不过当然了,一般都会给顶层类添加一些全局性质的消息绑定。由于是“底层操作”,基本上都需要修改 extend 函数。做了一个简单的:

function AbsObj () {
  //检测是否能响应此 verb,要再用一次异常处理
  function canHandle(verb){
    try {
      // 别担心这里的 self 会传递不过去
      self(verb)
    } catch (e) {
      return false
    }
    return true
  }
  function toString() {} // 这个搞起来其实很麻烦~`
  var self = function (verb) {
    return eval(verb)
  }
  return self
}

js> Obj=extend(function(){x=5})
js> o=Obj()
js> o('canHandle')('x')
true
js> o('canHandle')('y')
false

文章写完了,小结一下。消息传递的编程不仅仅是一种代码风格,还可以成长为一种完备的机制。这种完备性远不只是这两篇加起来不到300行的文章所能覆盖的(例如非常彻底的“万物皆对象”,因为只要是能响应消息的函数,连接一下 AbsObj 就是合法对象了;类,函数都可以),大家可以试着玩一玩,顺便体会一下这个计算模型的透明和强大。
另外,熟悉函数式编程的朋友可以帮忙思考一下:这样一个基于闭包变换的计算模型实质上是函数式的,再配合动态的函数式的对象级继承(用一个匿名类代换一下)就能在纯 FP 真正下实现 OOP 了。可惜的是每一次更新操作都要重新生成对象,性能代价大了点,不知道大家有什么好想法。

July 27, 2007

functional.js 介绍及源码分析

作者 lichray 对刚刚在网络上现身的 JavaScript 函数式编程库 functional.js 进行了详尽的解读。

functional.js 是模仿 Haskell 语言标准库 Prelude 制作的函数式编程库,主要实现了:

  • 扩展的克里化函数
  • 运算符函数化
  • 紧缩的匿名函数语法
  • 无须指定参数的匿名函数语法
  • 函数向导语法
  • 基本的通用列表操作
  • 部分扩展基于对象化

其中,扩展语法由字符串表示。未能实现的特性有:

  • 尾递归优化
  • 模式匹配(包括参数匹配、列表匹配、情况分析)
  • 惰性运算(包括无穷列表)
  • 列表领悟
  • 扩展绑定、同时绑定
  • 其它列表操作(以及对于列表操作的基于对象化)

下面我们一边分析源代码,一边讲解库的用法。

一、库安装和概览
functional.js 库的所有用户级操作分为3个部分:

  1. 全局操作,绑定在全局对象 Functional 上,主要是高阶函数操作和列表操作,所谓库安装即把这些内容可选的、安全地复制到全局环境
  2. 函数扩展,实现特殊高阶函数特性的工具(特供内部)
  3. 语法扩展,绑定在 String.prototype 上,负责将字符串表示的 lambda 语法翻译为相应的高阶函数(特供内部)

下面是安装函数 Functional.install 的源代码(中文注释为笔者所加,下同):

Functional.install = function(except) { // except 参数是一个对象,不加载这些操作
var source = Functional,
   target = window; // 复制操作到全局环境 window,仅限于浏览器环境
for (var name in source)
   name == 'install' // 当然,不能把 install 复制到 source
   || name.charAt(0) == '_' // 命名开头为 _,私有属性
   || except && name in except
   || {}[name] // work around Prototype
   || (target[name] = source[name]);
}

一般只要执行 Functional.install() 一句即可。

二、高阶函数操作
1. Functional.compose ([Function]) // 匿名的参数类型指的是 arguments 的类型,下同
  接受一列参数个数被认为相等的(允许 Currying 算子)函数为参数,返回一个函数,它接受一定的参数,能够对它们累积倒序 apply 那列函数。
  示例:compose('1+', '2*')(2) => 5
  
2. Functional.sequence ([Function])
  累积 apply 的顺序为参数顺序,为 compose 的反序。
  示例:sequence('1+', '2*')(2) => 6

以上两个操作亦可见于 Function.prototype,用法:'1+'.lambda().sequence('2*')(2) ==> 6

3. Function.prototype.flip ()
  返回一个函数,是原函数对象 this 参数接受顺序颠倒后的版本,不应属于函数扩展类。
  示例:flip('a/b')(1, 2) => 2
  
4. Function.prototype.saturate ([]) {
  返回一个函数,是原函数对象 this 忽略自己接受的参数,仅接受指定参数的版本。
  形式:f.saturate(args...)(args2...) == f(args...)
  
5. Function.prototype.aritize (n::Number) // 有名的参数类型由 :: 指定,下同
  返回一个函数,是原函数对象 this 忽略自己接受的参数列表中下标为 n 的参数的版本。
  
6. Function.S (f, g::Function)
  以单个大写字母命名的是函数的原子操作,应被收入 Functional 对象,这个很奇怪。
  形式:S(f, g)(args...) == f(g(args...), args...)
  
三、通用列表操作
1. 绑定在 Functional 对象上的部分完全照抄 Haskell Prelude 以及 Clean 的命名,它们是:

  1. map(f, [x1, x2...]) = [f(x, 0), f(x2, 1), ...]
  2. foldl, reduce(f, init, [x0, x1, x2]) == f(f(f(init, x0), x1), x2)
  3. filer, select('%2', [1,2,3,4]) -> [1, 3]
  4. foldr(f, init, [x0, x1, x2]) == fn(x0, f(x1, f(x2, init)))
  5. some(f, [x1, x2, x3, ...]) == f(x1) || f(x2) || f(x3)...
  6. every(f, [x1, x2, x3, ...]) == f(x1) && f(x2) && f(x3)...

以上操作的介绍网上到处都是,不再赘述;但有一点不同,即它们除了接受正常参数之外,还在最后接受一个可选参数 object::Object,它被用于指定操作执行的对象/环境。
另外,这些操作全部基于命令式风格实现,对于没有尾递归优化的 JavaScript 来说,效率有保障。

四、群体谓词操作
1. Functional.and ([Function])
  接受一列函数为参数,返回一个函数,它接受一个参数,对该参数 apply 那列函数,如结果全为 true,返回 true;否则返回 false。
  形式:and(f1, f2...)(args...) == f1(args...) && f2(args...)...
  示例:and('>1', '>2')(2) => false
  
2. Functional.or ([Function])
  接受一列函数为参数,返回一个函数,它接受一个参数,对该参数 apply 那列函数,如结果全为 false,返回 false;否则返回 true。
  形式:or(f1, f2...)(args...) == f1(args...) || f2(args...)...
  示例:or('>1', '>2')(2) => true
  
3. Functional.not = function(fn::Function)
  返回一个函数,是参数返回的布尔值的函数(谓词,下同) fn 取否的版本。
  形式:f.not()(args...) == !f(args...)
  
4. Functional.equal ([Function])
  接受一列函数为参数,返回一个函数,它接受一个参数,对该参数 apply 那列函数,如结果全部 == ,返回 true;否则返回 false。
  形式:equal(f1, f2...)(args...) == f1(args...) == f2(args...)...
  示例:equal()() => true // 特殊情况

五、函数扩展
  这一章仅仅是介绍内部实现。
1. Function.prototype.bind (object::Object,[])
  返回一个函数,作为 this 函数对象的副本,使其将在 object 环境下执行,并额外携带参数。
  形式:f.bind(obj, args...)(args2...) == f.apply(obj, [args..., args2...])
  
2. Function.prototype.curry ([])
这是实现克里化特性的关键函数,思想来自网络

Function.prototype.curry = function(/*args...*/) {
var fn = this;
var args = [].slice.call(arguments, 0);
return function() {
   return fn.apply(this, args.concat([].slice.call(arguments, 0)));
};
}

  返回那个传说中的可在参数不足时分步调用的函数——Currying 算子。
  形式:f.curry(args1...)(args2...) == f(args1..., args2...)

其它的 curry 类函数有:

  • rcurry,对从右边开始缺少参数的函数作克里化
  • ncurry,不接受全部参数就不 apply 参数的版本
  • rncurry,前者的反序版本
  • uncurry,作者一再强调,这不是 curry 的反转版本。它会拆分出第一个已得参数,形式为:f.uncurry(a, b...) == f(a)(b...)


3. Function.prototype.partial ([])
  在此函数定义之前,有定义 _ = Function._ = {} 。结合它们可以允许你像在 Haskell 中那样在参数列表中用 _ 忽略参数。但现在空谈是没用的,要结合第七章的语法扩展。
  
4. Function.prototype.guard (guard:>Function, otherwise)
  类似的,是一个允许在函数定义中使用向导功能的工具,尚缺少语法扩展支持。
  形式:f.guard(g, h)(args...) == f(args...), when g(args...) is true
    f.guard(g ,h)(args...) == h(args...), when g(args...) is false

六、工具函数
1. Functional.invoke (methodName::String, [])
  示例:invoke('toString')(123) => "123"
  
2. Functional.pluck (name::String)
  示例:pluck('length')("abc") => 3
  
3. Functional.until (pred:>Function, fn:>Function) // 用 :> 表示将参数强制转换类型

Functional.until = function(pred, fn) {
  // 使用时参数会被强制转为 Functional 的函数,参数可为字符串
fn = Function.toFunction(fn);
pred = Function.toFunction(pred);
  // 返回一个接受一个参数的函数,
return function(value) {
    // 它不断对此参数 apply 函数 pred,
   while (!pred.call(null, value))
      // 并用 fn(value) 的值更新 value,
     value = fn.call(null, value);
   return value; // 直到测试结果为 true。
}
}

  类似 Haskell 的 until,是一种函数式的循环,用命令式风格实现。
  
4. Functional.zip ([])
  特别注意,此 zip 并非 Haskell 中的 zip,它接受可变参数列表而不是列表的列表。
  形式:zip(a, b...) == [[a0, b0], [a1, b1], ...]

以上的章节中绑定在 Functional 上函数都可作为 Function 的对象方法直接使用,我们看这一行:

Functional.__initalFunctionState =
Functional._startRecordingMethodChanges(Function.prototype);

前文对它们作出了定义,这里忽略。用法:name(arg, args...) == arg.name(args...)。

七、语法扩展
1. String.prototype.lambda ()
  把字符串表示的字符串翻译为函数扩展可接受的函数,进一步转为 JavaScript 函数。

String.prototype.lambda = function() {
var params = []; // 存储字符串形式的参数的列表
var expr = this;
  // ECMAsplit 是作者为兼容 IE6.0 所写的 split 版本
var sections = expr.ECMAsplit(/\s*->\s*/m); // 使字符串被 '->' 分割
  /* 注意,分割的结果支持超过任意个 '->',下面会发现,
    -> 9 或者
    x -> y -> x+y 这样的代码也会被正确理解。
  */
if (sections.length > 1) {
    // 这就是所谓的“正确理解”了
   while (sections.length) {
     expr = sections.pop();
      // 然后把参数打碎,再重组为 JS 可识别的参数语法
      /* 也就是说,x y -> x*y+2 和
        x,y -> x*y+2 都可被接受。
      */
     params = sections.pop().split(/\s*,\s*|\s+/m);
      // 装配成代码,顺便支持尾递归语法
     sections.length && sections.push('(function('+params+'){return ('+expr+')})');
   }
} else if (expr.match(/\b_\b/)) {
   params = '_'; // 忽略参数的前奏,下文判断
} else {
    // 这里处理运算符表达式参数缺失的情况,相当于运算符函数化
    // 分为前缺失和后缺失两种情况,
   var leftSection = expr.match(/^\s*(?:[+*\/%&|\^\.=<>]|!=)/m);
   var rightSection = expr.match(/[+\-*\/%&|\^\.=<>!]\s*$/m);
    /* 注意,前缺失类似 *2,后缺失类似 2*,复杂表达式同样支持
      此外,前后都缺失也可以,比如 * 甚至是 *3*
    */
   if (leftSection || rightSection) {
      // 翻译缺失代码的技术:用 $1、$2 代换参数
     if (leftSection) {
       params.push('$1');
       expr = '$1' + expr;
     }
     if (rightSection) {
       params.push('$2');
       expr = expr + '$2';
     }
   } else {
      // 这个地方就有点意思了;它使得函数支持参数指定缺失
      /* 比如 x*y 就已经是一个函数了,相当于 x y->x*y
        作者还特别防止了一个 bug,即对象属性访问语法中,
        属性部分不被认为是未指定的参数。例如
        obj.pro + 4 这个函数,只有 obj 一个参数
        而且,this 和 arguments 不会被认为是未知数。
      */
     var vars = this.replace(/(?:\b[A-Z]|\.[a-zA-Z_$])[a-zA-Z_$\d]*|[a-zA-Z_$][a-zA-Z_$\d]*:|this|arguments|'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"/g, '')
.match(/([a-z_$][a-z_$\d]*)/gi) || [];
     for (var i = 0, v; v = vars[i++]; )
       params.indexOf(v) >= 0 || params.push(v);
   }
}
return new Function(params, 'return (' + expr + ')'); // 把代码装配成函数对象
}


八、过滤器生成器
  仅供特别好学的同志们参考。
1. Function.prototype.prefilterObject (filter::Function)
  形式:fn.prefilterObject(filter).apply(object, args...) == fn.apply(filter(object), args...)
  
2. Function.prototype.prefilterAt (index::Number, filter::Function)
  形式:fn.prefilterAt(i, filter)(a1, a2, ..., a_{n}) == fn(a1, a2, ..., filter(a_{i}), ..., a_{n})
  
3. Function.prototype.prefilterSlice (filter::Function, start, end::Number)
  形式:fn.prefilterSlice(i0, i1, filter)(a1, a2, ..., a_{n}) == fn(a1, a2, ..., filter(args_{i0}, ..., args_{i1}), ..., a_{n})

九、其它用户级函数
1. Functional.id = Functional.I = function(x) {return x};

2. Functional.constfn = Functional.K = function(x) {return function() {return x}};

3. .toFunction ()
  在 String.prototype,Function.prototype,Function(需要参数 fn::Function) 上都有绑定,把对象转换为一个合适的 Functional 函数。但你不需要把代码写这样,map('*2'.toFunction(),alist),因为全局用户级函数都会对应为函数的参数自动执行 toFunction(),只要 map('*2',alist) 就行了。另外,String.prototype 上还有 JavaScript-Like 的 call、apply 方法。

十、结语
  functional.js 很强,很有用,很牛X;但同时也很年轻(7.20 发布),很多可以实现的功能还不完善,不说列表领悟什么的吧,至少应该把 Haskell Prelude 库在通用列表操作方面的函数的移植工作完成。我们期待 Oliver Steele 的表现。

June 8, 2007

OOP 诡异教程(上)

  • 本文分上、下两篇,站在一个难以名状的角度上研究了 JavaScript 语言中面向对象机制的起源、内涵和发展,带领读者从原始森林走向高楼大厦。文章作者 lichray 是个 ECMAScript 的狂热追随者,mozilla.org 邮件列表里的无名潜水员。
  • 文章中使用了 Rhino 解释器,行开头有 "js>" 表示那是输入,输入下一行没有这个标记的表示解释器回馈消息。
  • PS: 读懂本文需要对 JavaScript 闭包和逃逸变量有较深入的了解。

一. 对象和消息
考虑一下我们平常怎么说话的。我们叫某某人做某事,用下面的句式:
forest run!
其中"!"是语气的标志,对于编程语言来说是没有意义的,全部换成".":
forrest run.
不知道如果我告诉大家上面这句话就是 Smalltalk 语言中一个合法语句大家会怎么想。好了,不谈这个。这样我们就得到了一种语法,"宾"谓结构:
ObjectVerb ::
  Object Verb.
如果让它支持多个 Verb,比如
forrest run, jump, stop.
可以扩展成这样:
ObjectVerb ::
  Object VerbList.
VerbList ::
  Verb
  Verb , VerbList
很明显,对于 JavaScript 来说,上面的 BNF 不可能和任何一个产生式匹配。问题出在哪儿?我们要帮 JavaScript 指定,谁是 Object,谁是 Verb。鉴于 Object 只有一个,Verb 有多个,我们可以用括号来区分它们,然后把最后那个句号去掉:
ObjectVerb ::
  Object ( VerbList )
这样上面的那句话就变成了下面的形式:
forrest (run, jump, stop)
很像函数调用,是吧?不过还有一个问题,现在这些 Verb(s) 对于 JavaScript 来说是“裸词”(Perl 语),我们可以避开再去定义这些标识符,用字符串代替;最后再说明一下 Object 是什么:
forrest ('run', 'jump', 'stop')
那么现在我们第一个“模仿”自然语言的程序版本出现了,加上下面针对 JavaScript 的文法:
Object ::
  Identifier
Verb ::
  StringLiteral

二. 实现消息传递
有了文法,一切都好办。看得出来,我们下面的工作是定义能创建一个新 Object 的函数,函数中有一些动作,产生的新 Object 是一个能处理这些消息的函数。创建 Forrest Gump 的函数还可以创建 Tom,Mike 等等;他们都是 People:
function People () {
  function run () {
    print("I'm running!")
  }
  function jump () {
    print("I'm jumping!")
  }
  function stop () {
    print("I can't stop!")
  }  
  return (function (verb) {
    switch (verb) {
      case 'run': run(); break
      case 'jump': jump() ;break
      case 'stop': stop() ;break
    }
  })
}
为了简单起见还可以把返回的那个函数写成这样:
    (function (verb) {
      eval(verb)();
    }
  })
Ok。现在我们来试一试这个智商低于 85 的 Forrest Gump 怎么样:
js> forrest = People()
js> forrest('run')
I'm running!
js> forrest('jump')
I'm jumping!
js> forrest('stop')
I can't stop!
事情就是这样。我们成功地创造了对象,还让他做动作、说话。
不过,这个实现并不是我们上文中最后一个文法所指出的。它不支持连续发送指令。改一改。要加入顺序执行指令的办法:
function People () {
  function run () {
    print("I'm running!")
  }
  function jump () {
    print("I'm jumping!")
  }
  function stop () {
    print("I can't stop!")
  }
  function _do_verbs_ (verblist) {
    for (var i=0; i <> forrest = People()
js> forrest('jump','run','jump','stop')
I'm jumping!
I'm running!
I'm jumping!
I can't stop!

三. 利用消息传递处理状态
什么是状态?我们在进行面向对象编程时,把状态表示为对象的一组数据,我们称之为“属性(property)”。在我们的消息传递编程风格中,可以直接把这些数据堆到产生对象的那个函数中去。下面给 Forrest 加入一个状态,Forrest 口袋里的钱。先得声明原先有多少钱:
forrest = People(1000)
然后,我们希望可以执行这样的代码,让 forrest 支出 200 美元:
forrest('pay', 200)
但很明显,我们无法分清 200 是 Verb 还是 'pay' 所要求的数据。我们只得简化文法,只允许一次发送一个消息,以保全我们的脑细胞:
forrest('pay')(200)
也就是说,我们需要让 forrest('pay') 这一表达式返回一个能改变状态的函数,而不仅仅是调用函数来显示一句话。也就是说,如果我们想让 Forrest 急得跳起来,我们先得跳起来:
forrest('jump')()
新时代的 Forrest 实现如下(省略了一点多余的代码):
function People (money) {
  //var money = money
  function pay (dollars) {
    money -= dollars
  }
  function restMoney () {
    return money
  }
  function run () {
    print("I'm running!")
  }
  return (function (verb) {
    return eval(verb)
  })
}
试一下。先支出 200 美元,然后看看他还剩多少钱:
js> forrest=People(1000)
js> forrest('restMoney')()
1000
js> forrest('pay')(200)
js> forrest('restMoney')()
800
当然,我们的 Forrest 还可以赚钱。下面这个版本比较彻底地说明了消息传递编程风格的一切。可以直接修改钱之后,我们可以不需要在创建 Object 的时候就说明原有多少钱;当然,使用注释中的版本更自然:
function People (/* money */) {
  var money = 0; // var money = money ? money : 0;
  function setMoney (dollars) {
    money = dollars
  }
  function addMoney (dollars) {
    money += dollars
  }
  function pay (dollars) {
    money -= dollars
  }
  function restMoney () {
    return money
  }
  return (function (verb) {
    return eval(verb)
  })
}
试一下吧:
js> forrest = People()
js> forrest('addMoney')(1000)
js> forrest('restMoney')()
1000
js> forrest('pay')(200)
js> forrest('restMoney')()
800
上篇完。小结一下:消息传递的编程风格指的是,把函数 A 的执行上下文当作对象的数据环境,在此定义对象的动词(函数),然后从此上下文中返回一个可以接受、处理消息的函数(常为匿名)。用函数 A 产生消息处理器作为对象,向此对象传递参数作为消息,以此执行函数 A 环境中定义的动作,这些动作还可能改变所在上下文中用一组数据定义的对象状态。

May 30, 2007

ECMA-262试译:7.9 Automatic Semicolon Insertion

7.9 自动分号插入

空语句,变量语句,表达式语句,do-while 语句,continue 语句,break 语句,return 语句,以及 throw 语句,这些确定的 ECMAScript 语句必须以分号结束。这些分号可以总是明确地出现在源代码文本中。为方便起见,在特定的情况下,源代码文本中的这些分号可以被省略。在下面描述的这些情况中,分号被自动插入源代码托肯流。

7.9.1 自动分号插入的规则

  • 从左到右解析程序时,若遇到不被任何产生式允许的托肯(被称为 违规托肯), 于是,在下列情况一个或多个为真的违规托肯之前自动插入一个分号:
    1. 该违规托肯与前一个托肯之间以至少一个行结束符分隔开。
    2. 该违规托肯是 }
  • 从左到右解析程序时,若遇到输入托肯流的结尾,且解析器无法把此输入的托肯流解析为单个完整的 ECMAScript ,程序,于是在这个输入流的结束处自动插入一个分号。
  • 从左到右解析程序时,若遇到的托肯被一些文法的产生式允许,但该产生式是非严格产生式, 且此托肯是终结符或非终结符的第一个托肯,此终结符或非终结符后紧跟着的是非严格产生式中的记法"[no 行结束 符 here]"(因此这样的托肯被称为非严格托肯)。当非严格托肯与前一个托肯之间以至少一个行结束符分 隔开时,在此非严格托肯前自动插入一个分号。

不过,有一种附加的情况凌驾于优先规则:如果此分号将被解析为空语句,或那个分号将成为一个 for 语句头中的两个分号之一,决不自动插入分号(参见12.6.3)。

NOTE
这些是文法中仅有的非严格产生式。

后缀表达式 :
左侧表达式 [no LineTerminator here] ++
左侧表达式 [no 行结束符 here] --

Continue语句 :
continue [no 行结束符 here] 标识符opt ;

Break语句 :
break [no 行结 束符 here] 标识符opt ;

Return语句 :
return [no 行 结束符 here] 表达式opt ;

Throw语句 :
throw [no 行结 束符 here] 表达式 ;

这些非严格产生式的实际效果如下所示:

  • 若遇到托肯 ++-- ,解析器将视其为一个后缀运算符时,且在提前托肯与托肯 ++-- 之间有至少一个行结束符,则在托肯 ++-- 前自动插入一个分号。
  • 若遇到托肯 continue, break, return,throw,且在下一个托肯之前遇到一个 行 结束符,在 continue, break, return,throw 之后自动插入一个分号。

最后给 ECMAScript 程序员一些忠告:

  • ++-- 应和其操作数出现在同一行。
  • returnthrow 语句中的表达式应和托肯 returnthrow 出现在同一行。
  • breakcontinue 语句中的标签应和托肯 breakcontinue 出现在同一行。

7.9.2 自动分号插入的例子

源代码

{ 1 2 } 3

即使应用自动分号插入规则,它也不是 ECMAScript 文法中的合法句子。作为对比,源代码

{ 1

2 } 3

同样不是 ECMAScript 中的合法句子,却会被自动分号插入变形为下面的形式:

{ 1
;2 ;} 3;

这就是一个回复的 ECMAScript 句子了。

源代码

for (a; b
)

不是合法的 ECMAScript 句子,且因 for 语句头需要分号,句子无法被自动分号插入调整。自动分号插入决不插入 for 语句头中的两个分号之一。

源代码

return
a + b

被自动分号插入变形为下面的形式:

return;
a + b;

NOTE
这里的表达式 a + b 不被作为 return 语句的值返回,因为'行结束符'把它和托肯 return 分隔开了。

源代码

a = b
++c

被自动分号插入变形为下面的形式:

a = b;
++c;
NOTE
这里的托肯 ++ 不被视为应用于变量 b 的后缀运算符,因为在 b++ 之间存在一个'行结束符'。

源代码

if (a > b)
else c = d

不是合法的 ECMAScript 句子,且即使没有文法产生式引用于此,在 else 托肯之前的句子无法被自动分号插入调整,因为被自动插入的分号将被解析为一个空语句。

源代码

a = b + c
(d + e).print()

被自动分号插入变形,因为第二行开始的括号表达式可以解释为函数调用的参数列表:

a = b +c(d + e).print()

在这种状况下,赋值表达式必须以一个左括号为开始。在提前的语句结尾处提供一个清晰的分号而不依赖于自动分号插入,对于程序员来说是个好想法。


//这一篇好像很有用的样子~~

ECMA-262试译:7.9 Automatic Semicolon Insertion

7.9 自动分号插入

空语句,变量语句,表达式语句,do-while 语句,continue statement,break 语句,return 语句,以及 throw 语句,这些确定的 ECMAScript 语句必须以分号结束。这些分号可以总是明确地出现在源代码文本中。为方便起见,在特定的情况下,源代码文本中的这些分号可以被省略。在下面描述的这些情况中,分号被自动插入源代码托肯流。

7.9.1 自动分号插入的规则

  • 从左到右解析程序时,若遇到不被任何产生式允许的托肯(被称为 违规托肯), 于是,在下列情况一个或多个为真的违规托肯之前自动插入一个分号:
    1. 该违规托肯与前一个托肯之间以至少一个行结束符分隔开。
    2. 该违规托肯是 }
  • 从左到右解析程序时,若遇到输入托肯流的结尾,且解析器无法把此输入的托肯流解析为单个完整的 ECMAScript ,程序,于是在这个输入流的结束处自动插入一个分号。
  • 从左到右解析程序时,若遇到的托肯被一些文法的产生式允许,但该产生式是非严格产生式, 且此托肯是终结符或非终结符的第一个托肯,此终结符或非终结符后紧跟着的是非严格产生式中的记法"[no 行结束 符 here]"(因此这样的托肯被称为非严格托肯)。当非严格托肯与前一个托肯之间以至少一个行结束符分 隔开时,在此非严格托肯前自动插入一个分号。

不过,有一种附加的情况凌驾于优先规则:如果此分号将被解析为空语句,或那个分号将成为一个 for 语句头中的两个分号之一,决不自动插入分号(参见12.6.3)。

NOTE
这些是文法中仅有的非严格产生式。

后缀表达式 :
左侧表达式 [no LineTerminator here] ++
左侧表达式 [no 行结束符 here] --

Continue语句 :
continue [no 行结束符 here] 标识符opt ;

Break语句 :
break [no 行结 束符 here] 标识符opt ;

Return语句 :
return [no 行 结束符 here] 表达式opt ;

Throw语句 :
throw [no 行结 束符 here] 表达式 ;

这些非严格产生式的实际效果如下所示:

  • 若遇到托肯 ++-- ,解析器将视其为一个后缀运算符时,且在提前托肯与托肯 ++-- 之间有至少一个行结束符,则在托肯 ++-- 前自动插入一个分号。
  • 若遇到托肯 continue, break, return,throw,且在下一个托肯之前遇到一个 行 结束符,在 continue, break, return,throw 之后自动插入一个分号。

最后给 ECMAScript 程序员一些忠告:

  • ++-- 应和其操作数出现在同一行。
  • returnthrow 语句中的表达式应和托肯 returnthrow 出现在同一行。
  • breakcontinue 语句中的标签应和托肯 breakcontinue 出现在同一行。

7.9.2 自动分号插入的例子

源代码

{ 1 2 } 3

即使应用自动分号插入规则,它也不是 ECMAScript 文法中的合法句子。作为对比,源代码

{ 1

2 } 3

同样不是 ECMAScript 中的合法句子,却会被自动分号插入变形为下面的形式:

{ 1
;2 ;} 3;

这就是一个回复的 ECMAScript 句子了。

源代码

for (a; b
)

不是合法的 ECMAScript 句子,且因 for 语句头需要分号,句子无法被自动分号插入调整。自动分号插入决不插入 for 语句头中的两个分号之一。

源代码

return
a + b

被自动分号插入变形为下面的形式:

return;
a + b;

NOTE
这里的表达式 a + b 不被作为 return 语句的值返回,因为'行结束符'把它和托肯 return 分隔开了。

源代码

a = b
++c

被自动分号插入变形为下面的形式:

a = b;
++c;
NOTE
这里的托肯 ++ 不被视为应用于变量 b 的后缀运算符,因为在 b++ 之间存在一个'行结束符'。

源代码

if (a > b)
else c = d

不是合法的 ECMAScript 句子,且即使没有文法产生式引用于此,在 else 托肯之前的句子无法被自动分号插入调整,因为被自动插入的分号将被解析为一个空语句。

源代码

a = b + c
(d + e).print()

被自动分号插入变形,因为第二行开始的括号表达式可以解释为函数调用的参数列表:

a = b +c(d + e).print()

在这种状况下,赋值表达式必须以一个左括号为开始。在提前的语句结尾处提供一个清晰的分号而不依赖于自动分号插入,对于程序员来说是个好想法。


//这一篇好像很有用的样子~~

May 29, 2007

ECMA-262试译:7.8 Literals(下)

语义

数值常量代表数值类型的值。用两个步骤来确定这个值:首先,从常量中获得数学值;然后,用下面所描述的方式舍入这个数学值。

  • 数值常量的MV :: 十 进制常量的MV 是 十进制常量的MV。
  • 数值常量的MV :: 十六进制整数常量的MV 是 十六进制整数常量的MV。
  • 十进制常量的MV :: 十进制整数常量 .十进制整数常量的MV。
  • 十进制常量的MV :: 十进制整数常量 . 十进制数十进制整数常量的MV 加 ( 十进制数的MV 乘以 10 -n ), 这里的 n十进制数中的字符数。
  • 十进制常量的MV :: 十进制整数常量 . 指数部分十进制整数常量的MV 乘以 10 e的MV ,这里的 e的MV 是 指数部分的MV。
  • 十进制常量的MV :: 十进制整数常量 . 十进制数 指数部分 是 ( 十进制整数常量的MV 加 ( 十进制数的MV 乘以 10-n )) 乘以 10e, 这里的 n十进制数中的字符数 和 e的MV 是 指数部分的MV。
  • 十进制常量的MV ::. 十进制数十进制数的MV 乘以 10-n, 这里的 n十进制数中 的字符数。
  • 十进制常量的MV :: . 十 进制数 指数部分的MV 是 十进制数的MV 乘以 10e-n,这里的 n十进制数中的字符数 且 e指数部分的MV。
  • 十进制常量的MV :: 十进制整数常量十进制整数常量的MV。
  • 十进制常量的MV :: 十进制整数常量 指数部分十进制整数常量的MV 乘以 10e的MV, 这里的 e 是 指数部分的MV。
  • 十进制整数常量的MV :: 0 是 0。
  • 十进制整数常量的MV :: 非 零数字 十进制数 是 ( 非零数字的MV 乘以 10n的MV ) 加 十进制数的MV, 这里的 n十进制数中的字符数。
  • 十进制数的MV :: 十 进制数字十进制数字的MV.
  • 十进制数的MV :: 十 进制数 十进制数字 是 ( 十进制数的MV 乘以 10) 加 十进制数字的MV。
  • 指数部分的MV :: 指 数指示符 有符号整数有符号整数的MV。
  • 有符号整数的MV :: 十 进制数十进制数的MV.
  • 有符号整数的MV :: + 十进制数十进制数的MV.
  • 有符号整数的MV :: - 十进制数的MV 是 十进制数的负MV.
  • 十进制数字的MV :: 0 or of 十六进制数字 :: 0 是 0.
  • 十进制数字的MV :: 1 or of 非零数字 :: 1 or of 十六进制数字 :: 1 是 1. 十进制数字的MV :: 2 or of 非零数字 :: 2 or of 十六进制数字 :: 2 是 2.
  • 十进制数字的MV :: 3 or of 非零数字 :: 3 or of 十六进制数字 :: 3 是 3.
  • 十进制数字的MV :: 4 or of 非零数字 :: 4 or of 十六进制数字 :: 4 是 4.
  • 十进制数字的MV :: 5 or of 非零数字 :: 5 or of 十六进制数字 :: 5 是 5. 十进制数字的MV :: 6 or of 非零数字 :: 6 or of 十六进制数字 :: 6 是 6.
  • 十进制数字的MV :: 7 or of 非零数字 :: 7 or of 十六进制数字 :: 7 是 7.
  • 十进制数字的MV :: 8 or of 非零数字 :: 8 or of 十六进制数字 :: 8 是 8.
  • 十进制数字的MV :: 9 or of 非零数字 :: 9 or of 十六进制数字 :: 9 是 9. 十六进制数字的MV :: a or of 十六进制数字 :: A 是 10.
  • 十六进制数字的MV :: b or of 十六进制数字 :: B 是 11.
  • 十六进制数字的MV :: c or of 十六进制数字 :: C 是 12.
  • 十六进制数字的MV :: d or of 十六进制数字 :: D 是 13.
  • 十六进制数字的MV :: e or of 十六进制数字 :: E 是 14.
  • 十六进制数字的MV :: f or of 十六进制数字 :: F 是 15.
  • 十六进制整数常量的MV :: 0x 十六进制数字十六进制数字的MV。
  • 十六进制整数常量的MV :: 0X 十六进制数字十六进制数字的MV。
  • 十六进制整数常量的MV :: 十 六进制整数常量 十六进制数字 是 ( 十六进制整数常量的MV 乘以 16) 加 十六进制数字的MV。

一旦数值常量的 MV 被精确地确定,接下来就会被舍入为数值类型的一个值。如果 MV 是0,那么舍入值为 +0; 否则,舍入值必须正是 MV 的值(在8.5中定义),除非该常量是 一个有效数字超过20位的十进制常量——在这种情况下,此数字的值是下 面两种之一:一是将其20位之后的每个有效数字用0替换,产生此常量的 MV;二是将其20位之后的每个有效数字用0替换,并在20位有效数字之后增加数字位,产生此常量的 MV 值。判断一个数字是否为有 效数字,首先它不能是指数部分的一部分,且

  • 它不是0;或
  • 它的左边是一个非零值,右边是一个不在指数部分中的非零值。

7.8.4 字符串常量

字符串常量是被单引号或双引号括起的零个或多个字符。每个字符都可以呈现为转义序列。

语法

字符串常量 :: " 双字符串字符集opt "
'
单字符串字符sopt '

双字符串字符集 :: 双字符串字符 双字符串字符集opt

单字符串字符s :: 单字符串字符 单字符串字符sopt

双字符串字符 :: 源代码字符 but not 双引号 " or 反斜杠 \ or 行结束符
\ 转义序列

单字符串字符 :: 源代码字符 but not 单引号 ' or 反斜杠 \ or 行结束符
\ 转义序列

转义序列 :: 字符转义序列
0 [lookahead ? 十进制数字]
十六进制转义序列
Unicode转义序列

字符转义序列 :: 单个转义字符
非转义字符

单个转义字符 :: one of ' " \ b f n r t v

非转义字符 :: 源代码字符 but not 转义字符 or 行结束符

转义字符 :: 单个转义字符
十进制数字

x
u

十六进制转义序列 :: x 十六进制数字 十六进制数字

Unicode转义序列 :: u 十六进制数字 十六进制数字 十六进制数字 十六进制数字

非终止十六进制数字的定义在7.8.3节中给出。源 代码字符的描述在章节26中。

字符串常量代表字符串类型的值。该常量的字符串值(SV)的描述,取决于字符串常量各个部分提供的字符值(CV)。作为此过程的一部 分,一些字符串常量中的字符被解释为拥有数学值(MV),正如下文和章节7.8.3中所描述的。

  • 字符串常量的SV :: "" 是 空字符序列
  • 字符串常量的SV :: '' 是 空字符序列
  • 字符串常量的SV :: " 双字符串字符集 " 是 the SV of 双字符串字符集.
  • 字符串常量的SV :: ' 单字符串字符s '单字符串字符s的SV.
  • 双字符串字符集的SV :: 双字符串字符 是一个字符的序列,双字符串字符的SV.
  • 双字符串字符集的SV :: 双字符串字符 双字符串字符集双字符串字符的SV 的序列 后跟所有双字符串字符集的SV 中的有序字符
  • 单字符串字符集的SV :: 单字符串字符 是一个字符的序列, 单字符串字符的SV.
  • 单字符串字符s :: 单字符串字符 单字符串字符s单字符串字符后跟所有单字符串字符集的SV 中的有序字符
  • 双字符串字符 :: 源代码字符 but not 双引号 " or 反斜杠 \ or 行结束符 是 the 源代码字符 字符本身。
  • 双字符串字符 :: \ 转义序列转义序列. 单字符串字符 :: 源代码字符 but not 单引号 ' or 反斜杠 \ or 行结束符源代码字符 字符本身。
  • 单字符串字符 :: \ 转义序列转 义序列.
  • 转义序列 :: 字符转义序列 是 the 字符转义序列.
  • 转义序列 :: 0 [lookahead ? 十进制数字] 是 a 字符 (Unicode 值 0000).
  • 转义序列 :: 十六进制转义序列十六进制转义序列.
  • 转义序列 :: Unicode转义序列Unicode转义序列.
  • 字符转义序列 :: 单个转义字符 是 由单个转义字符根据下表确定的代码点值的字符:
//此处表格不发,blogger支持不好。
  • 字符转义序列 :: 非转义字符 是 the 非转义字符.
  • 非转义字符 :: 源代码字符 but not 转义字符 or 行结束符 是 the 源代码字符 字符本身。
  • 十六进制转义序列 :: x 十六进制数字 十六进制数字 是以 (第一个十六进制数字的 MV 值的16倍) 加 (第二个 十六进制数字的 MV 值) 为代码点值的字符。
  • Unicode转义序列 :: u 十六进制数字 十六进制数字 十六进制数字 十六进制数字 是以 (第一个 十六进制数字)的 MV 的 (4096 (即, 163 )倍) 加 (第二个 十六进制数字)的 MV 的(256 (即 , 162 )倍) 加 (第三个 十六进制数字的 16 倍) 加 (第四个 十六进制数字的 MV) 为代码点值的字符。
NOTE
“行结束”字符不能出现在字符串常量中,即使在它前面有一个反斜杠 \。 使行结束字符成为字符串常量的值一部分的正确方法是,使用转义序列,如 \n\u000A

7.8.5 正则表达式常量

正则表达式常量被扫描后,转换为 RegExp 对象的输入元素(章节15.10)。 此对象在包含它的程序或函数开始求值之前被创建。求值此常量产生一个到那个对象的引用;这不产生新对象。决不能用 === 比较被程序求值为正则表达式对象的两个正则表达式常量,即使两个常量内容相同。在运行时创建RegExp 对象,可以用 new RegExp (章节15.10.4),或者以函数的方式调用 RegExp 构造函数(章节15.10.3)。

下面的产生式描述了正则表达式常量的语法,以及输入元素扫描器查找正则表达式常量结尾的方法。给正则表达式构造函数传递包含正 则表达式体正则表达式徽标的、不被解释的字符串,将以更严厉的文法,根据它们自己解释它 们。实现可以扩展正则表达式构造函数的文法,但不应扩展正 则表达式体正则表达式徽标的产生式,以及被它们使用的产生式。

语法

正则表达式常量 :: / 正则表达式体 / 正则表达式徽标

正则表达式体 :: 正则表达式首字符 正则表达式字符集

正则表达式字符集 :: [empty]
正则表达式字符集 正则表达式字符

正则表达式首字符 :: 非结束符 but not * or \ or /
反斜杠序列

正则表达式字符 :: 非结束符 but not \ or /
反斜杠序列

反斜杠序列 :: \ 非结束符

非结束符 :: 源代码字符 but not 行结束符

正则表达式徽标 :: [empty]
正则表达式徽标 标识部分

NOTE
正则表达式常量不可为空;// 开始一个单行注释而非表示空正则表达式。描述空正则表达式,使用 /(?:)/


语义

正则表达式常量代表一个 Object 类型的值。确定该值要两个步骤:首先,包含和的产生式延伸的字符,只收集、不解释地转为两个字符串,分别为 Pattern 和 Flags。然后,用参数 Pattern 和 Flags 两个参数调用 new RegExp 构造函数,结果是 正则表达式常量 的值。如果调用 new RegExp 产生了错误,可能的实现是,作为其判断力,在扫描程序时立即报错,或推迟报错,直到该正则表达式常量在出现执行的轨迹中被求值。

May 28, 2007

ECMA-262试译:7.8 Literals(上)

7.8 常量

语法

常量 ::
空值常量
布尔值常量
数值常量
字符串常量
正则表达式常量

7.8.1 空值常量

语法

空值常量 ::
null

语义

空值常量 null 的值是空类型唯一的值,也就是 null

7.8.3 布尔值常量

语法

布尔值常量 ::
true
false

语义

布尔值常量值 true 是布尔值类型的值,也就是 true

布尔值常量值 false 是布尔值类型的值,也就是 false

7.8.3 数值常量

语法

数值常量 ::
十进制常量
十六进制整数常量

十进制常量 ::
十进制整数常量 . 十进制数opt 指数部分opt
. 十进 制数 指数部分opt
十进制整数常量 指数部分opt

十进制整数常量 ::
0
非零数字 十进制数opt

十进制数 ::
十进制数字
十进制数
十进制数字

十进制数字 :: one of
0 1 2 3 4 5 6 7 8 9

非零数字 :: one of
1 2 3 4 5 6 7 8 9

指数部分 ::
指数指示符 有符号整数

指数指示符 :: one of
e E

有符号整数 ::
十进制数
+
十进制数
- 十进 制数

十六进制整数常量 ::
0x 十六进制数字
0X
十六进制数字
十六进制整数常量 十六进制数字

紧跟在数值常量后的源代码字符既不是标识符开始也不是十 进制数

NOTE
例如: 3in
不是两个输入元素3in,而是一个错误。

①原书此处无此句,有误。


//7.7节没有话,就不放在这儿了
//前文所有的文法、注释格式都已经过调整
//下面要翻文字有大量重复单词,准备采取自动化翻译手段

ECMA-262试译:7.6 Identifiers

7.6 标识符

描述

解释标识符的文法根据在章节5.16中给出,对于 Unicode 标准的升级版3.0有一些小更正。此文法同时基于 Unicode 标准所具体指明的常规及非常规字符类别。在 Unicode 标准2.1版中具体指明的类别中的字符,必须被所有符合标准的 ECMAScript 实现认作是其它类别中的字符;不过,符合标准的 ECMAScript 实现可以允许附加那些基于 Unicode 早期版本中的类别分配的、合法的标识符字符集。

本标准具体指明了一个背离给定的 Unicode 标准的文法:允许美元号($)和下划线 (_)出现在标识符的任何位置。美元号仅应在机械生成的代码中使用。

标识符中同样允许有 Unicode 转义序列,它们在此处给标识符提供单个字符,由 Unicode转义序列的 CV 算法计算(参见7.8.4)。在 Unicode转义序列之 前提前出现的 \ 不为标识符提供字符。Unicode转义序列不 能被用于向标识符中放置一个字符,否则是违法的。换句话说,如果 \ Unicode 转义序列的序列被它的Unicode转义序列的 CV 替换,结果必定仍是合法的标 识符,与原始的标识符有完全相同的字符序列。

根据 Unicode 标准来判断两个标识符不相等时,不考虑它们是否被呈现为完全相同的代码点序列(换句话说,仅要求符合标准的 ECMAScript 实现对标识符作以比特为单位的比较)。这意味着,所得到的源代码文本在到达编译器之前就已经被转化成常规的形式 C。

语法
标识符
::
标识符名 but not保留 字
标识符名 ::
标识符开始
标识符名 标识符体

标识符开始 ::
Unicode字母
$
_
\Unicode转义序列
标识符体 ::
标识符开始
Unicode联合标记
Unicode数字
Unicode连接标点符号

\Unicode转义序列
Unicode字母
以下 Unicode 分类中的任何字符: "Uppercase letter(Lu,大写字母)", "Lower caseletter(Ll,小写字母)", "Titlecase letter(Lt,标题字母)", "Modifier letter (Lm,修饰字母)","Other etter(Lo,其它字母)", 或 "Letter number(Nl,字母数字)"。
Unicode联合标记
以下 Unicode 分类中的任何字符: "Non-spacing mark(Mn,非空格标记)" 或 "Combining spacing mark(Mc,联合空格标记)"。
Unicode数字
以下 Unicode 分类中的任何字符:"Decimal number(Nd,十进制数)"。
Unicode连接标点符号
以下 Unicode 分类中的任何字符:"Connector punctuation (Pc,连接标点符号)"。
Unicode转义序列
参见7.8.4
十六进制数字:: one of
0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F

May 27, 2007

ECMA-262试译:7.5 Tokens

7.5 托肯

语法
托肯 ::

保留字
标识符
标点符号
数字常量
字符串常量

7.5.1 保留字

描述

保留字不能被用作标识符。

语法
保留字 ::

关键字
未来保留字
空值常量
布尔值常量

7.5.2 关键字

下列托肯是 ECMAScript 关键字,且不能被用作 ECMAScript 程序中的标识符。

语法
关键字 :: one of

break else new var case finally return void catch for switch while continue function this with default if throw delete in try do instanceof typeof

7.5.3 未来保留字

下面的单词被用作将来可能出现的扩展的关键字,因此,为了让它们被将来采用的这些扩展所允许,它们被保留。

语法
未来保留字 :: one of

abstract enum int short boolean export interface static byte extends long super char final native synchronized class float package throws const goto private transient debugger implements protected volatile double import public

ECMA-262试译:7.4 Comments

7.4 注释

描述

注释可以是单行或多行的。多行注释不能自包含。

由于单行注释可以包含除行结束符之外的任何字符①。根据常规,托肯总是进行最长匹配,所以单行 注释总是由从 // 开始到行末结束的所有字符组成。不过,行尾的行结束符不被识别为单行注释的一部分;它被词法文法识别为分隔符,并 成为给语法文法提供的输入元素流的一部分。这一点非常重要,因为这意味着单行注释的存在与否对自动分号插入的过程没有影响。

除非多行注释中包含有一个行结束字符,导致整个注释被语法文法解析为一个行 结束符,否则抛弃注释,行为类似空白字符。

语法

注释::
多行注释
单行注释

多行注释::
/*
多行注释字符集opt */

多行注释字符集::
多行注释非星号字符 多行注释字符集opt

*
后置星号注释字符opt

后置星号注释字符::
多行非斜杠或星号字符 多行注释字符集opt
*
后星号注释字符opt

多行非星号字符::
源代码字符 but not 星号 *

多行非正斜杠或星号字符::
源代码字符 but not 正斜杠 /or 星号*

单行注释::
//
单行注释字符集opt

单行注释字符集::
单行注释字符 单行注释字符集opt

单行注释字符::
源代码字符
but not 行结束符


①此句有歧义,正确意思是"单行注释直到行末"。

ECMA-262试译:7.3 Line Terminators

7.3 行结束符

与空白字符类似,行结束字符被用于提升源代码文本的可读性,以及把托肯(不可分割的词法单元)互相分隔开来。不过,与空白字符不同的
是,行结束符对语法文法的行为有影响。一般说来,行结束符可以出现在两个托肯之间,不过有少数地方,语法文法禁止它的出现。行结束符不能出现在任何托肯之
间,即使是字符串也不行。行结束符还对自动分号插入过程有影响。

下面的字符被认为是行结束符:





代码点值名称正式名称
\u000A 换行
<LF>
\u000D 行末 <CR>
\u2028 行分隔符 <LS>
\u2029 段落分隔符<PS>

语法

行结束符 ::

<LF>
<CR>
<LS>
<PS>


//原文件地址

ECMA-262试译:7.2 White Space

7.2 White Space

空白字符被用于提升源代码文本的可读性,以及把托肯(不可分割的词法单元)互相分隔开来,在其它方面是无关紧要的。空白字符允许出现在 两个托肯之间,也允许出现在字符串中(在这个地方,它们会被看成是有意义的字符,成为字符串常量的一部分)。但空白字符不能出现在其它任何托肯之间。

下面的字符被认为是空白字符:

代码点值 名称 正式名称
\u0009 制表符 <TAB>
\u000B 垂直制表符 <VT>
\u000B 表单结束符 <FF>
\u0020 空格 <SP>
\u00A0 非阻断空格 <NBSP>
其它类别"Zs" 其它任何Unicode“空白分隔符” <USP>
语法
空白字符 ::

<TAB>
<VT>
<FF>
<SP>
<NBSP>
<USP>


//原文地址

ECMA-262试译:7.1 Unicode Format-Control Characters

7.1 Unicode 格式控制字符

Unicode 格式控制字符(Unicode 字符数据库"Cf"分类,比如 LEFT-TO-RIGHT 标记、RIGHT-TO-LEFT 标记)被用于控制范围内的文本格式化,以使文本被相对于此的高阶协议(如标记语言)忽略。在源代码文本中允许出现这些字符也是有用的,便于查看和编辑。

格式控制字符可以在 ECMAScript 程序的任何位置出现。这些字符将在应用词法文法之前从源代码文本中被移除。因为这些字符将在处理字符串常量和正则表达式常量之前被移除,所以必须使用 Unicode 转义序列(7.6),以使字符串和正则表达式常量中包含 Unicode 格式控制字符。

May 26, 2007

ECMA-262试译:7 Lexical Conventions

7 词法约定

ECMAScript 程序的源代码文本首先被转换为一个输入元素的序列,其中的每一项是托肯、行结束符、注释或空白中的一个。源代码文本被从左到右扫描,重复地把最长的可能的 字符序列作为下一个输入元素。

词法文法中有两个目标符。符号输入分隔元素在其它语法文法中上下文中可以是一个除号(/) 或除法赋值(/=)运算符。符号输入元素正则表达式在其它语法文法上 下文中使用。

需要注意的是,除号和正则表达式常量存在于语法文法中的上下文中,且都被语法文法允许;不论如 何,在斜线号不被识别为正则表达式常量的开始这一情况下,词法文法使用输入分隔元素目标符。为了使它能在任何情况 下正常工作,可以把正则表达式常量用括号括起。

语法
输入分隔元素 ::

空白
行结束符
注释
托肯
分隔符

输入元素正则表达式 ::

空白
行结束符
注释
托肯
正则表达式常量

ECMA-262试译:6 Source Text

6 源代码文本

ECMAScript 源代码文本被表示为一个 Unicode 编码的字符序列,Unicode 版本2.1或更新,使用 UTF-16 转换格式。我们期望文本已被 Unicode 规范化形式 C(canonical composition)规范化,Unicode 技术报告 #15中描述了它。合乎标准的 ECMAScript 实现不要求展示任何文本的规范化形式,做出它们展示规范化文本的行为,只需展示文本自身。

源文件字符 ::

任何 Unicode 字符

ECMAScript 源文件文本可以包含任何 Unicode 字符。所有的 Unicode 空白字符都被视为空格,所有 Unicode 行、段分隔符都被视为行分隔符。Unicode 非拉丁文字符允许出现在标识符,字符串常量,正则表达式常量和注释中。

贯穿此文档的剩余部分,短语"代码点"和单词"字符"将被用于代指16位无符号值,用来呈现单个 UTF-16的16位单元。短语"Unicode 字符"将被用于代指抽象语言学或排版单元,呈现为单个 Unicode 标量值(可能长于16位,将被呈现为多于一个的代码点)。这儿仅代指单个以 Unicode 标量值形式呈现的实体:组合字符得到的字符序列仍是独立的“Unicode 字符”,即使用户认为整个序列是单个字符。

在字符串常量、正则表达式常量和标识符中,任何字符(代码点)也可以被表示为一个由六个字符组成的 Unicode 转义序列,形式为 \u 加上四个十六进制数字。事实上,在注释中,这样的转义序列被当作注释的一部分忽略 掉。在字符串常量或正则表达式常量中,Unicode 转义序列向常量的值提供一个字符。在标识符中,转义序列给标识符提供一个字符。

NOTE 1
虽然这篇文档常常提到在“字符串”中的“字符”和16位无符号整数 (该字符的 UTF-16 编码)之间的“转换”,但事实上,这种转换是不存在的,因为“字符串& rdquo;中的“字符”其实正是用那个16位无符号整数表示的。

NOTE 2
在对待 Unicode 转义序列的行为方面,ECMAScript 语言与 Java 语言不同。例如,在 Java 程序中,Unicode 转义序列 \u000A 在单行注释中出现,它将被解释为一个行结束符(Unicode 字符000A代表换行),因此下一个字符不再是注释的一部分。类似地,如果
在 Java 程序中,Unicode 转义序列 \u000A 出现在字符串常量中,将被同样解释为一个行结束符,所以它不能允许出现在字符串常量中——要把换行作为字符串常量的值的一部分,必须写成 \n 而不是 \u000A。在 ECMAScript 程序中,在注释中出现的 Unicode 转义序列决不会被解释,因此不能使注释结束。类似地,在 ECMAScript 程序中,在字符串常量中出现的 Unicode 转义序列总是为字符串常量的值提供字符,而决不会被解释成行结束符或引号,这使 Unicode 转义序列不可能结束一个字符串常量。

ECMA-262试译:5.2 Algorithm

5.2 算法约定

本规范中常会用到一种标有数字序号的列表来详细指明一个算法的步骤。这些算法是用来说明语义的。实际操作中,具体实现可能会对给定的特 性给出更有效率的算法。

若算法要产生一个值作为结果,使用说明"返回 x"来指明算法的结果是 x 的值,此时算法结束。记法 Result(n) 被用作"第n步的结果" 的缩写。Type(x) 被用作"x的类型"的缩写。

加法、减法、取负、乘法、除法这些数学运算,以及这一节稍后定义的数学函数应被理解为总是使用实数做精确的数学计算,这不包括无穷大或 负零。本标准中的算法在适当的地方会建模浮点数运算,描述其步骤,处理无穷大和有符号零并进行舍入。如果数学运算或函数应用于一个浮点数,应被理解为应用 于此浮点数所代表的精确的数学值;比如,浮点数必须是有限的,若为+0或-0则简单地取与之相符的数学值0。

数学函数 abs(x) 返回 x 的绝对值,即如果 x 为负(小于零)则是 -x, 否则是 x 本身。

若为 x 正,数学函数 sign(x) 返回1;为负则返回-1。在本标准中,对于 x 为零的情况,不使用函数 sign。

记法"x modulo y"(y 必须为有限的非零值)计算 k 值,它与 y 同号(或同为零),使得 abs(k) < y) 且对于同样的整数 qx-k = q × y

数学函数 floor(x) 返回不大于 x 的最 大的整数(可接近正无穷)。

NOTE
floor(x) = x-(x modilo 1).
若定义一个"抛出异常"的算法,执行此算法结束后没有返回结果。调用这个算法,直到算法步骤到达了明确地处理这个异常的时候,此过程也会结束,处理异常的术语有"如果一个异常被抛出..."。一旦某个算法步骤遭遇异常,就不再认为此异常发生过。

ECMA-262试译:5.1 Syntatic and Lexical Grammars(下)

5.1.5 文法记法

词法和字符串文法的终结符,以及有些语法文法的终结符,将以等宽(fixed width)字 体显示在文法的 产生式中,贯穿本规范中该文本被直接引用为一个终结符 的全过程。它们可出现在写成的程序中。所有的非终结字符以这种特定的方式,作为恰当的、ASCII 范围内的 Unicode 字符被识别,以区别其它 Unicode 范围内看上去相似的 Unicode 字符。

非终结符以斜体(italic)显示。非终结符的定义由其被定义的名字后跟一个或更多冒号引入 (冒 号的数量指出产生式所属的文法)。非终结符中一个或多个可变的右式紧跟在下一行。例如,文法语法的定义:

With语句 :

with ( 表达式 ) 语句

这说明非终结符With语句表示 with 托肯,后跟一个左括号托肯,再跟随一个表达式,其后是一个右括号托肯,再后面是一个语句。 出现的表达式语句它们自身都是非终结符。作为另一个例子,有文法定义:

参数列表 :

赋值表达式
参数列表 , 赋值表达式

这说明一个参数列表可以表现为单个赋值表达式,或一个参 数列表后跟一个逗号,再跟一个赋值表达式参数列表的 定义是递归的(recursive),也就是说,它的定义中借用了它本身。结果是参数列表可 以包含任意数量为正的参数,以逗号隔开,每个参数表达式都是一个赋值表达式。像这样的非终结符的递归定义很常见。

出现在终结符或非终结符后面的下标后缀"opt",指出这是一个可选符号(optional symbol)。 包含可选符号的可变部分其实可以细分为两个右式,其一忽略可选元素,而另一个包括它。这意味着:

变量声明 :

标识符 初始化器opt

是下述产生式的简略形式:

变量声明 :

标识符
标识符 初始化器

再如:

迭代语句 :

for ( 入口表达 式opt ; 表 达式opt ; 表 达式opt) 语 句

是下述产生式的简略形式:

迭代语句 :

for ( ; 表达式opt ; 表达式opt)
for ( 入口表达式 ; 表达式opt ; 表达式opt) 语句

它是由下面的产生式的简略形式转变得到的:

迭代语句 :

for ( ; ; 表达式opt) 语句
for ( ; 表达式 ; 表达式opt) 语句
for ( 入口表达式 ; ; 表达式opt) 语句
for ( 入口表达式 ; 表达式 ; 表 达式opt) 语 句

它是由下面的产生式的简略形式转变得到的:

迭代语句 :

for ( ; ; ) 语句
for ( ; ; 表达式 ) 语 句
for ( ; 表达式 ; ) 语 句
for ( ; 表达式 ; 表达式 ) 语句
for ( 入口表达式 ; ; ) 语 句
for ( 入口表达式 ; ; 表达式 ) 语句
for ( 入口表达式 ; 表达式 ; ) 语句
for ( 入口表达式 ; 表达式 ; 表 达式 ) 语句

所以说,迭代语句其实有八个可变右式。

如果产生式右式出现了短语"[lookahead ∉ set]",指的是如果紧接着的输入终结符不是所给集合 set 的 成员,则不使用此产生式。 集合 set 可以被写成克里闭包中的非闭合终结符组成的列表。为方便起见,这个集合也可写成一个非终结符,这使它代表除去此非终结符的所有终结符构成的集合。例如,给 出下列定义:

十进制数字 :: one of

0 1 2 3 4 5 6 7 8 9

十进制数 ::

十进制数字
十进制数 十进制数字

定义

Lookahead示例 ::

n [lookahead ∉ {1,3,5,7,9}] 十进制数
十进制数字 [lookahead ∉ 十 进制数]

当字母 n 后跟一个或多个首位是偶数的十进制数,或后跟一个后面没有另一个十进制数字的数字时,完成匹配。

如果语法文法的产生式右式出现了短语"[no LineTerminator here]",它指出了该产生式是一个受约束产生式(restricted production): 如果行结束符出现在输入流的指定位置上,这个产生式不被使用。例如,下面的产生式:

返回语句 :

return [no LineTerminator here] 表达式opt ;

这里指出了,如果在 return 托肯和 表达式 之间出现了行结束符,则不使用此产生式。

直到出现的行结束符被受限产生式禁止之前,在输入的元素流中,行结束符允 许在两个连续托肯之间出现任意次数但对程序的语法可接受性没有影响。

如果在语法定义的冒号之后出现了单词"one of",它表示随后的行中的所有终结符被当 作一个可变定义。例如,在 ECMAScript 的词法文法中包含下面的产生式:

非零数字 :: one of

1 2 3 4 5 6 7 8 9

这只不过是下面的产生式的简略形式:

非零数字 ::

1
2
3
4
5
6
7
8
9

当词法文法或数字化字符串文法的产生式可变部分中出现了多字符托肯,表示这个字符序列构成一个托肯。

使用短语"but not"可具体指明产生式的某些特定扩充是不允许的,这样可以把指定的 扩充排除在外。例如,下面的产生式:

标识符 ::

标识符名 but not 保 留字

意思是,非终结符标识符可以被任意可以替换标识符名的 字符序列替换,同一序列不能被保留字替换。

最后,对于实际上不可能列出全部可变元的少量非终结符,我们用普通字体写出描述性的短语来描述它们:

源文件字符 ::

任何 Unicode 字符


①原书此处有 one of,错误。

ECMA-262试译:5.1 Syntatic and Lexical Grammars(上)

5.1 语法和词法文法

这一节描述了本规范中使用的上下文无关文法,这是 ECMAScript 程序语法结构的定义。

5.1.1 上下文无关文法(Context-Free Grammars)

一个上下文无关文法由许多产生式(productions)组 成。每个产生式都拥有一个抽象符号作为其左式(left-hand side),被称为非终结 符(nonterminal),以及一个由零个或多个非终结符和终结符(terminal)组 成的右式(right-hand side)。对于每个文法而言,终结符是从一个特定的字母表中抽取的。

句子的开始由单个显式的非终结符组成,被称为目标符(goal symbol),一个给定的上 下文无关文法规定了这样一种语言,它被表示为可能的终结符序列组成的集合(有可能是无限集),这是用产生式左式重 复替换序列中一切非终结符得到的,替换时所用的产生式的左式要和被替换的非终结符一致。

5.1.2 词法文法和正则表达式文法

ECMAScript 的词法文法(lexical grammar)在条款7中 给出。此文法 以 Unicode 字符集中的字符作为其终结符。它定义了一个产生式集合,其中的产生式以目标符输入分隔元素 (InputElementDiv)输入正则表达式元素(InputElementRegExp)开 始,这描述了 Unicode 字符是如何被翻译成输入元素序列的。

为 ECMAScript 语法文法提供的除空白和注释之外的输入元素被称为 ECMAScript 托肯 (token)。这些托肯即 ECMAScript 语言中的保留字、标识符、常量和标点符号。此外,行结束符即使不被认为是托肯,它同样是输入的元素流中的一部分,并指导自动插入分号的过程 (7.8.5)。普通的空白和单行注释被丢弃,且不出现在为语法文法提供的输入元素流中。对于多 行注释 (MultiLineComment)(即形如 "/*...*/"的注释,不论它跨越了多少行),如果它没有包含行结束符,就被简单地丢弃;假使多行注释包含一 个以上行结束符,则用单个行结束符替换它,使之成为为语法文法提供的输入元素流的一部分。

ECMAScript 的正则表达式文法(RegExp grammar)15.10 中给出。此文法同样 以 Unicode 字符集中的字符作为其终结符。它定义了一个产生式集合,其中的产生式以目标符的模式(Pattern)为 起始,描述了 Unicode 字符是如何被翻译成正则表达式模式的。

词法文法和正则表达式文法的产生式被识别为被两个冒号"::"分割的产生式。词法文法和正则表达式文法共享某些产生式。

5.1.3 数字化字符串文法

第二个文法被用于将字符串翻译为数值量。此文法类似词法文法中与数字常量有关的部分, 以 Unicode 字符集中的字符作为其终结符。此文法在9.3.1中出现。数字化字符串文法的产生式被识 别为被三个冒号":::"分割的产生式。

5.1.4 语法文法

ECMAScript 的语法文法在条款11121314中 给出。此文法以词法文法定义的 ECMAScript 托肯作为其终结符(5.1.2)。它定义了一个产生式 集合,其中的产生式以目标符 Program 开始,描述了托肯序列是如何构成语法正确的 ECMAScript 程序的。

若将一个 Unicode 字符流被解析为一个 ECMAScript 程序,首先,通过重复应用词法文法,它被转化为一个输入元素流;通过一次应用语法文法,这个输入元素流继续被解析。如果输入元素流中已不再留有托肯,而托肯仍无法被解析为单个目标非终结符 Program,则这个程序语法出错。

语法文法的产生式被识别为仅被一个冒号":"分割的产生式。

语 法文法被表示为0,0,0的分段,0其实不是能被正确的 ECMAScript 程序接受的托肯序列中的一员。确定的额外托肯序列同样会被接受,也就是说,即使只有冒号被加入序列中的某个位置(比如在行结束字符之前),这些托肯也会被 文法所描述。深入说来,即便终结字符出现在某些“尴尬”的位置上,被文法描述的、确定的托肯序列也不考虑其是否能被接受。


//全文太长,先发的这部分是说明部分
//最后一段没看懂,晕

ECMA-262试译:4.3 Definitions

4.3 定义

下面给出了与 ECMAScript 有关的关键术语的非正式定义。

4.3.1 类型

类型是数据取值的集合。

4.3.2 原语值

原语值是类型 UndefinedNullNumberBoolean String 的一个成员。原语值是直接表示语言实现的底层数据。

4.3.3 对象

对象是未 排序的属性的集合,其中每个属性包含一个原语值、对象或函数。被作为属性保存的函数被称为方法。

4.3.4 构造函数

构造函数是 一种创建并初始化对象的函数对象。每个工作;构造函数都拥有一个相关联的原型对象,用它来实现继承和共享属性。

4.3.5 原型

原型是一 种对象,被用在 ECMAScript 中实现继承结构、状态和行为。当构造函数创建对象时,那个对象隐含引用构造函数的关联原型,以此分解属性引用。通过程序中的表达式 constructor.prototype 可以引用到构造函数的关联原型,通过继承,添加给对象的属性会被所有共享此原型的对象共享。

4.3.6 本地对象

本地对象指 的是由 ECMAScript 实现提供并独立于宿主环境的任何对象。这篇规范定义了标准本地对象。有些本地对象是内置的;其余的可能在 ECMAScript 程序执行的过程中被构造。

4.3.7 内置对象

内置对象指 的是由 ECMAScript 实现提供的,独立于宿主环境的,并在 ECMAScript 程序刚开始执行时就出现的对象。这篇规范定义了标准内置对象,一个 ECMAScript 实现也可能指明并定义其它的内置对象。所有的内置对象都是本地对象。

4.3.8 宿主对象

宿主对象指的是由 ECMAScript 实现提供的,使 ECMAScript 的执行环境变得完备的对象。所有非本地对象都是宿主对象。

4.3.9 未定义值

未定义值是 一种原语值,当一个变量未被赋值时被使用。

4.3.10 Undefined 类型

类型 Undefined 仅有一个值,叫做 undefined

4.3.11 空值

空值是一种原语值,用来代表0,空或不存在的引用。

4.3.12 Null 类型

类型 Null 仅有一个值,叫做 null

4.3.13 布尔值

布尔值Boolean 的成员,它是两个特殊的值之一,truefalse

4.3.14 Boolean 类型

Boolean 类型仅用两个特殊的值表示逻辑实体。其一被称为 true 另一个被称为 false

4.3.15 Boolean 对象

Boolean 对象是 Object 类型的一个成员,内置 Boolean 对象的实例。也就是说,在 new 表达式中使用 Boolean 构造函数提供一个布尔值参数创建出 Boolean 对象。结果对象拥有一个隐含(无命名的)属性是那个布尔值。一个 Boolean 对象可被强制为一个布尔值。

4.3.16 字符串值

字符串值String 类型的一个成员,它是一个有序的、长度有限的的列表,包含零个或更多16位无符号整数。

NOTE 即使每个值常常表示单个16位 UTF-16文本,语言也不给出关于期望这些值被表示为16位无符号整数的限制或要求。

4.3.17 String 类型

String 类型是所有字符串值的集合。

4.3.18 String 对象

String 对象是 Object类 型的一个成员,内置 String 对象的实例。也就是说,在 new 表达式中使用 String 构造函数提供一个字符串参数创建出 String 对象。结果对象拥有一个隐含(无命名的)属性是那个字符串值。通过以函数的方式调用 String 构造函数可以把一个 String 对象强制为一个字符串值。

4.3.19 数值

数值Number 类型的一个成员,是数字的直接表示。

4.3.20 Number 类型

Number 类型是表示数字的值的集合。在 ECMAScript 中,表示双 精度64位格式(IEEE 754)的值的集合包含特殊值“Not-a-Number”(非数字,NaN),正无穷和负无穷。

4.3.21 Number 对象

Number 对象是 Object类 型的一个成员,内置 Number 对象的实例。也就是说,在 new 表达式中使用 Number 构造函数提供一个数值参数创建出 Number 对象。结果对象拥有一个隐含(无命名的)属性是那个数值。通过以函数的方式调用 Number 构造函数可以把一个 Number 对象强制为一个数值(15.7.1)。

4.3.22 Infinity(无穷)

原语值 Infinity 表示正无穷数值。这个值是 Number 类型的一个成员。

4.3.23 NaN

原语值 NaN 表示 IEEE 标准“非数字”值。这个值是 Number 类型的一个成员。


//重新调整了文档结构,这篇可以发了。

May 25, 2007

ECMA-262试译:4.2.1 Objects

4.2.1 对象

ECMAScript 并没有严格意义上的类,在这一点上不同于 C++、Smalltalk 或者 Java,不过作为替代,它支持构造函数(constructors),利用它,可通过执行代码创建对象:给对象分配 存储,然后通过赋初始值来初始化对象属性的全部或部分。所有的构造函数都是对象,但并非所有的对象都是构造函数。每个构造函数都有一个 Prototype(原 型)属性,被用于实现基于原型继承(prototype-based inheritance)共 享属性(shared properties)。使用构造函数来创建对象要用到 new(新 建)表达句;举个例子,new String("A String") 创建了一个新的字符串对象。不使用 new 调用构造函数的后果取决于这个构造函数本身。举个例子,String("A String") 产生一个原语字符串而非对象。

ECMAScript 支持基于原型继承。每一个构造函数都有一个相关联的原型,它创建的所有 对象都拥有一个隐含的引用指向那个与其构造函数相关联的原型(所谓的对象的原型)。更深一步讲,原型还可能会拥有 到它的原型的隐含的非空引用,依此类推;这被称为原型链(prototypechain)。当引用被作为对象中的属性 时,在原型链中第一个包含以这个引用的属性名为名的属性的对象,它的那个属性就是被引用者。换句话说,首先检查被直接提及的对象是否包含某个属性;如果那 个对象包含同名属性,被引用指向的就是这个属性;如果那个对象并没有包含同名属性,接下来检查它的原型,如此继续下去。

通常,在基于类的面向对象语言中,实例装载状态,类装载方法,且继承的仅仅是结构和行为。而在 ECMAScript 中,状态和方法均由对象装载,且结构、行为、状态都会被继承。

所有没有直接包含某个它们的原型所包含的特定属性的对象,与它们的原型共享那个属性和它的值。下面的图表阐述了一切:

CF 是一个构造函数(当然也是一个对象)。使用 new 表达式,我们创建了五个对象:cf1,cf2,cf3,cf4 和 cf5。这些对象中的每一个都包含了名为 q1 和 q2 的属性。虚线表示隐含的原型关系;比方说,cf3 的原型是 CFp。构造函数 CF自己拥有两个属性,名为 P1 和 P2,它们对于 CFp,cf1,cf2,cf3,cf4 或 cf5 而言都是不可见的。CFp 中名为 CFP1 的属性被 cf1,cf2,cf3,cf4 和 cf5 (除了 CF)共享,这样,CFp 的隐含原型链中的所有属性没有名为 q1,q2 或 CFP1 的。需要注意的是,CF 和 CFp 之间没有隐含的原型关联。

不同于基于类的面向对象语言,ECMAScript 中的属性可以通过给它们赋值的方式,把它们动态添加给对象。也就是说,构造函数不需要给所构造的对象的部分或全部属性命名或赋值。在上面的图表中,通过给 CFp 中的属性赋新值就可以给 cf1,cf2,cf3,cf4 和 cf5 添加新的共享属性。


//觉得这篇是我到目前为止翻的最好的 :)