JavaScript语言精粹-读书笔记

前言:很久之前读过一遍该书,近日得闲,重拾该书,详细研究一方,欢迎讨论指正。

目录:

1、精华

2、语法

3、对象

4、函数

5、继承

6、数组

7、正则表达式

8、方法

9、代码风格

10、优美的特性

附录A 毒瘤

附录B 糟粕

附录C JSLint

附录D 语法图

附录E JSON

正文:

第1章 精华

本书的目的就是要揭示JavaScript中的精华,让大家知道它是一门杰出的动态编程语言。

JavaScript是建立在一些非常优秀的想法和少数非常糟糕的想法之上。

那些优秀的想法包括函数、弱类型、动态对象和富有表现力的对象字面量表示法。

那些糟糕的想法包括基于全局变量的编程模型。

JavaScript的函数是基于词法作用域的*对象。

JavaScript是1门弱类型语言。

JavaScript有非常强大的对象字面量表示法。

JavaScript的继承是原型继承

JavaScript依赖于全局变量来进行连接。

所有编译单元的所有*对象被撮合到一个被称为全局对象的命名空间中。

本文贯彻始终都会用到一个method方法去定义新方法:

Function.prototype.method = function (name, func){
  this.prototype[name] = func;
  return this;
}

第2章 语法

本章介绍JavaScript的精华部分的语法,并简要地概述其语言结构。

空白

空白可能表现为被格式化的字符或注释的形式。

如:

var that = this;

var 和 that 之间的空格不能移除,但是其他的空格可以移除。

JavaScript提供两种注释形式,一种是用/* */包围的块注释。一种是以//开头的行注释。注释应该优先被用来提高程序的可读性。其中,块注释遇到正则表达式的时候,对于被注释的代码来说是不安全的。

标识符

标识符由一个字符开头,其后可选择性地加上一个或多个字母、数字或下划线。标识符不能使用保留字。

JavaScript不允许使用保留字来命名变量或参数,同时,不允许在对象字面量中,或者用点运算符提取对象属性时,使用保留字作为对象的属性名。

数字

JavaScript只有一个数字类型。它在内部被表示为64位的浮点数,和Java的double数字类型一样。

它没有分离出整数类型,所以:

1 === 1.0  // true
100 === 1e2 // true

NaN是一个数值,它表示不能产生正常结果的运算结果。NaN不等于任何值,包括它本身。可以用isNaN(number)检测NaN。

Infinity表示所有大于1.79769313486231570e+308的值。

数字拥有方法。JavaScript有一个对象Math,它包含一套作用于数字的方法。例如,可以用Math.floor(number)方法把一个数字转换成一个整数。

字符串

字符串字面量可以被包在一对单引号或双引号中,它可能包含0个或多个字符。

\(反斜线符号)是转义字符。JavaScript在被创建的时候,Unicode是一个16位的字符集,所以JavaScript中的所有字符都是16位的。

JavaScript没有字符类型。要表示一个字符,只需创建一个包含一个字符的字符串即可。

转义字符用来把那些正常情况下不被允许的字符插入到字符串中,比如反斜线、引号和控制字符。\u约定用来指定数字字符编码。

"A" === "\u0041" // true

字符串有一个length属性。例如,"seven".length是5。

字符串是不可变的。

语句

在web浏览器中,每个<script>标签提供一个被编译且立即执行的编译单元。因为缺少链接器,JavaScript把它们抛到一个公共的全局命名空间中。

当var语句被用在函数内部时,它定义的是这个函数的私有变量。

语句通常按照从上到下的顺序被执行。JavaScript可以通过条件语句(if 和 switch),循环语句(while\for\do)、强制跳转语句(break\return\throw)和函数调用来改变执行序列。

代码块是包在一对花括号中的一组语句。JavaScript中的代码块不会创建新的作用域,因此变量应该被定义在函数的头部,而不是在代码块中。

下面的值被当做假(6个)

false

null

undefined

空字符串''

数字0

数字NaN

其它所有的值都被当做真,包括true、字符串"false",以及所有的对象。

case从句防止继续执行下一个case,case从句后应该加一个强制跳转语句,break。

for...in语句

会枚举一个对象的所有属性名(或键名)

var x;
var mycars = {};
mycars["name"] = "BMW";
mycars["color"] = "green";

Object.prototype.size = "6meter";

for (x in mycars){
  console.log(mycars[x]); //BMW green 6meter
}

通常需要检查object.hasOwnProperty(variable)来确定这个属性是否是该对象的成员,还是来自于原型链。

for (x in mycars){
  if(mycars.hasOwnProperty(x)){
    console.log(mycars[x]); //BMW green
}
}

return语句会导致从函数提前返回。它也可以指定要返回的值,如果没有指定返回表达式,那么返回值是undefined。

typeof运算符产生的值有number、string、boolean、undefined、function、object。

如果运算数是一个数组或null,那么typeof的结果是object,其实是的。

函数调用运算符是跟随在函数名后面的一对圆括号。

字面量

对象字面量是一种可以方便地按指定规格创建新对象的表示法。属性名可以是标识符或字符串。

这些名字被当做字面量名而不是变量名来对待,所以对象的属性名在编译时才能知道。

函数

函数字面量定义了函数值,它可以有一个可选的名字,用于递归地调用自己。它可以指定一个参数列表,这些参数就像变量一样,在调用时由传递的实际参数初始化。

函数的主体包括变量定义和语句。

第3章 对象

JavaScript的简单数据类型(5种)包括数字、字符串、布尔值(true和false)、null和undefined。其他所有的值都是对象。

JavaScript中的对象是可变的键控集合。

数组是对象,函数是对象,正则表达式是对象,对象也是对象。

对象是属性的容器,其中每个属性都拥有名字和值。属性的名字是可以包括空字符串在内的任意字符串。属性值可以是除undefined值之外的任何值。

var obj = {
  "": 'aa' ;
}
obj[""]; // aa

JavaScript里的对象是无类型的。它对新属性的名字和属性的值没有限制。对象适合用于汇集和管理数据。对象可以包含其他对象,所以他们可以很容易地表示成树状或图形结构。

JavaScript包含一种原型链的特性,允许对象继承另一个对象的属性。正确地使用它,能减少对象初始化时消耗的时间和内存。

对象字面量

一个对象字面量就是包围在一对花括号中的零个或多个"名/值"对。对象字面量可以出现在任何允许表达式出现的地方。

var empty_object = {};

var stooge = {
  "first-name": "Jerome",
  "last-name": "Howard"
}

在对象字面量中,如果属性名是一个合法的JavaScript标识符且不是保留字,则并不强求要用引号括住属性名。如:"first-name"是必须的,first_name是可选的。用逗号来分隔多个"名/值"对。

属性的值可以从包括另一个对象字面量在内的任意表达式中获得。对象是可以嵌套的。

var flight = {
  airline: "Oceanic",
  number: 815,
  arrival: {
    IATA: "LAZ",
    time: "2004-09-23 10:42",
    city: "Los Angeles"
  }
}

检索

要检索对象里面的值,可以采用在[]后缀中括住一个字符串表达式的方式。

如果字符串表达式是一个字符串字面量,而且它是一个合法的JavaScript标识符且不是保留字,那么也可以用.表示法代替。优先考虑使用.表示法,因为它更紧凑且可读性更好。

stooge["first-time"]; // "Jerome"
flight.departure.IATA;  // "LAZ"

如果你尝试检索一个并不存在的成员属性的值,将返回undefined。

stooge["middle-name"]; //undefined
flight.status; // undefined
stooge["FIRST-NAME"]; //undefined

||运算符可以用来填充默认值:

var middle = stooge["middle-name"] || "(none)"
var status = flight.status || "unknown";

尝试从undefined的成员属性中取值将会导致TypeError异常。这时可以通过&&运算符来避免错误。

var stooge = {
  first_name: 'bally'
}
stooge.last_name; // undefined
stooge.last_name.name; // throw "TypeError"
stooge.last_name && stooge.last_name.name; // undefined

更新

对象里的值可以通过赋值语句来更新。如果属性名已经存在于对象里,那么这个属性的值就会被替换。

stooge['first-name'] = 'Jome';

如果对象之前没有拥有那个属性名,那么该属性就会被扩充到对象中。

stooge['middle-name'] = 'Lester';
stooge.nickname = 'Curly';
flight.equipment = {
  model: 'Boeing 777'
};
flight.status = 'overdue';

引用

对象通过引用来传递,它们永远不会被复制。

var stooge = {
    first_name: 'bally'
}
var x = stooge;  // undefined
x.nickname = 'Curly'  // "Curly"
stooge.nickname;  "Curly"

var a = {}, b = {}, c = {};
a = b = c = {}; //a、b和c都引用同一个空对象

原型

每个对象都连接到一个原型对象,并且它可以从中继承属性。所有通过对象字面量创建的对象都连接到Object.prototype,它是JavaScript中的标配对象。

当创建一个对象时,可以选择某个对象作为它的原型。

给Object增加一个create方法。这个方法创建一个使用原对象作为其原型的新对象。

if(typeof Object.create !== 'function'){
    Object.create = function (o){
     var F = function () {};
    F.prototype = o;
    return new F();
  }
}
var another_stooge = Object.create(stooge);
another_stooge.nickname; // 'Curly'

原型连接只有在检索值时才被用到,如果我们尝试去获取对象的某个属性值,但该对象没有此属性名,那么JavaScript会试着从原型对象中获取属性值。如果该原型对象也没有此属性,再从它的原型中寻找,依次类推,直到该过程最后到达终点Object.prototype。

原型关系是一种动态的关系。如果我们添加一个新的属性到原型中,该属性会立即对所有基于该原型创建的对象可见。

stooge.profession = 'actor';
another_stooge.profession; // 'actor'

反射

检查对象并确定对象有什么属性。

typeof flight.number;  // 'number'
typeof flight.status;  // 'string'
typeof flight.arrival;  // 'object'
typeof flight.manifest;  // 'undefined'

请注意原型链中的任何属性都会产生值。

typeof flight.toString;  // 'function'
typeof flight.constructor;  // 'function'

使用hasOwnProperty方法,如果对象拥有独有的属性,将返回true。hasOwnProperty方法不会检查原型链。

flight.hasOwnProperty('number'); // true
flight.hasOwnProperty('constructor'); // false

枚举

for...in语句可用来遍历一个对象中的所有属性名。该枚举过程将会列出所有的属性,包括函数和你可能不关心的原型中的属性。

最常用的过滤方法就是hasOwnProperty方法,以及使用typeof来排成函数。

var Stooge = function(){}
Stooge.prototype.nick_name = 'nick';
Stooge.prototype.getName = function(){
  console.log(this.nick_name)
}
for(name in another_stooge){
  if(typeof another_stooge[name] !== 'function'){  //过滤函数
    console.log(name)
  }
}

删除

delete运算符可以用来删除对象的属性。如果对象包含该属性,那么该属性就会被删除。它不会触及原型链中的任何对象。

删除对象的属性可能会让原型链中的属性透现出来。

another_stooge.nickname;  // 'Moe';
delete another_stooge.nickname;
another_stooge.nickname;  // 'Curly'

减少全局变量污染

JavaScript可以很随意地定义全局变量来容纳你的应用的所有资源。遗憾的是,全局变量削弱了程序的灵活性,应该避免使用。

最小化使用全局变量的方法之一是为你的应用只创建一个唯一的全局变量。

var MYAPP = {};

此时,该变量变成了你的应用的容器:

MYAPP.stooge = {
  "first-name": "Joe",
  "last-name": "Howard"
}
MYAPP.flight = {
  airline: "Oceanic",
  number: 815,
  arrival: {
    IATA: "LAX",
    time: "2004-09-23 10:42",
    city: "Los Angeles"
 }
}

只要把全局性的资源都纳入一个名称空间之下,你的程序与其他应用程序、组件、类库之间发生冲突的可能性就会显著降低。很明显,MYAPP.stooge指向的是顶层结构。

第4章 函数

JavaScript设计地最出色的就是它的函数的实现。函数用于指定对象的行为。

函数对象

JavaScript中的函数就是对象。

对象字面量产生的对象连接到Object.prototype。

函数对象连接到Function.prototype(该原型对象本身连接到Object.prototype)

每个函数在创建时都会附加两个隐藏属性:函数的上下文和实现函数行为的代码。

每个函数对象在创建时都配有一个prototype属性。它的值是一个拥有constructor属性且值为该函数的对象。

constructor指向该对象的构造函数。

function abc(){}
abc.constructor; //function Function() { [native code] }
var bbc = new abc();
bbc.constructor; //function abc(){}

因为函数是对象,所以他们可以像任何其他的值一样被使用。函数可以保存在变量、对象和数组中。

函数可以被当做参数传递给其他函数,函数也可以再返回函数。而且,因为函数是对象,所以函数也可以拥有方法。

函数的与众不同在于他们可以被调用。

函数字面量

函数对象通过函数字面量来创建:

//创建一个名为add的变量,并用来把两个数字相加的函数赋值给它
var add = function (a, b){
  return a + b;
}

函数字面量包括4个部分:

第1部分:保留字function。

第2部分:函数名,可以被省略(匿名函数)。

第3部分:包围在圆括号中的一组参数,在该函数被调用时,初始化为实际提供的值。

第4部分:包围在花括号中的一组语句。这些语句是函数的主体,在函数被调用时执行。

通过函数字面量创建的函数对象包含一个连到外部上下文的连接。这被称为闭包。

调用

调用一个函数会暂停当前函数的执行,传递控制权和参数给新函数。

除了声明时定义的形式参数,每个函数还接收两个附加的参数:this和arguments。

在JavaScript中一共有4种调用模式:

方法调用模式、函数调用模式、构造器调用模式、apply调用模式。

如果实际参数值过多,超出的参数值会被忽略。

如果实际参数值过少,超出的参数值会被替换为undefined。

对参数值不会进行类型检查:任何类型的值都可以被传递给任何参数。

方法调用模式

当一个函数被保存为对象的一个属性时,我们称它为一个方法。当一个方法被调用时,this被绑定到该对象。

//创建myObject对象,它有一个value属性和一个increment方法
//increment方法接受一个可选的参数,如果参数不是数字,默认使用1
var myObject = {
  value: 0,
  increment: function(inc){
    this.value += typeof inc === 'number' ? inc : 1;
  }
}
myObject.increment();
myObject.value;
myObject.increment(2);
myObject.value; 

方法可以使用this访问自己所属的对象,所以它能从对象中取值或对对象进行修改。

通过this可取得它们所属对象的上下文的方法称为公共方法。

函数调用模式

当一个函数并非一个对象的属性时,那么它就是被当做一个函数来调用的。

以此模式调用函数时,this被绑定到全局对象。

var myObject = {
  value: 0,
  increment: function(inc){
    this.value += typeof inc === 'number' ? inc : 1;
  }
}
myObject.double = function(){
  var that = this;
  var helper = function(){
      that.value = add(that.value, that.value);  //this指向全局对象
  }
  helper();
}
function add(a, b){
  return a + b;
}
myObject.increment(2)
myObject.double();
myObject.value;

构造器调用模式

JavaScript是一门基于原型继承的语言。这意味着对象可以直接从其他对象继承属性。该语言是无类型的。

如果在一个函数前面带上new来调用,那么背地里将会创建一个连接到该函数的prototype成员的新对象,同时this会被绑定到那个新对象上。

var Quo = function(string){
  this.status = string;
}
Quo.prototype.get_status = function(){
  return this.status;
}
var myQuo = new Quo('confused');
myQuo.get_status();  // "confused"

一个函数,如果创建的目的就是希望结合new前缀来调用,那它就被称为构造器函数。

不推荐使用这种形式的构造器函数。

Apply调用模式

apply方法让我们构建一个参数数组传递给调用函数。允许我们选择this的值。

apply方法接收两个参数。第1个是要绑定给this的值,第2个就是一个参数数组。

var array = [3,4];
function add(a,b){
  return a + b;
}
var sum = add.apply(null, array);
sum;  

//构造一个包含status成员的对象
var statusObject = {
  status: 'A-OK'
}
var status = Quo.prototype.get_status.apply(statusObject);

参数

当函数被调用时,会得到一个免费配送的参数,那就是arguments数组。

函数可以通过此参数访问所有它被调用时传递给它的参数列表,包括那些没有被分配给函数声明时定义的形式参数的多余参数。这使得编写一个无需指定参数个数的函数成为可能。

var sum = function(){
  var i, sum = 0;
  for(i = 0; i < arguments.length; i++){
    sum += arguments[i]
  }
  return sum;
}
sum(2,4,6,8);  

返回

当一个函数被调用时,它从第一个语句开始执行,并在遇到关闭函数体的}时结束。然后函数把控制权交还给调用该函数的程序。

return语句可用来使函数提前返回。当return语句被执行时,函数立即返回而不再执行余下的语句。

一个函数总是会返回一个值。如果没有指定返回值,则返回undefined。

如果函数调用时在前面加上了new前缀,且返回值不是一个对象,则返回this(该新对象)。

异常

var add = function(a, b){
  if(typeof a !== 'number' || typeof b !== 'number'){
    throw {
      name : 'TypeError',
      message: 'add needs numbers'
    }
  }
  return a + b;
}
var try_it = function(){
  try {
    add('seven');
  } catch(e){
    console.log(e.name + ": " + e.message)
  }
}
try_it(); // TypeError: add needs numbers

一个try语句只会有一个捕获所有异常的catch代码块。如果你的处理手段取决于异常的类型,那么异常处理器必须检查异常对象的name属性来确定异常的类型。

扩充类型的功能

给Function.prototype增加方法来使得该方法对所有函数可用:

Function.prototype.method = function(name, func){
  this.prototype[name] = func;
  return this;
}

通过给Function.prototype增加了一个method方法,下次给对象增加方法的时候就不必键入prototype了。

通过给Number.prototype增加一个integer方法来取整:

Number.method('integer', function(){
  return Math[this < 0 ? 'ceil' : 'floor'](this);
});
(-10 / 3).integer();

增加一个移除字符串首尾空白的方法:

String.method('trim', function(){
  return this.replace(/^\s+|\s+$/g, '');  //\s匹配任何不可见字符,包括空格、制表符、换页符等
})

因为JavaScript原型继承的动态本质,新的方法立刻被赋予到所有的对象实例上。

基本类型的原型是公共结构,所以在类库混用时务必小心。一个保险的做法就是只在确定没有该方法时才添加它。

Function.prototype.method = function(name, func){
  if(!this.prototype[name]){
    this.prototype[name] = func;
  }
  return this;
}

递归

递归函数可以非常高效地操作树形结构,比如浏览器端的文档对象模型(DOM),每次递归调用时处理指定的树的一小段。

//定义walk_the_DOM函数,它从某个指定的节点开始,按HTML源码中的顺序访问该树中的每个节点
//它会调用一个函数,并依次传递每个节点给它
//walk_the_DOM调用自身去处理每一个子节点
var walk_the_DOM = function walk(node, func){
  func(node);
  node = node.firstChild;
  while(node){
    walk(node, func);
    node = node.nextSibling;
  }
}
//定义一个getElementsByAttribute函数它以一个属性名称字符串和一个可选的匹配值作为参数
//它调用walk_the_DOM,传递一个用来查找节点属性名的函数作为参数
//匹配的节点会累加到一个结果数组中
var getElementsByAttribute = function (att, value) {
  var results = [];
  walk_the_DOM(document.body, function(node){
    var actual = node.nodeType === 1 && node.getAttribute(att);
    if(typeof actual === 'string' && (actual === value || typeof value !== 'string')){
      results.push(node);
    }
  })
}
getElementsByAttribute('class','body')

作用域

JavaScript不支持块级作用域,支持函数作用域。

闭包

作用域的好处是内部函数可以访问定义它们的外部函数的参数和变量(除了this和arguments)。

一个更有趣的情形是内部函数拥有比它的外部函数更长的生命周期

构造一个myObject对象,拥有一个value属性和一个increment方法,假定我们希望保护该值不会被非法更改。

通过调用一个函数的形式去初始化myObject,该函数会返回一个对象字面量。

函数里定义了一个value变量。该变量对increment和getValue来说是可用的,但函数的作用域使得它对其他的程序来说总是不可见的。

var myObject = (function(){
  var value = 0;
  return {
    increment: function(inc){
      value += typeof inc === 'number' ? inc : 1;
    },
    getValue: function(){
      return value;
    }
  }
}())

status是私有属性,定义另一种形式的Quo函数:

var quo = function(status){
  return {
    get_status: function(){
      return status;
    }
  }
}
//构造一个quo实例
var myQuo = quo('amazed');
myQuo.get_status();

备注:这个quo函数被设计成无须在前面加上new来调用,所以名字也没有大写。

来看一个更有趣的例子:

//定义一个函数,它设置一个DOM节点为黄色,然后把它渐变为白色
var fade = function(node){
  var level = 1;
  var step = function(){
    var hex = level.toString(16);
    node.style.backgroundColor = '#FFFF' + hex + hex;
    if(level < 15){
      level += 1;
      setTimeout(step, 100);
    }
  }
  setTimeout(step, 100);
}
fade(document.body);

理解内部函数访问外部函数的实际变量而无须复制是很重要的。

var add_the_handlers = function(nodes){
  var i;
  for(i = 0; i < nodes.length; i++){
    nodes[i].onclick = function(e){
      alert(i)
    }
  }
}

add_the_handlers函数的本意是想传递给每个事件处理器一个唯一值,但它未能达到目的,因为事件处理器函数绑定了变量i本身,而不是函数在构造时的变量i的值。

var add_the_handlers = function(nodes){
  var helper = function(i){
    return function(e){
      alert(i)
    }
  }
  var i;
  for(i = 0;i < nodes.length; i++){
    nodes[i].onclick = helper(i);
  }
}

避免在循环中创建函数,在循环之外创建一个辅助函数,让这个辅助函数再返回一个绑定了当前i值的函数。

回调

发起异步请求,提供一个当服务器的响应到达时随即触发的回调函数。

异步函数立即返回,这样客户端就不会被阻塞。

request = prepare_the_request();
send_request_asynchronously(request, function(response){
  display(resposne);
})

模块

我们可以使用函数和闭包来构造模块。

模块是一个提供接口却隐藏状态与实现的函数或对象。

通过使用函数产生模块,几乎可以完全摒弃全局变量的使用。

Function.prototype.method = function(name, func){
  if(!this.prototype[name]){
    this.prototype[name] = func;
  }
  return this;
}
String.method('deentityfy', function(){
  var entity = {
    quot: '"',
    lt: '<',
    gt: '>'
  };
  return function(){
    return this.replace(/&([^&;]+);/g,
      function(a,b){
        var r = entity[b];
        return typeof r === 'string' ? r : a;
      }
     )
   }
}())
'&lt;&quot;&gt;'.deentityfy();

模块模式的一般形式是:一个定义了私有变量和函数的函数,利用闭包创建可以访问私有变量和函数的特权函数,最后返回这个特权函数,或者把它们保存到一个可以访问到的地方。

使用模块模式可以完全摒弃全局变量的使用,它促进了信息隐藏和其他优秀的设计实践。

对于应用程序的封装,或者构造其他单例对象,模块模式非常有效。

模块模式可以用来产生安全的对象,假定我们想要构造一个用来产生序列号的对象:

var serial_maker = function(){
  //返回一个用来产生唯一字符串的对象
  //唯一字符串由两部分组成,前缀+序列号
  //该对象包含一个设置前缀的方法,一个设置序列号的方法
  // 和一个产生唯一字符串的gensym方法
  var prefix = '';
  var seq = 0;
  return {
    set_prefix: function(p){
      prefix = String(p);
    },
    set_seq: function(s){
      seq = s;
    },
    gensym: function(){
      var result = prefix + seq;
      seq += 1;
      return result;
    }
  }
}
var seqer = serial_maker();
seqer.set_prefix('Q');
seqer.set_seq(1000);
var unique = seqer.gensym();

seqer包含的方法没有用到this或that,因此没有办法损害seqer。

级联

有一些方法没有返回值,如果让这些方法返回this而不是undefined,就可以启用级联。

如:

getElement('myBoxDiv')
  .move(350,150)
  .width(100)
  .height(100)

柯里化

函数也是值,从而我们可以用更有趣的方式操作函数值。柯里化允许我们把函数与传递给它的函数相结合,,产生一个新的函数。

Function.method('curry', function(){
  var slice = Array.prototype.slice,
  args = slice.apply(arguments),
  that = this;
  return function(){
    return that.apply(null, args.concat(slice.apply(arguments)))
  }
})
function add(a,b){
  return a + b;
}
var add1 = add.curry(1);
add1(6); 

第5章

JavaScript是一门基于原型的语言,这意味着对象直接从其他对象继承。

伪类

JavaScript不直接让对象从其他对象继承,反而插入了一个多余的间接层:通过构造器函数产生对象。

当一个函数对象被创建时,Function构造器产生的函数对象会运行类似这样的一些代码:

this.prototype = {constructor: this};

新函数对象被赋予一个prototype属性,它的值是一个包含constructor属性且属性值为该函数的对象。

这个prototype对象是存放继承特征的地方。因为JavaScript语言没有提供一种方法去确定哪个杉树是打算用来做构造器的,所以每个函数都会得到一个prototype对象。

采用构造器调用模式,用new前缀去调用一个函数,函数执行的方式会被修改。

如果new运算符是一个方法而不是一个运算符,可能会像这样执行:

Function.method('new',function(){
//创建一个新对象,它继承自构造器函数的原型对象
var that = Object.create(this.prototype);
//调用构造器函数,绑定this到新对象上
var other = this.apply(that, arguments);
//如果它的返回值不是一个对象,就返回该对象
return (typeof other === 'object' && other) || that;
})

定义一个构造器并扩充它的原型:

var Mammal = function(name){
  this.name = name;
}
Mammal.prototype.get_name = function(){
  return this.name;
}
Mammal.prototype.says = function(){
  return this.sayings || '';
}

现在可以构造一个实例:

var myMammal = new Mammal('Herb the Mammal');
var name = myMammal.get_name();

构造另一个伪类来继承Mamal,这是通过定义它的constructor函数并替换它的prototype为一个Mammal的实例。

var Cat = function(name){
  this.name = name;
  this.saying = 'meow';
}
//替换Cat.prototype为一个新的Mammal实例
Cat.prototype = new Mammal();
//扩充新原型对象,增加purr和get_name方法
Cat.prototype.purr = function(n){
  var i, s = '';
  for(i = 0; i < n; i++){
    if(s){
      s += '-';
    }
    s += 'r';
  }
  return s;
}
Cat.prototype.get_name = function(){
  return this.says() + ' ' + this.name + ' '  + this.says();
}
var myCat = new Cat('Herny');
var says = myCat.says();
var purr = myCat.purr(5);
var name = myCat.get_name();

伪类模式本意是想向面向对象靠拢,但它看起来格格不入。我们可以隐藏一些丑陋的细节,通过使用method方法来定义一个inherits方法实现。

Function.method('inherits', function(Parent){
  this.prototype = new Parent();
  return this;
})

这里,inherits和method方法都返回this,可以采用级联的形式编程。

现在可以只用一行语句构造我们的cat对象。

var Cat = function(name){
  this.name = name;
  this.saying = 'memo'
}
.inherits(Mammal)
.method('purr', function(n){
  var i, s = '';
  for(i = 0; i < n; i++)
    if(s){
      s += '-';
    }
    s += 'r';
  }
  return s;
})
.method('get_name',function(){
  return this.says() + ' ' + this.name + ' '  + this.says();
})

原型

先用对象字面量去构造一个有用的对象:

var myMammal = {
  name: 'Herb the Mammal',
  get_name: function(){
    return this.name;
  },
  says: function(){
    return this.saying || '';
  }
}

一旦有了一个想要的对象,就可以利用Object.create方法构造出更多的实例。

var myCat = Object.create(myMammal);
myCat.get_name

函数化

应用模块模式来保护私有变量。

我们从构造一个生成对象开始,以小写字母开头来命名它,因为它并不需要使用new前缀。

该函数包括4个步骤:
1、创建一个新对象

2、有选择地定义私有实例变量和方法

3、给这个新对象扩充方法

4、返回那个新对象

var constructor = function(spec , my){
  var that,其他的私有变量;
  my = my || {};
  把共享的变量和函数添加到my中
  that = 一个新对象
  添加给that特权方法
  return that;
}

把这个模式应用到mammal中。

var mamal = funtion(spec){
  var that = {};
  that.get_name = function(){
    return spec.name;
  };
  that.says = function(){
    return spec.saying || '';
  }
  return that;
}
var myMammal = mammal({name: 'Herb'})

函数化模式还给我们提供了一个处理父类的方法。

我们会构造一个superior方法,它取得一个方法名并返回调用那个方法的函数。该函数会调用原来的方法,尽管属性已经变化了。

Object.method('superior', function(){
  var that = this,method = that[name];
  return function(){
    return method.apply(that, arguments);
  }
})

第6章

数组字面量

var empty= [];
var numbers = [
  'zero','one'
]

对象字面量:

var numbers_object = {
  '0': 'zero', '1': 'one'
}

明显的差异

numbers继承自Array.prototype

numbers_object继承自Object.prototype

长度

每个数组都有一个length属性。

如果用大于或等于当前length的数字作为下标来存储一个元素,那么length值会被增大,不会数组越界。

删除

由于JavaScript的数组其实就是对象,所以delete运算符可以用来从数组中移除元素。

delete numbers[2].//杀出之后就是undefined

JavaScript数组有一个splice方法。它可以对数组做个手术,删除一些元素并将它们替换为其他的元素。

numbers.splice(2,1); //第1个参数是数组中的1个序号,第2个参数是要删除的元素个数,任何额外的参数会在序号那个点的位置被插入到数组中。

枚举

for可以避免for..in带来的问题

var i;
for(i = 0; i < myArray.length; i++){
  myArray[i]
}

容易混淆的地方

当属性名是小而连续的整数时,应该使用数组,否则,使用对象。

判断一个对象是否是数组:

var is_array = function(value){
  return Object.prototype.toString.apply(value) === '[object Array]'
}

方法

JavaScript提供了一套可用的方法,是被存储在Array.prototype中的函数。

给array增加一个方法,允许我们对数组进行计算。

Array.method('reduce', function(f,value){
  var i;
  for(i = 0; i < this.length; i++){
    value = f(this[i], value)
  }
  return value;
})
//创建一个数组
var data = [4,8,15,16];
//定义两个简单的函数
var add = fucntion(a, b){
  return a+b;
}
var mult = function(a, b){
  return a * b;
}
var sum = data.reduce(add, 0);
var product = data.reduce(mult, 1);

因为数组本身就是对象,所以可以直接给一个单独的数组添加方法:

//给data数组添加一个total方法
data.total = function(){
  return this.reduce(add, 0)
}
total = data.total;

指定初始值

Array.dim(a,b)创建a个b元素

Array.dim = function(dimension, initial){
  var a = [],i;
  for(i = 0; i < dimension; i++){
    a[i] = initial;
  }
  return a;
}
var myArray = Array.dim(10,0);

构造一个矩阵

Array.matrix = function(m, n, initial){
  var a, i, j, mat = [];
  for(i = 0;i < m; i++){
    a = [];
    for(j = 0;j < n; j++){
      a[j] = initial;
    }
    mat[i] = a;
  }
  return mat;
}

构造一个单位矩阵

Array.identity = function(n){
  var i,mat = Array.matrix(n,n,0);
  for(i = 0;i < n; i++){
    mat[i][i] = 1;
  }
  return mat;
}
myMatrix = Array.identity(4);
myMatrix[3][3];  

第7章 正则表达式

上一篇:UML类图中的几种关系总结


下一篇:JavaScript语言精粹学习笔记