第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强大表现力的来源。
调用
除了声明时定义的形式参数,每个函数还接受两个附加的参数:this
和 arguments
。
参数 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中却会成为糟糕的建议,因为他缺少块级作用域。
最好的做法是在函数的顶部声明函数中可能用到的所有变量。
闭包
闭包就是函数的内部函数,可以引用定义在其外部作用于 的变量。闭包比创建他们的函数有更长的生命周期,而且闭包内部存储的其外部变量的引用