JavaScript语言精粹

第1章 精华


JavaScript建立在一些非常优秀的想法和少数非常糟糕的想法之上
优秀的想法包括:函数、弱类型、动态对象和非常富有表现力的对象字面量表示法
糟糕的想法:基于全局变量的编程模型

第2章 语法


数字

JavaScript只有一个数字类型,在内部被表示为64位浮点数,
Infinity表示所有大于1.79769313486231570e+308的值。
NaN表示不能产生正常的值,NaN不等于任何值,包括他自己。可以使用ES5的isNaN(number)来检测NaN

语句

这些值判断时,被当作假:false、null、undefined、空字符串" "、数字0 、数字NaN

第3章 对象


JavaScript的简单类型包括number数字、string 字符串、布尔值 booler 、null 值、undefined值 (特点:不可变)。其他的所有值都是对象
数字、字符、布尔值“貌似”对象,因为他们拥有方法,但他们是不可变的。
JavaScript中的对象是可变的键控集合,数组是对象、函数是对象、正则表达式是对象、当然对象也是对象。
JavaScript包括一个原型链对象,允许对象继承另一对象的属性。正确使用它能减少对象初始化的时间和内存消耗。

对象字面量

对象字面量提供了一种非常方便地创建新对象值得表示法。
一个对象字面量就是包围在一对花括号中的零或多个"名/值"对。对象字面量可以出现在任何允许表达式出现的地方。
属性的值可以从另一个包括对象自变量在内的任意表达式中获得。对象是可嵌套的。

var  flight ={
  airline:"Oceanic",
  number:815,
  departure:{
    IATA:"SYD",
    time:"2004-09-22",
    city:"Sydney"
  },
  arrival:{
    IATA:"LAX",
    time:"2004-09-23",
    city:"Los Angeles"
  }
};
检索

检索对象中包含的值:
(1)可以采用在[ ]后缀中括住一个字符串表达式的方式 `stooge["first-name"] ;
(2)如果字符串表达式是一个常数,而且他是一个合法的JavaScript标识符而非保留字,那么也可以用 . 表示法代替。
优先考虑 . 表示法,因为它更紧凑可读性也更好。
尝试检索一个undefined值将会导致TypeError异常。这可以通过&&运算符来避免错误

flight.eqipment                                    //undefined
flight.equipment.model                             //throw "TypeError"
flight.equipment&&flight.equipment.model           //undefined

引用

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

var x=stooge ;
x.nickname="Curly";
var nick=stooge.nickname;
//因为x和stooge是指向同一个对象的引用,所以nick为"Curly"
var a={},b={},c={};
//a,b,c 每个都引用一个不同的空对象
a=b=c={};
//a,b,c都引用同一个空对象
参数传递

对象通过引用来传递 ; 基础类型通过值传递

首先验证一下基础类型 例1

function add(num){
  num+=10;
  return num;
}
num = 10;
alert(add(num));
alert(num);
//输出20,10

对于这里输出的20,10,按照js的官方解释,在参数传递的时候,做了一件复制栈帧的动作,这样外部声明的变量num和函数参数的num,拥有完全相同的值,但拥有完全不同的参数地址,两者谁都不认识谁,在函数调用返回的时候弹出函数参数num栈帧。所以改变函数参数num,对原有的外部变量没有一点影响。
( ES5只有全局作用域和函数作用域,没有块级作用域;)

下面看一下对象是如何传递参数的 例2

function setName(obj){
  obj.name="ted";
}
var obj = new Object();
setName(obj);
alert(obj.name);
//输出ted

以上代码实质:创建了一个Object对象,将其引用赋值给 obj (类似 地址的赋值),然后在参数传递的时候,复制了一个栈帧给 函数参数的obj ,所有两者拥有相同的值(不妨将其理解为obj对象的地址),然后在setName做改变的时候,事实上是改变了obj 对象自身的值,在改变完成后同样也要弹出函数参数obj对应的栈帧。所以对应的输出是改变后obj对象的值。

为了验证以上观点,稍微改造 例3

function setName(obj){
  obj.name="ted";
  obj = new Object();
  obj.name="marry";
}
var obj = new Object();
setName(obj);
alert(obj.name);
//输出ted

将一个新对象赋给了函数参数obj,这样 函数参数 和 引用的外部obj 有着完全不同的值和内存地址,即函数参数有了新的堆栈空间。详情见文章

枚举

对象枚举采用for in循环,如果有必要过滤那些不想要的值。最常用的过滤器是hasOwnProperty方法,以及使用typeof来排除函数:

var name;
for(name in another_stooge){
  if(typeof name_stooger[name] !== 'function'){
    document.writeIn(name+':'+another_stooge[name]);
  }
}

属性名出现 的顺序不确定,如果想确保属性一特定的顺序出现,最好的办法就是完全避免使用for in 语句,而是创建一个数组, 采用for循环,这样既可以正确顺序遍历,也不必担心枚举出原型链中的属性。

删除

delete运算符用来删除对象的属性。如果对象包含该属性,那么该属性就会被移除。他不会触及到原型链中任何对象。
删除对象的属性可能会让来自原型链中的属性透现出来

stooge._proto_.nickname='Curly';
stooge.nickname='Moe';

stooge.nickname  //'Moe'
delete stooge.nickname;
stooge.nickname  //'Curly'
减少全局变量的污染

全局变量削弱了程序的灵活性,应该避免使用
最小化使用全局变量的方法之一是,只创建一个唯一的全局变量:

var MyAPP={};

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

MYAPP.stooge={
  "first-name":"Joe",
  "last-name":"Howard"
};

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

第4章 函数


JavaScript设计的最出色的就是函数的实现。它近乎于完美,但是也有瑕疵。
函数包含一组语句,他们是JavaScript的基础模块单元,用于代码复用、信息隐藏和组合调用。函数用于指定对象的行为。一般来说,所谓【编程】就是将一组需求分解成【一组函数】与【数据结构】的技能。

函数字面量

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

var add=function(a,b){
  return a+b;
}

函数字面量包括四个部分:
1.保留字 — function
2.函数名 — 可以省略,此时成为匿名函数
3.包围在圆括号中的一组参数 — 不像普通变量被初始化为undefined,而是在函数被调用时初始化为实际提供的参数的值。
4.包围在花括号中的一组语句 — 是函数的主体,在函数被调用时执行

通过函数字面量创建的函数对象包含一个连到外部上下文的连接。这被称为闭包。是JS强大表现力的来源。

调用

除了声明时定义的形式参数,每个函数还接受两个附加的参数:thisarguments
参数 this在面向对象编程中非常重要,他的值取决于调用的模式。、
在JavaScript中一共有 4 种调用模式:方法调用模式、函数调用模式、构造器调用模式、apply调用模式。这些调用模式在如何初始化关键参数 this上存在差异。

方法调用模式:可以使用this访问自己所属的对象。
当一个函数被保存为对象的一个属性时,我们称之为一个方法。

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

myObject.increment(2);
console.log(myObject.value);    //3

函数调用模式:此模式的this被绑定到全局对象。

var someFn = function(){
  return this === window; // true
}

这是语言设计上的一个错误, 倘若语言设计正确,那么当内部函数被调用时,this仍然绑定到外部函数的this变量。
这个设计错误的后果就是方法不能利用内部函数来帮助它工作,因为内部函数的this被绑定了错误的值,所以不能共享该方法对对象的访问权。
解决办法
如果该方法定义一个变量并把它赋值为this,那么内部函数就可以通过那个变量访问到this

// 给myObject增加一个double方法
myObject.double = function(){
  var that = this  // 变量赋值为 this
  var helper = function(){
    that.value = add(that.value , that.value);
  };
  helper();  //以函数形式调用helper
}

// 以方法形式调用double
myObject.double();
document.writeIn(myObject.value);   // 6

构造器调用模式
JavaScript是一门基于原型继承的语言。这意味着可以直接从其他对象继承属性。该语言是无类型的。
如果在函数前面带上一个new来调用,那么背地里将会创建一个连接到该函数的prototype成员对象,同时this也会被绑定到那个新对象上。
new前缀也会改变return语句的行为。

//创建一个名为Quo的构造器函数。他构造一个带有 status 属性的对象
var Quo= function(string){
  this.status=string;
}
//给Quo的所有实例提供一个名为 get_status 的公共方法
Quo.prototype.get_status = function(){
  return this.status;
}
//构造一个Quo实例
var myQuo=new Quo("confused");
document.writeIn(myQuo.get_status()); //打印显示 "confused"

一个函数,如果创建的目的就是为了结合new前缀来调用,那么他就被称为构造器函数.

Apply调用函数:让我们构建一个参数数组传递给调用函数,允许我们选择this的值

因为JavaScript是一门函数式的面向对象编程语言,所以函数可以拥有方法。

//构造一个包含两个数字的数组,并将它们相加
var array=[3,4];
var sum=add.apply(null,array); //sum 的值为7
//构造一个包含 status 成员的对象
var statusObject = {
  status :'A-OK'
};
//status

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

var sum = function(){
  var i,sum=0;
  function(i=0;i

这是一个语言设计上的错误,arguments并不是一个真正的数组,他只是一个‘类似数组’(array-like)的对象,虽然他有'length'属性,但是并没有任何数组的办法。要使用数组的方法需要用到call函数

返回
return 语句可以使函数提前返回。当return被执行时,函数立即返回而不再执行余下的语句。
一个函数总会返回一个值。如果没有指定返回值,则返回undefined
如果函数调用时在前面加了new前缀,且返回值不是一个对象,则返回this(该新对象)

异常
如果处理手段取决于异常类型,那么异常处理器,必须检查异常对象的name属性来确定异常的类型

var add=function(a,b){
  if(typeof a!=='number' || typeof b!=='number'){
    throw{
      name:'TypeError',
      message:'add needs numbers'
    };
    return a+b;
  }
}
//构造一个try_it 函数,以不正确的方式调用之前的Add函数
var  try_it = function(){
  try{
      add('seven');
  }catch(e){
      console.log(e.name+':'+e.message);
  }
}
try_it();

throw语句中断函数的执行。它应该抛出一个exception对象,该对象包含一个用来识别异常类型的name属性和一个描述性message属性。也可以添加其他属性。
如果 try代码块抛出了异常,控制权就会跳转到它的catch从句。

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

扩充类型的功能
我们可以通过给Function.prototype增加方法来时的该方法对所有函数可用:

//保险的做法是只在确定没有该方法的时候再添加
Function.prototype.method = function(name,func){
  if(!this.prototype[name]){
    this.prototype[name] = func;
  }
  return this;
}

通过给Function.prototype增加一个method方法,下次给对象增加方法的时候就不必须键入prototype这几个字符,省掉了一些麻烦。

实例1:
JavaScript 本身提供的取整方法有些丑陋。我们可以通过给Number.prototype增加一个integer方法来改善,根据数字的正负来判断使用Math.ceiling还是Math.floor.

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

实例2:JavaScript 缺少一个移除字符串收尾空白的方法。

String.method('trim',function(){
  return this.replace(/^\s+|\s+$/g,' ');
});
console.log('"' + "  neat  ".trim()+'"');

递归

/*递归 汉诺塔 */
    var hanoi = function (n,a,b,c){
        if(n>0){
            hanoi(n-1,a,c,b);
            console.log('Move n' + n + 'form' + a + 'to' + c );
            hanoi(n-1,b,a,c);
        }
    }
    hanoi(3,"A","B","C");

作用域

JavaScript代码并不支持块级作用域,只有函数作用域。很多现代语言中都推荐尽可能延迟声明变量,而在JavaScript中却会成为糟糕的建议,因为他缺少块级作用域。
最好的做法是在函数的顶部声明函数中可能用到的所有变量。

闭包
闭包就是函数的内部函数,可以引用定义在其外部作用于 的变量。闭包比创建他们的函数有更长的生命周期,而且闭包内部存储的其外部变量的引用

你可能感兴趣的:(JavaScript语言精粹)