月度归档:2015年08月

JavaScript权威指南 – 语言核心 笔记

以下为JavaScript权威指南 – 语言核心读书笔记,版本v1.0,部分内容没有很细致,后续补充,同时修改版本号。

1 JavaScript概述
JavaScript是Sun Microsystem公司(Oracle收购了此公司)的注册商标。而JavaScript这个语言是由网景(现在的Mozilla)实现的(注:由于竞争关系,同时代的相同产物是VBScript,微软已经放弃)。网景将这门语言作为标准提交给了ECMA(欧洲计算机制造协会),由于商标上的冲突(JavaScript是Sun的注册商标,Java的发明者),所以ECMA为这门语言的标准改了一个名字“ECMAScript”,同样由于商标的原因,微软对这门语言的实现叫JScript。一般,用JavaScript来指语言本身,用ECMAScrip来指这个语言的标准。

目前ECMAScript有3、5、6版本。目前绝大多数浏览器都已经实现了第5版本的标准。对于JavaScript 1.5 和 JavaScript 1.8这样的版本号,是Mozilla对JavaScript语言实现的版本编号,1.5对应ECMAScript 3,后续的包含了非标准扩展。另外,JavaScript解释器或引擎也有版本号,比如Google将它的JavaScript解释器命名为V8,它的版本号有3.0等。
……………………..

2 词法结构
2.1 字符集
JavaScript程序是用Unicode字符集编写的(UTF-16)。 注:JS实际只支持2字节Unicode。
2.1.1 区分大小写
JavaScript区分大小写。但HTML不区分大小写(XHTML区分大小写,不过浏览器可以包容)。
2.1.2 空格、换行符和格式控制符
除了普通空格符(\u0020),JavaScript还可以识别如下这些表示空格的字符:
水平制表符(\u0009)、垂直制表符(\u000B)、换页符(\uoooC)、不中断空白(\uooAo)、字节序标记(\uFEFF)。

JavaScript将如下字符识别为行结束符:换行符(\u000A),回车符(\u000D),行分隔符(\u2028),段分隔符(\u2029)。回车符加换行符在一起被解析为一个单行结束符。

2.1.3 Unicode转义序列
JavaScript定义了一种特殊序列,使用6个ASCII字符来代表任意16位Unicode内码。这些Unicode转义序列均以\u为前缀,其后跟随4个十六进制数。这种Unicode转义写法可以用在JavaScript字符直接量、正则表达式直接量和标识符中(关键字除外)。

2.1.4 标准化

2.2 注释
单行注释(//)和块注释(/**/)
2.3 直接量 literal
2.4 标识符和保留字
JavaScript标识符必须以字母、下划线或美元符开始(数字不允许作为首字符出现)。JavaScript允许标识符中出现Unicode字符全集中的字母和数字。
2.5 可选的分号
在return,break和continue和随后的表达式之间不能有换行。(一般应该总是使用分号)

3 类型、值和变量
JavaScript的数据类型分为两类:原始类型和对象类型。原始类型包括数字、字符串和布尔值。JavaScript中有两个特殊的原始值:null(空)和undefined(未定义),它们不是数字,字符串和布尔值。它们通常分别代表了各自特殊类型的唯一的成员。

JavaScript中除了数字、字符串、布尔值、null和undefined之外就是对象。对象是属性的集合,每个属性都由名值对构成。普通的JavaScript对象是命名值的无序集合。

有一个称为全局的对象,一般是指Window对象。它定义了全局的属性。

JavaScript同样定义了一种特殊对象–数组(array),表示带编号的值的有序集合。

JavaScript还定义了另一种特殊对象–函数。函数是具有与它相关联的可执行代码的对象,通过调用函数来运行可执行代码,并返回运算结果。

注:这里把对象跟数字、字符串等放在一起,而它们是类型,所以JavaScript中的所谓对象,也是指类型,务必要区别对象实例。

3.1 数字
JavaScript不区分整数值和浮点数值,所有数字均用浮点数值表示。注:JavaScript中实际的操作则是基于32为整数(如数组的索引)。

3.1.1 整型直接量
除了十进制的整数直接量,JavaScript同样能识别十六进制值(0x或0X为前缀)。ECMAScript 6的严格模式下,八进制直接量是明令禁止的。

3.1.2 浮点型直接量
3.1.3 JavaScript中的算术运算
除了加减乘除和求余这些基本运算符外,JavaScript还支持更加复杂的算术运算:

Math.pow(2,53)
Math.round(.6)
Math.ceil(.6)
Math.floor(.6)
Math.abs(-5)
Math.max(x,y,z)
Math.min(x,y,z)
Math.random()
Math.PI
Math.E
Math.sqrt(3)
Math.pow(3,1/3)
Math.sin(0)
Math.log(10)
Math.log(100)/Math.LN10	//以10为底100的对数
Math.exp(3)		//e的三次幂

JavaScript中的算术运算在溢出(overflow)、下溢(underflow)或被零整除时不会报错。正负无穷大(infinity),在JavaScript中以Infinity表示。

被零整除在JavaScript并不报错:它只是简单的返回无穷大(Infinity)或负无穷大(-Infinity)。有一个例外,零除以零是没有意义的,这个运算结果也是一个非数字值(NaN)。JavaScript预定义了全局变量Infinity和NaN来表示正无穷大和非数字值。

JavaScript中的非数字值有一点特殊:它和任何值都不相等,包括自身。可用函数isNaN()来判断值是否是NaN。

JavaScript中有一个类似的函数isFinite(),在参数不是NaN、Infinity或-Infinity的时候返回true(就是合法正确的数值)。

负零值也特殊,它和正零是相等的。不过作为除数时例外(因为正负零被除得到Infinity和-Infinity, 正无穷与负无穷不相等)

3.1.4 二进制浮点数和四色五入错误
JavaScript通过浮点数形式表示数字,在表示整数时,只是它的一个近似值(意味着判断两个数字是否相等时,总是不等的)

3.1.5 日期和时间

3.2 文本
JavaScript内部使用UTF-16表示,这个编码特点是在2字节时,和Unicode对应的2字节编码一致,超过这个范围就要用4个字节甚至8个字节,但是JavaScript对字符串长度的判断,永远都是以2个字节作为单位的,意思就是,如果一个特殊字符用了4个字节,它返回的字符长度是2,但是实际是一个字符。(JS实际是UCS2方案,跟Unicode 基本平面,即2字节码段一致,UTF-16编码方案在对应基本平面的Unicode字符时是直接对应的,但是对于非基本平面的字符,UTF-16会转码成4个字节来表示,而JS实际无法识别这种转换,所以当遇到这个情况时,它还是老实地按照2个字节2个字节来读,所以说JS根本不支持UTF-16,而非要说它采用了UTF-16也是混淆视听装逼装上瘾的感觉)

3.2.1 字符串直接量
在EC3中,字符串直接量必须写在一行中,在EC5中,字符串直接量可以写在多行中,每行必须以反斜线结束,反斜线和行结束符都不算是字符串直接量的内容。(HTML中习惯用双引号界定字符串,JS嵌入HTML时,JS中最好使用单引号界定字符串)

3.2.2 转义字符
如果把反斜线放在非预定义的转义字符前,那么反斜线将被忽略(比如\# 和 #一样,因为\#是非预定义转义字符,斜线被忽略)。

3.2.3 字符串的使用
加号用于数字,表示两数字相加;用于字符串,表示字符串连接。JS中字符串是固定不变的,类似replace()和toUpperCase()的方法都返回新字符串,原字符串本身并没有发生改变。在EC5中,字符串数组可以当做只读数组,可以用括号语法访问。

3.2.4 模式匹配
RegExp不是语言中的基本数据类型,但具有直接量写法(/^[0-9]/)。

3.3 布尔值
任意JS的值都可以转换为布尔值,如下值会被装换为false:
undefined null 0 -0 NaN “”。 所有其他值,包括所有对象(数组)都会转换成true。

3.4 null和undefinded
空值用null表示,表示已经定义,但值为空,undefined表示没有定义,或定义未初始化,或初始化为undefined值。
3.5 全局对象
在客户端JavaScript中,在其表示的浏览器窗口中的所有JavaScript代码中,Window对象充当了全局对象。这个全局Window对象有一个属性window引用其自身,它可以代替this来引用全局对象。Window对象定义了核心全局属性,单它也针对Web浏览器和客户端JavaScript定义了少部分其它全局属性。
3.6 包装对象
主要有String、Number、Boolean,比如定义了一个字符串变量,当调用其方法时,会调用new String(s)来转换成字符串对象,然后调用其方法,这个就是包装对象。
3.7 不可变的原始值和可变的对象引用
不可变原始值指数字、字符串、布尔、null和undefined类型的值,它们都是不可改变的(字符串修改会返回一个新字符串),这些值的比较是值比较。而对象类型(数组 函数)是引用类型,即使两个完全相同的对象,比较时也是不等的,因为它们的引用不一样。同理,对象类型值赋值也是引用的赋值(引用同一个地址)。****

3.8 类型转换
期望是数字 字符串 布尔值的,会对应转换为数字字符串布尔值:

10 + " object"   => "10 object"
"7" * "4"        => 28
var n = 1 - "x"; => NaN, 字符串"x"装换为数字得到NaN
n + " object"    => "NaN object", n是NaN, 转换为字符串为“NaN”

// 得到NaN, 1x转换为字符串是NaN, 1-NaN还是NaN
// 这里的1x隐形转换并不是转换为1,而是转换成了NaN, 而parseInt("1x")会得到1 ****
var m = 2 - "1x"; 

数字表示的字符串可以直接转换为数字,也允许头尾包含空格,但是只要包含非数字,就都是NaN, 而parseInt()函数显式转换则不然,(123x装换为数字得到123,12×3得到12, x123得到NaN)。原始值到对象的转换调用String()Number()Boolean()构造函数,转换为各自包装对象。undefined转为数字时是NaN,而Null装换为数字时是0。 undefined和null转换为对象时都会抛出异常,因为没有对应的包装对象。 对象一般会转换为字符串,调用toString()方法。

3.8.1 转换和相等性(4.9.1)
3.8.2 显示类型装换
加号(+)在加字符串时,会把数字转换为字符串做链接操作,都是数字时会做相加操作。一元操作符:x+会把x转换为数字,!x会把x装换为布尔值。
3.8.3 对象装换为原始值
toString方法。

3.9 变量声明
如果未在var声明语句中给变量指定初始值,那么虽然声明了这个变量,但在给它存入一个值之前,它的初始值就是undefined。

如果试图读取一个没有声明的变量,JS会报错。在ES5严格模式中,给一个没有声明的变量赋值也会报错。非严格模式下,则会在全局对象创建一个同名属性(在Node.js中会产生内存泄露,一般来说,变量务必先定义后使用,为了能避免变量未声明就可使用,应该让代码运行在严格模式下)

3.10 变量作用域
函数内声明的变量只在函数体内有定义,它们是局部变量。函数参数也是局部变量。在函数体内,局部变量的优先级高于同名的全局变量。

函数定义是可以嵌套的。由于每个函数都有它自己的作用域,因此会出现几个局部作用域嵌套的情况。
3.10.1 函数作用域和声明提前
JavaScript的函数作用域是指在函数内声明的所有变量在函数体内始终是可见的。这意味变量在声明之前甚至已经可用。JavaScript的这个特性被非正式地称为声明提前(hoisting),即JavaScript函数里声明的所有变量(但不涉及赋值)都被提前至函数体的顶部:

var scope = "global";
function f(){
	console.log(scope); 	// undefined 而不是 global
	var scope = "local";
	console.log(scope);	//local
}

第一次输出scope可能会认为应该是global,但是实际输出了undefined,这个就是声明提前起了作用。可以看到,由于声明提前,函数内部相当于是先定义scope,但没有赋值,于是undefined就是scope的值,运行到第二行时才真正赋值。(声明提前是JS引擎的预编译时进行的)
3.10.2 作为属性的变量
当声明一个JS全局变量时,实际是定义了全局对象的一个属性。当使用var声明一个变量时,创建的这个属性是不可配置的(不能delete):

var tv =1;
fv = 2; 
this.fvv = 3;

delete tv; // false;
delete fv; // true
delete this.fvv; // true

当delete一个var声明的全局变量时,实际是无法删除的(不可配置)。全局变量可以通过this来引用。
3.10.3 作用域链
函数可以嵌套,外部定义的变量内部可见,内部定义的和外部同名的变量,优先级最大,这里就形成了一条作用域链。

4 表达式和运算符
4.1 原始表达式
JavaScript中的原始表达式包含常量或直接量、关键字和变量。在EC5的严格模式中个,对不存在的变量进行求值会抛出一个引用错误常量。
4.2 对象和数组的初始化表达式

[]
[1+2,3+4]
[[1,2,3],[4,5,6],[7,8,9]]

var sparseArray = [1,,,,5];
var sparseArray = [1,,,,5,];

数组直接量中的列表逗号之间的元素可以省略,这时省略的空位会填充值undefined。数组直接量的元素列表结尾处可以留下单个逗号,这时并不会创建一个新的值为undefined的元素。

4.3 函数定义表达式
4.4 属性访问表达式
点语法 和 中括号语法。在”.”和”[“之前的表达式总是会首先计算。如果结算结果是null或者undefined,表达式会抛出一个类型错误,因为这两个值都不能包含任意属性。如果命名的属性不存在,那么整个属性访问表达式的值就是undefined。
4.5 调用表达式
函数表达式后跟随一对圆括号,括号内是一个以逗号隔开的参数列表,参数可以有0个也可以多个。如果函数使用return语句给出一个返回值,那么这个返回值就是整个调用表达式的值。否则,调用表达式的值就是undefined。
4.6 对象创建表达式
4.7 运算符概述
4.7.1 操作数的个数
大多是一元和二元运算符,支持一个三元运算符。
4.7.2 操作数类型和结果类型
JavaScript中的所有值不是真值就是假值,因此对于那些希望操作数是布尔值的操作符来说,它们的操作数可以是任意类型。
4.7.3 左值
4.7.4 运算符的副作用
对变量产生影响,比如增加减少等…
4.7.5 运算符优先级
使用圆括号。
4.7.6 运算符的结合性
4.7.7 运算顺序

4.8 算术表达式
4.8.1 “+”运算符

1 + 2  			//3 加法
"1" + "2" 		//"12" 字符串连接
"1" + 2			//"12" 数字转换成字符串后进行字符串连接
1+{}			//"1[object Object]" 对象装换为字符串后进行字符串连接
true + true		//2 布尔值转换为数字后做加法
2 + null		//2 null转换为0后做加法
2 + undefined		//NaN undefined转换为NaN后做加法

1 + 2 + " hello"	//"3 hello" 最边先加法,结果转换成字符串后连接

对于对象,首先是把对象转换为原始值,看这个原始值是字符串还是数字来决定是字符串链接操作还是数字相加操作。

4.8.2 一元算术运算符
4.8.3 位运算符

4.9 关系表达式
4.9.1 相等和不等运算符
严格等===在比较时不会发生类型装换,如下是比较特殊的情况:

null !== null;
undefined !== undefined;
NaN !== NaN; // isNaN()
0 === -1; 

相等运算符“==”比较时,当类型不一致时会进行类型转换:
– null == undefined
– 一个是数字,一个是字符串,先把字符串换成数字,然后比较
– 布尔值会先转换为1 和 0
– 对象则首先转换为原始值

// true首先换成1, 然后符合一个数字一个字符串,则把字符串换成数字1
// 最后是1等于1
"1" == true;

4.9.2 比较运算符
比较操作只能针对数字和字符串,一般认为比较的是数字比较,所以一个字符串和一个数字比较时,先把字符串换成数字再比较(+运算符优先偏向字符串,如果有字符串,则首先转换为字符串; 而比较运算更加偏向数字,只有都是字符串时才是对字符串进行比较)。字符串转换为数字可能是NaN,只要出现NaN,比较结果都是false(NaN !== NaN)

4.9.3 in运算符

var point = {x:1, y:1};
"x" in point 		//true
"z" in point		//false
"toString" in point 	//true 对象有toString方法

var data = [7,8,9];
"0" in data		//true
1 in data		//true
3 in data		//false

4.9.4 instanceof运算符

var d = new Date();
d instanceof Date;	//true
d instanceof Object;	//true
d instanceof Number;	//false

var a = [1,2,3];
a instanceof Array;	//true
a instanceof Object;	//true
a instanceof RegExp;	//false

所有对象都是Object的实例。

4.10 逻辑表达式
4.10.1 逻辑与(&&)
比如a&&b,返回第一个可以转换为false的元素值,否则返回true。
4.10.2 逻辑或(||)
比如a||b,返回第一个可以装换为true的元素值,否则返回false。
4.10.3 逻辑非(!)

4.11 赋值表达式
4.12 表达式计算
4.12.1 eval()
4.12.2 全局eval()
4.12.3 严格eval()

4.13 其他运算符
4.13.1 条件运算符(?:)
4.13.2 typeof运算符

typeof undefined === "undefined";
typeof null === "object"; // null的类型是object是一个很意外的结果,大概原始设置时只有对象可以设置为null
typeof true === "boolean";
typeof 任意数字或NaN === "number";
typeof 任意字符串 === "string";
typeof 函数 === "function";
typeof 任意内置对象(非函数) === “object”;

4.13.3 delete运算符
delete一个对象或数组元素时,可以看做是对应赋值为undefined,实际就表示变量未定义的意思。注:如果是数组,索引不会重排。内置核心和客户端属性,用户通过var语句声明的变量,通过function语句定义的函数和函数参数都不能删除。
4.13.4 void运算符
4.13.5 逗号运算符(,)
(这个章节重点是+号运算和字符串拼接的理解,相等判断,比较运算类型的隐式转化)

5 语句
5.1 表达式语句
5.2 复合语句和空语句

{
	x = Math.PI;
	cx = Math.cos(x);
	console.log(cx);
}

//空语句,一个逗号一行
;

语句块的结尾不需要分号。块中的原始语句必须以分号结束。

5.3 声明语句
5.3.1 var
5.3.2 function

5.4 条件语句
5.4.1 if
5.4.2 else if
5.4.3 switch

5.5 循环
5.5.1 while
5.5.2 do/while
5.5.3 for
5.5.4 for/in

5.6 跳转
5.6.1 标签语句
5.6.2 break语句
5.6.3 continue语句
5.6.4 return语句
5.6.5 throw语句
5.6.6 try/catch/finally语句

5.7 其他语句类型
5.7.1 with语句(p1113)
5.7.2 debugger语句
5.7.3 “use strict”
“use strict”是ECMAScript 5引入的一条指令。使用”use strict”指令的目的是说明后续的代码将会解析为严格代码(strict code)。如果顶层(不在任何函数内的)代码使用”use strict”指令,那么它们就是严格代码。如果函数体定义所处的代码是严格代码或者函数体使用”use strict”指令,那么函数体的代码也是严格代码。

6 对象
属性名是字符串,因此可以把对象看成是从字符串到值的映射。JavaScript对象还可以从一个称为原型的对象继承属性。对象的方法通常是继承的属性。这种原型式继承是JavaScript的核心特征。

除了字符串、数字、true、false、null和undefined之外,JavaScript中的值都是对象。尽管字符串、数字和布尔值不是对象,但它们的行为和不可变对象非常类似。

除了包含属性之外,每个对象还拥有三个相关的对象特性:
>对象的原型(prototype)指向另一个对象,本对象的属性继承自它的原型对象
>对象的类是一个标识对象类型的字符串
>对象的扩展标记指明了是否可以向该对象添加新属性

三类JavaScript对象和两类属性:
>内置对象(native object)是由ECMAScript规范定义的对象或类(TMD,到底对象还是类)。如数组、函数、日期和正则表达式都是内置对象。
>宿主对象(host object)是有JavaScript解释器所嵌入的宿主环境(比如Web浏览器)定义的。
>自定义对象是由运行中的JavaScript代码创建的对象(到底是指类型定义还是实例,简直操蛋)
>自有属性是直接在对象中定义的属性(这个时候的对象就是指类)
>继承属性是在对象的原型对象中定义的属性。

6.1 创建对象
可以通过对象直接量、关键字new和Object.create()函数来创建对象。
6.1.1 对象直接量
花括号语法。
6.1.2 通过new创建对象
JavaScript语言核心中的原始类型都包括内置构造函数。如:

var o = new Object();	//和{}一样
var a = new Array();	//和[]一样
var d = new Date();
var r = new RegExp();

6.1.3 原型
每个JavaScript对象(null除外)都和另一个对象相关联。这个对象就是原型,每一个对象都从原型继承属性。

所有通过对象直接量创建的对象都具有同一个原型对象,并可以通过JavaScript代码Object.prototype获得对原型对象的引用。通过关键字new和构造函数调用创建的对象的原型就是构造函数的prototype属性的值。同使用{}创建对象一样,通过new Object()创建的对象也继承自Object.prototype。同样,通过new Array()创建的对象的原型就是Array.prototype,通过new Date()创建的对象的原型就是Date.prototype。(对象实例有原型的概念,对象的类有一个叫prototype的属性,原型是一个实例,不是类,对象实例生成时,继承这个实例,实际非常诡异…)

6.1.4 Object.create()
Object.create()是一个静态函数,第一个参数是要创建的对象的原型。

var o = Object.create({x:1,y:2});

可以传入参数null来创建一个没有原型的新对象。

6.2 属性的查询和设置
6.2.1 作为关联数组的对象
点与方括号用法老调重弹。
6.2.2 继承

var o = {}; 	//o从Object.prototype继承对象和方法
o.x = 1;	//给o定义一个属性x
var p = Object.create(o); //p继承o和Object.prototype
p.y = 2;	//给p定义一个属性y
var q = Object.create(p); //q继承p、o和Object.prototype
q.z = 3;	//给q定义一个属性z
var s = q.toString(); //toString继承自Object.prototype
q.x + q.y	//3,x和y分别继承自o和p

js_prototype

var s = q.toString() 首先在q内找,再到q的原型p中找,再到p的原型o中找,再到o的原型Object.prototype中找,找到toString()方法。

q.x + q.y,在q中寻找x和y,再到q的原型p中找到y,继续到p的原型o中找到x。

var o = {}; 	
o.x = 1;	
var p = Object.create(o); 
p.y = 2;	
var q = Object.create(p); 
q.z = 3;	
var s = q.toString();

o.x = 10;
p.x = 100;

o.xy = 1000;

console.log(q.x); 	// 100
console.log(q.y);	// 2
console.log(q.z);	// 3
console.log(q.xy);	// 1000

对象o是在最后添加了xy属性,但是q.xy输出了o的xy属性值;x被修改了两次,输出了最近的值。这个不难理解,因为这些修改的都是原型,这个对于q来说,可以动态扩充,这个就是JavaScrip核心特征。

6.2.3 属性访问错误
访问不存在的属性不会出错(返回undefined),但是如果访问一个不存在的对象则会出错。

6.3 删除属性(方法的定义也是对象的属性)
delete运算符只能删除自有属性,不能删除继承属性(要删除继承属性必须从定义这个属性的原型对象上删除它,而且这会影响到所有继承自这个原型的对象)…

6.4 检查属性
in运算符的左侧是属性名(字符串),右侧是对象。如果对象的自有属性或继承属性中包含这个属性则返回true。

对象的hasOwnProperty()方法用来检测给定的名字是否是对象的自有属性。

propertyIsEnumerable()是hasOwnProperty()的增强版,只有检测到自有属性且这个属性的可枚举性为true时它才返回true(首先是自有,然后是可枚举的,可枚举啥概念??非方法?)。

6.5 枚举属性
for/in循环可以在循环中遍历对象中所有可枚举的属性(包括自有属性和继承的属性),把属性名称赋值给循环变量。对象继承的内置方法不可枚举,但在代码中给对象添加的属性都是可枚举的。

有许多工具库给Object.prototype添加了新的方法或属性,这些方法和属性可以被所有对象继承并使用。然而在EC5标准之前,这些新添加的方法是不能定义为不可枚举的,因此都可以在for/in循环中枚举出来。需要过滤:

for(p in o){
	if(!o.hasOwnProperty(p)) continue; //跳过继承的属性
}

for(p in o){
	if(type o[p] === "function") continue; // 跳过方法
}

EC5定义了两个用以枚举属性名称的函数。第一个是Object.keys(),反返回一个数组,这个数组由对象中可枚举的自有属性的名称组成,第二枚举属性的函数是Object.getOwnPropertyNames(),它返回对象的所有自有属性的名称,而不仅仅是可枚举的属性。

6.6 属性getter和setter
p132
6.7 属性的特性
除了包含名字和值之外,属性还还包括一些标识它们可写,可枚举和可配置的特性。
p134

6.8 对象的三个属性
每一个对象都有与之相关的原型(prototype)、类(class)和可扩展性(extensible attribute)。
6.8.1 原型属性
在EC5中,将对象作为参数传入Object.getPrototypeOf()可以查询它的原型。要想检测一个对象是否是另一个对象的原型(或处于原型链中),请使用isPrototypeOf()方法。如:p.isPrototypeOf(o)来检测p是否是o的原型。

Mozilla实现的JavaScript对外暴露了一个专门命名的__proto__属性,用以直接查询/设置对象的原型。

6.8.2 类属性
创建对象的类型。p139
6.8.3 可扩展性

6.9 序列化对象
6.10 对象的方法

7 数组
数组继承自Array.prototype中的属性,它定义了一套丰富的数组操作方法。
7.1 创建数组
如果省略数组直接量中的某个值,省略的元素将被赋予undefined值:

var count = [1,,3];
var undefs = [,,];

数组直接量的语法允许有可选的结尾的逗号,所以[,,]只有两个元素而非三个。

var a = new Array();
var a = new Array(10);

注:数组中没有存储值,甚至数组的索引属性等还未定义。

7.2 数组元素的读和写
数组是对象的特殊形式。JavaScript将指定的数字索引值转换成字符串–索引值1变成”1″–然后将其作为属性名来使用。

数组的特别之处在于,当使用小于2的32次方的非负数作为属性名时数组会自动维护其length属性值。(2的32次方的值是数组的索引,其它的也可以作为数组索引,但是不体现到length属性值,跟一般的对象属性没有什么不一样)

事实上数组索引仅仅是对象属性名的一种特殊类型,这意味着JavaScript数组没有越界错误的概念。当试图查询任何对象中不存在的属性时,不会报错,只会得到undefined值。

7.3 稀疏数组
如果数组是稀疏的,length属性值大于元素的个数(有length个元素,但是有些是根本不存在的,这跟值为undefined是有却别的)。

a = new Array(5); 	//数组没有元素,但a.length是5
a = []			//空数组,length=0
a[100] = 0;		//赋值添加一个元素,但是设置length为1001

7.4 数组长度
数组的两个特殊行为:
1) 如果为一个数组元素赋值,它的索引i大于或等于现有数组的长度时,length属性的值将设置为i+1。
2) 设置length属性为一个小于当前长度非负整数n时,当前数组中那些索引值大于或等于n的元素将从中删除。

7.5 数组元素的添加与删除
添加:直接赋值或调用push()。也可用unshift()在首部插入。可以像删除对象属性一样使用delete运算符来删除数组元素。

删除数组元素与为其赋undefined值是类似的。注:对一个数组元素使用delete不会修改数组的length属性,也不会将元素从高索引处一下来填充已删除属性留下的空白。如果从数组中删除一个元素,它就变成稀疏数组。(如果期望删除了元素索引从新排列,需要使用splice()方法)。

7.6 数组遍历
for循环,假设数组是非稀疏的,如果要排除null,undefined和不存在的元素:

//排除null,undefined和不存在的元素
for(var i = 0; i < a.length; i++) {
	if(!a[i]) continue;
}

//排除undefined
for(var i = 0; i < a.length; i++) {
	if(a[i] === undefined) continue;
}

//排除不存在
for(var i = 0; i < a.length; i++) {
	if(!(i in a)) continue;
}

可以使用for/in循环处理稀疏数组。循环每次将一个可枚举的属性名赋值给循环变量。不存在的索引将不会遍历到。for/in循环能够枚举继承的属性名,如果添加到Array.prototype中的方法。由于这个原因,在数组上不应该使用for/in循环,除非使用额外的检测方法来过滤不需要的属性:

for(var i in a) {
	if(!a.hasOwnProperty(i)) continue; //跳过继承而来的属性
}

如果算法依赖于遍历的顺序,最好不要使用for/in。

EC5定义了一些遍历数组元素的新方法,按照索引的顺序按个传递给定义的一个函数,这些方法中最常用的就是forEach()方法:

var data = [1,2,3,4,5];
var result = 0;
data.forEach(function(x){
	result + = x*x;
});
console.log(result);

7.7 多维数组
7.8 数组方法
EC3在Array.prototype中定义的数组函数。
7.8.1 join()
数组变成字符串。

//数组拆分成字符串
var a = [1,2,3];
a.join(); 	// "1,2,3"

a.join("-");	// "1-2-3"

//字符串组装成数组
var s = "1-2-3";
s.split("-"); 	//[1,2,3]

7.8.2 reverse()
这个方法是在原来数组中使用替换方法进行翻转。
7.8.3 sort()
按照字母表顺序返回排序后的数组。如果数组包含undefined元素,它们会被排到数组的尾部。

如果希望以非字母顺序进行数组排序,必须给sort()方法传递一个比较函数….
7.8.4 concat()
7.8.5 slice()
返回数组片段。
7.8.6 splice()
Array.splice()方法是在数组中插入或删除元素的通用方法。不同于slice()和concat(),splice()会修改调用的数组。

splice()第一个参数指定插入和(或)删除的起始位置,第二个参数指定应该从数组中删除的元素的个数。如果省略第二个参数,从起始点开始到数组末尾的所有元素都将被删除。splice()返回一个有删除元素组成的数组,或者如果没有删除元素就返回一个空数组。

var a = [1,2,3,4,5,6,7,8];
a.splice(4); 	//返回[5,6,7,8],a是[1,2,3,4]
a.splice(1,2);	//返回[2,3],a是[1,4]
a.splice(1,1);	//返回[4],a是[1]

splice()的前两个参数指定了需要删除的数组元素。紧跟其后的任意个参数指定了需要插入到数组中的元素(从指定位置前插入,删除是从指定位置(包含)开始删除):

var a = [1,2,3,4,5];
a.splice(2,0,'a','b'); 	//返回[],a是[1,2,'a','b',3,4,5]
a.splice(2,2,[1,2],3);	//返回['a','b'],a是[1,2,[1,3],3,3,4,5]

注意:[1,2]是作为整体插入的。

7.8.7 push()和pop()
7.8.8 unshift()和shift()
7.8.9 toString()和toLocalString()

7.9 ECMAScript 5中的数组方法
forEach() map() filter() every() some() reduce() reduceRight() indexOf() lastIndexOf()
7.10 数组类型
EC5中使用Array.isArray()来判断是否是数组。

Array.isArray([]) // true
Array.isArray({}) // false

7.11 类数组对象
7.12 作为数组的字符串

8 函数
除了实参之外,每次调用还会拥有另一个值–本次调用的上下文–this关键字的值。如果函数挂载在一个对象上,作为对象的一个属性,就称它为对象的方法。当通过这个对象来调用函数时,该对象就是此次调用的上下文,也就是该函数的this的值。用于初始化一个新创建的对象的函数称为构造函数。

在JavaScript里,函数即对象,程序可以随意操控它们。比如,JavaScript可以把函数赋值给变量,或者作为参数传递给其他函数。因为函数就是对象,所以可以给它们设置属性,调用它们的方法。

8.1 函数定义
如果return语句没有一个与之相关的表达式,则它返回undefined值。如果一个函数不包含return语句,那它就只执行函数体中的每条语句,并返回undefined值给调用者。
8.2 函数调用
8.2.1 函数调用
EC3和EC5对函数调用的规定,调用上下文是全局对象。在严格模式下,调用上下文是undefined。

以函数形式调用的函数通常不使用this关键字。不过,”this”可以用来判断当前是否是严格模式:

var strict = (function(){ return !this; }());

8.2.2 方法调用
和变量不同,关键字this没有作用越的限制,嵌套的函数不会从调用它的函数中继承this(内部不指向外部,它有自己的this)。如果嵌套函数作为方法调用,其this的值指向调用它的对象。如果嵌套函数作为函数调用,其this值不是全局对象就是undefined。不要误以为调用嵌套函数时this会指向调用外层函数的上下文。如果需要访问这个外部函数的this值,需要将this的值保持在一个变量中,这个变量和内部函数都在同一个作用越中。通常使用变量self来保存this。
8.2.3 构造函数调用
使用new调用。构造函数通常不使用return关键字,它们通常初始化新对象,当构造函数的函数体执行完毕时,它会显式返回。在这个情况下,构造函数调用表达式的计算结果就是这个新对象的值。然而如果构造函数显式地使用return语句返回一个对象,那么调用表达式的值就是这个对象。如果构造函数使用return语句但没有指定返回值,或者返回一个原始值,那么这时将忽略返回值,同时使用这个新对象作为调用结果。

8.2.4 间接调用
函数对象也包含方法。其中的两个方法call()和apply()可以用来间接地调用函数。两个方法都运行显式指定调用所需要的this值,也就是说,任何函数可以作为任何对象的方法来调用。

8.3 函数的实参和形参
实际,JavaScript函数调用时不检查传入形参的个数。
8.3.1 可选形参
当调用函数的时候传入的实参比函数声明时指定的形参个数要少,剩下的形参都将设置为undefined值。(无法在参数中放置默认值)

function getPropertyNames(o,/* optional */ a) {
	if(a === undefined) a = []; //如果未定义
	for(var property in o) a.push(property);
	return a;
}

a = a || [];  //最常见方式

8.3.2 可变长的实参列表:实参对象
在函数体内,标识符arguments是指向实参对象的引用,实参对象是一个类数组对象。
8.3.3 将对象属性用做实参
参数顺序无关。传入一个对象,这个方式很常见。

$.ajax({
	url:'',
	method:''
});

8.3.4 实参类型
不检查类型。需要自己实现。

8.4 作为值的函数
p178
8.5 作为命名空间的函数
就是匿名函数。
8.6 闭包
函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为闭包。
8.7 函数属性、方法和构造函数
8.7.1 length属性
在函数体中,arguments.length表示传入函数的实参的个数。而函数本身的length属性表示形参的个数,就是定义时候的参数个数。

function test(x,y,z){}
console.log(test.length); // 输出3

由于直接函数调用,内部this指向全局对象(window)。可以通过函数内部的arguments.callee来获取当前运行的函数的引用。
8.7.2 prototype属性
8.7.3 call()方法和apply()方法
可以将call()和apply()方法看做是某个对象的方法,通过调用方法的形式来间接调用函数。

以对象o的方法来调用函数f():

f.call(o)
f.apply(o)

对应call(),第一个上下文对象后的参数就是函数的实参;对apply来说就是一个实参数组。
8.7.4 bind()方法
在对象上绑定一个方法。
8.7.5 toString()方法
8.7.6 Function()构造函数
Function()构造函数创建的函数并不使用词法的作用域。可以认为是在全局作用域中执行eval()。(很少使用)
8.7.7 可调用的对象

8.8 函数式编程

9 类和模块
如果两个对象继承自同一个原型,往往意味着它们是由同一个构造函数创建并初始化的。JavaScript中类的一个重要特征是动态可继承,可以将类看做是类型。
9.1 类和原型
在JavaScript中,类的所有实例对象都从同一个原型对象上继承属性。
9.2 类和构造函数
使用new调用构造函数会自动创建一个新对象,因此构造函数本身只需要初始化这个新对象的状态即可。调用构造函数的一个重要特征是,构造函数的prototype属性被用做新对象的原型。

定义构造函数即是定义类,并且类名首字母要大写,而普通的函数和方法都是首字母小写。

function Range(from, to) {
	this.from = from;
	this.to = to;	
}

Range.prototype = {
	includes: function(x) { return this.from <= x && x <= this.to; },
	foreach: function(f) {
		for(var x = Math.ceil(this.from); x <= this.to; x++) f(x);
	},
	toString: function() { return "("+this.from + "..."+this.to+")"; }
};

var r = new Range(1,3);
console.log(r.includes(2));

构造函数调用和普通函数调用时不尽相同的。构造函数就是用来构造新对象的,它必须通过关键字new调用,如果将构造函数用做普通函数的话,往往不会正常工作。

9.2.1 构造函数和类的标识
当使用instanceof运算符来检测对象是否属于某个类时会用到构造函数。假设这里有一个r对象,想知道r是否是Range对象,这样写:r instanceof Range,实际上instanceof运算符并不会检查r是否是由Range()构造函数初始化而来,而会检查r是否继承自Range.prototype。不过,instanceof的语法则强化了构造函数是类的共有标识的概念。
9.2.2 constructor属性
每个JavaScript函数都自动拥有一个prototype属性。这个属性的值是一个对象。这个对象包含唯一一个不可枚举属性constructor。constructor属性的值是一个函数对象,指向函数本身。(注意看,函数原始的prototype对象,它有一个constructor属性,它执行函数体本身):

function Range(from, to) {
	this.from = from;
	this.to = to;	
}
console.log(Range.prototype.constructor === Range); 	//true
var f = new Range(1,3);
console.log(f.constructor === Range);			//true

由于f对象是继承Range.prototype,自然r就有一个叫contructor的属性。但是:

function Range(from, to) {
	this.from = from;
	this.to = to;	
}
Range.prototype = {};
console.log(Range.prototype.constructor === Range); 	//false
var f = new Range(1,3);
console.log(f.constructor === Range);			//false

Range.prototype被覆盖,自然什么就没有了,所以在自定义的时候,为了兼容性,一般需要带上constructor:

Range.prototype = {
	constructor:Range,
	inc:function(){}
};

9.3 JavaScript中Java式的类继承
9.4 类的扩充
9.5 类和类型
9.5.1 instanceof运算符
尽管instanceof运算符的有操作数是构造函数,但计算过程实际上是检测了对象的继承关系,而不是检测创建对象的构造函数(检测prototype,一直往上,如果对象间接继承,也返回true)
9.5.2 constructor属性
9.5.3 构造函数的名称
注意instanceof 和 constructor比较的都是一个当前生成的对象,在多上下文中,这个对象是不一样的,所以会存在问题。实际,如果比较构造函数的名称,就不存在这个问题。

可以调用对象的toString方法转换成字符串,然后提取名称(来自constructor属性),不是所有对象都有constructor属性,也不是多少对象都有名字。
9.5.4 鸭式辩型

P217开始…….
9.6 JavaScript中的面向对象技术
9.7 子类
9.8 ECMAScript 5 中的类
9.9 模块

Nginx php-fpm日志记录 与 配置参考

Nginx的日志配置:

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

可以针对具体的域做具体配置。

PHP的php.ini中针对日志配置:

//错误报告级别
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
//是否显示错误
display_errors = On
//是否记录日志
log_errors = On
//日志记录到哪里
error_log = php_errors.log

在PHP php-fpm.conf关于日志的配置:

;;;;;;;;;;;;;;;;;;
; Global Options ;
;;;;;;;;;;;;;;;;;;
[global]
error_log = /var/log/php-fpm/error.log

; Log level
; Possible Values: alert, error, warning, notice, debug
; Default Value: notice
;log_level = notice

这个是针对php-fpm进行的日志配置,那么,它自然只应该记录和php-fpm相关的日志(cat /var/log/php-fpm/error.log):

[02-Aug-2015 21:40:54] WARNING: [pool www] child 4062, script '/data/web/nginx/timeout.php' (request: "GET /timeout.php") executing too slow (2.565468 sec), logging
[02-Aug-2015 21:40:54] NOTICE: child 4062 stopped for tracing
[02-Aug-2015 21:40:54] NOTICE: about to trace 4062
[02-Aug-2015 21:40:54] NOTICE: finished trace of 4062
[02-Aug-2015 22:15:29] NOTICE: Terminating ...
[02-Aug-2015 22:15:29] NOTICE: exiting, bye-bye!
[02-Aug-2015 22:15:30] NOTICE: fpm is running, pid 15707
[02-Aug-2015 22:15:30] NOTICE: ready to handle connections
[02-Aug-2015 22:45:56] NOTICE: Terminating ...
[02-Aug-2015 22:45:56] NOTICE: exiting, bye-bye!
[02-Aug-2015 22:50:41] NOTICE: fpm is running, pid 1243
[02-Aug-2015 22:50:41] NOTICE: ready to handle connections

就记录了进程启用终止等之类的信息,fpm有问题时,这个日志是很有用的信息。

如果PHP作为Apache的模块运行,当PHP脚本发生错误时,如果在PHP的php.ini中配置了log_error,那么错误就记录到这个指定文件,否则错误会上交到Apache来处理。这个过程一直被认为当PHP以fpm运行时是不正确的。不过我这里拿PHP 5.5以FPM运行时,它的过程跟PHP作为Apache的模块运行时的过程是一致的。这个应该是早期的PHP版本FPM运行时,没有向上报告导致的。比如当我去掉log_error配置项(php.ini和php-fpm.conf中都去掉):

tail -f /usr/local/nginx/logs/error.log
2015/08/02 23:30:11 [error] 1254#0: *12 FastCGI sent in stderr: "PHP message: PHP Parse error:  syntax error, unexpected 'echo' (T_ECHO) in /data/web/nginx/err.php on line 4" while reading response header from upstream, client: 192.168.1.100, server: nginx.ifeeline.com, request: "GET /err.php HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000", host: "nginx.ifeeline.com"

这里Nginx成功记录了PHP的语法错误。而当我恢复php.ini中的log_error配置时,错误就记录到了配置指定的文件,Nginx也没有重复记录这个错误。

PHP-FPM中,还可以配置request_slowlog,要找出执行时间超标的脚本,这个设置比较有用,以下是PHP-FPM配置参考:

#################################
#全局配置
#################################

#包含文件,一般,池的配置可以放入单独的文件
#比如www.conf,表示www池
include=/etc/php-fpm.d/*.conf

#进程ID号存放的文件,默认为none
pid = /var/run/php-fpm/php-fpm.pid

#错误日志,可以设置为syslog,让其把日志发送到syslog
#默认值/var/log/php-fpm.log
error_log = /var/log/php-fpm/error.log
;syslog.facility = daemon
;syslog.ident = php-fpm

#日志级别,可用值alert, error, warning, notice, debug
#默认值为notice
log_level = notice

#紧急重启阀值和区间,在循环的区间(emergency_restart_interval)
#如果子进程退出的次数等于emergency_restart_threshold的设置
#FPM优雅重启,默认值都为0
;emergency_restart_threshold = 0
;emergency_restart_interval = 0

#子进程等待一个来自主进程的可重用信号的限制时间
#默认为0,默认单位为s(秒)
;process_control_timeout = 0

#FPM将可fork的最大值。当在大量的池中使用动态PM时,这个值被设置来控制全局的进程数量
;process.max = 128
;process.priority = -19

#后台或前提运行
daemonize = yes

#设置主进程可打开的文件描述符数量. 默认值: 系统定义值默认可打开句柄是1024,可使用 ulimit -n查看,ulimit -n 2048修改。
;rlimit_files = 1024

#设置主进程最大的核心数
;rlimit_core = 0

#指定FPM将使用的事件机制
;events.mechanism = epoll

#当FPM和systemd,指定一个时间区间,用来向systemd报告健康通知
;systemd_interval = 10

#################################
#以下是针对池的配置
#################################

#FPM监听端口,即nginx中php处理的地址。可用格式为: 'ip:port', 'port', '/path/to/unix/socket'. 每个进程池都需要设置。
listen = 127.0.0.1:9000

#backlog数,-1表示无限制,由操作系统决定
;listen.backlog = -1

#允许访问FastCGI进程的IP,设置any为不限制IP,如果要设置其他主机的nginx也能访问这台FPM进程,listen处要设置成本地可被访问的IP。默认值是any。每个地址是用逗号分隔. 如果没有设置或者为空,则允许任何服务器请求连接
listen.allowed_clients = 127.0.0.1

#unix socket设置选项,如果使用tcp方式访问,可忽略。
;listen.owner = www
;listen.group = www
;listen.mode = 0660

#启动进程的帐户和组
user = www
group = www

#如何控制子进程,选项有static和dynamic。
pm = dynamic
#如果选择static,则由pm.max_children指定固定的子进程数。如果选择dynamic,则由下开参数决定:
pm.max_children 	#,子进程最大数
pm.start_servers 	#,启动时的进程数
pm.min_spare_servers 	#,保证空闲进程数最小值,如果空闲进程小于此值,则创建新的子进程
pm.max_spare_servers 	#,保证空闲进程数最大值,如果空闲进程大于此值,此进行清理

#设置每个子进程重生之前服务的请求数(服务这么多的请求自杀重启). 对于可能存在内存泄漏的第三方模块来说是非常有用的
pm.max_requests = 500

#FPM状态页面的网址. 如果没有设置, 则无法访问状态页面。默认值为没有设置。
;pm.status_path = /status

#FPM监控页面的ping网址. 如果没有设置, 则无法访问ping页面. 该页面用于外部检测FPM是否存活并且可以响应请求. 请注意必须以斜线开头 (/)。
ping.path = /ping

#用于定义ping请求的返回相应. 返回为 HTTP 200 的 text/plain 格式文本. 默认值: pong.
;ping.response = pong

#设置单个请求的超时中止时间
request_terminate_timeout = 0

#当一个请求到达设置的超时时间后,就会将对应的PHP调用堆栈信息完整写入到慢日志中. 设置为 '0' 表示 'Off'
request_slowlog_timeout = 10s

#慢请求的记录日志,配合request_slowlog_timeout使用
slowlog = log/$pool.log.slow

;rlimit_files = 1024
;rlimit_core = 0

#启动时的Chroot目录. 所定义的目录需要是绝对路径. 如果没有设置, 则chroot不被使用.
chroot =

#设置启动目录,启动时会自动Chdir到该目录. 所定义的目录需要是绝对路径. 默认值: 当前目录,或者/目录(chroot时)
chdir =

#重定向运行过程中的stdout和stderr到主要的错误日志文件中. 如果没有设置, stdout 和 stderr 将会根据FastCGI的规则被重定向到 /dev/null . 默认值: 空.
catch_workers_output = yes

#限制后缀,默认为php
;security.limit_extensions = .php .php3 .php4 .php5

#设置环境变量
;env[HOSTNAME] = $HOSTNAME
;env[PATH] = /usr/local/bin:/usr/bin:/bin
;env[TMP] = /tmp
;env[TMPDIR] = /tmp
;env[TEMP] = /tmp

#修改PHP配置
;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f www@my.domain.com
;php_flag[display_errors] = off
;php_admin_value[error_log] = /var/log/php-fpm/www-error.log
php_admin_flag[log_errors] = on
;php_admin_value[memory_limit] = 128M

; Set session path to a directory owned by process user
php_value[session.save_handler] = files
php_value[session.save_path] = /var/lib/php/session