笔记来自《JavaScript权威指南(第六版)》
包含的内容:
- 表达式和运算符
- 语句
- 对象
表达式和运算符
数组直接量中的列表逗号之间的元素可以省略,这时省略的空位会填充值undefined。元素列表末尾可以留下单个逗号,这时并不会创建一个新的值为undefined元素。
属性访问表达式,.identifier的写法只适用于要访问的属性名称是合法的标识符,并且需要知道要访问的属性的名字。如果属性名称是一个保留字或者包含空格和标识符,或是一个数字(对于数组来说),则必须使用方括号的写法。当属性名是通过运算得出的值而不是固定的值的时候,这时必须使用方括号写法。
如果一个对象创建表达式不需要传入任何参数给构造函数的话,那么这对空圆括号是可以省略掉的。new Object
运算符:
delete 删除属性
typeof 检测操作数类型
void 返回undefined值
instanceof 测试对象类
in 测试属性是否存在
求余运算符的操作数通常都是整数,但也适用于浮点数,比如,6.5%2.1结果是0.2。
加号的转换规则优先考虑字符串连接,如果其中一个操作数是字符串或者转换为字符串的对象,另外一个操作数将会转换为字符串,加法将进行字符串的连接操作。
从技术上讲,加法操作符的行为表现为:
1、 如果其中一个操作数是对象,则对象会遵循对象到原始值的转换规则转换为原始类值:日期对象通过toString()方法执行转换,其他对象则通过valueOf()方法执行转换(如果valueOf()方法返回一个原始值的话)。由于多数对象都不具备可用的valueOf()方法,因此它们会通过toString()方法来执行转换。
2、 在进行了对象到原始值的转换后,如果其中一个操作数是字符串的话,另一个操作数也会转换为字符串,然后进行字符串连接。
3、 否则,两个操作数都将转换为数字(或者NaN),然后进行加法操作。
【图】加法操作符例子
【图】前增量与后增量
表达式++x并不总和x = x + 1完全一样,“++”运算符从不进行字符串连接操作,它总是会将操作数转换为数字并增1。如果x是字符串“1”,++x的结果就是数字2,而x+1是字符串“11”。
位运算符要求它的操作数是整数,这些整数表示为32位整型而不是64位浮点型。必要时,位运算符首先将操作数转换为数字,并将数字强制表示为32位整型,这会忽略原格式中的小数部分和任何超过32位的二进制位。移位运算符要求右操作数在0~31之间。在将其操作数转换为无符号32位整数后,它们将舍弃第5位之后的二进制位,以便生成一个位数正确的数字。需要注意的是,位运算符会将NaN、Infinity和-Infinity都转换为0。
按位异或(^):有且只有一个为1,结果才为1。
按位非(~):将操作数的所有位取反,相当于改变它的符号并减1。
例如,~0x0F = 0Xfffffffo 或 -16
左移(<<):移动的位数是0~31之间的一个整数,新的位用0补。左移1位相当于它乘以2,例如,7 << 2 = 28
带符号右移(>>):移动的位数是0~31之间的一个整数,右边溢出的位将忽略,填补在左边的位由原操作数的符号决定,以便保持结果的符号与原操作数一致。如果第一个操作数是正数,移位后用0填补最高位,如果第一个操作数是负的,移位后用1填补高位。将一个值右移1位,相当于用它除于2(忽略余数),例如, 7 >> 1 = 3, -7 >> 1 = -4
无符号右移(>>>):左边的最高位总是填补0,与原来的操作数符号无关,例如,-1 >>> 4 = 0x0FFFFFFF。
如果两个值都是null或者都是undefined,则它们不相等。
如果其中一个值是NaN,或者两个值都是NaN,则它们不相等。NaN和其他任何值都是不相等的,包括它本身!通过x !== x来判断x是否是NaN,只有在x为NaN的时候,这个表达式的值才为true。
检测相等(==)将会遵守如下规则和类型转换:
1、 如果一个值是null,另一个是undefined,则它们相等。
2、 如果一个值是数字,另一个是字符串,先将字符串转换为数字,然后使用转换后的值进行比较。
3、 如果其中一个值是true,则将其转换为1在进行比较。如果其中一个值是false,则将其转换为0再进行比较。
4、 如果一个值是对对象,另一个值是数字或字符串,则先将对象转换为原始值,然后再进行比较。
5、 其他不同类型之间的比较均不相等。
字符串的比较是区分大小写的,大写的ASCII字母都“小于”小写的ASCII字符。
比较运行符更偏爱数字,只有在两个操作数都是字符串的时候,才会进行字符串的比较:
【图】比较运行符例子
【图】in运算符
【图】instanceof运算符
和其他很多解释性语言一样,JavaScript同样可以解释运行由JavaScript源代码组成的字符串,并产生一个值。JavaScript通过全局函数eval()来完成这个工作:
eval(“3 + 2”) // => 5
动态判断源代码中的字符串是一种强大的语言特性,几乎没有必要在实际中应用。如果你使用了eval(),你应当仔细考虑是否真的需要使用它。
“?:”典型应用场景,判断一个变量是否定义(并拥有一个有意义的真值),如果有定义则使用它,如果无定义则使用一个默认值。
greeting = “Hello ” + (username ? username: “there”);
【图】任何值在typeof运算后的返回值
delete是一元操作符,它用来删除对象属性或数组元素。删除数组中的元素,数组的长度不会改变。
在严格模式中,如果delete的操作数是非法的,比如变量、函数或函数参数,delete操作将抛出一个语法错误异常。删除不可配置的属性时会抛出一个类型错误异常。在非严格模式下,这些delete操作都不会报错,只是简单的返回false,以表明操作数不能执行删除操作。
【图】delete运行符例子
void是一元运算符,它出现在操作数之前,操作数可以是任意类型。这个运算符并不是经常使用:操作数会照常计算,但忽略计算结果并返回undefined。
逗号(,)运算符是二元运算符,它的操作数可以是任意类型。它首先计算做操作数,然后计算有操作数,最后返回有操作数的值。
i = 0, j = 1, k = 2; // => 计算结果是2
等价于:i = 0; j = 1; k = 2;
语句
一个JavaScript程序无非是一个以分号分隔的语句集合。
可以将多条语句联合在一起,形成一条复合语句。只须用花括号将多条语句括起来即可。
语句块注意点:结尾不需要分号;块中的行最好都有缩进;块中声明的变量并不是语句块私有的。
全局变量是全局对象的属性,但是var声明的变量是无法通过delete删除的。
如果var没有指定初始化表达式,那么这个变量的值初始为undefined。
多次声明同一个变量是无所谓的。
脚本中的所有函数和函数中所有嵌套的函数都会在当前上下文中其他代码之前声明,也就是说,可以在声明一个JavaScript函数之前调用它。
每个case关键字跟随任意的表达式。case的匹配操作实际上是“===”的比较。
不管continue语句带不带标签,它只能在循环体内使用。在其他地方使用将会报语法错误。
catch子句中标识符具有块级作用域,它只在catch语句块内有定义。
如果finally从句抛出一个异常,这个异常将替代正在抛出的异常。如果finally从句运行到了return语句,尽管已经抛出了异常且这个抛出异常还没有处理,这个方法依然会正常返回。
尽量避免使用with语句,使用with的语句难于优化,而且运行更慢。
只有在查找标识符的时候才会用到作用域链,创建新的变量的时候不使用它。
dubugger语句用来在调试模式运行的时候产生一个断点。但是它不会启动调试器。
“use strict”只能出现在脚本代码的开始或者函数体的开始、任何实体语句之前。目的是说明(脚本或函数中)后续代码将会解析为严格代码。
【图】严格模式和非严格模式的区别
对象
对象是动态的,可以新增属性也可以删除属性。
属性包括名字和值。属性名可以是包含空字符串在内的任意字符串,但对象中不能存在两个同名的属性。
属性特性:
1、 可写,表明是否可以设置该属性的值。
2、 可枚举,表明是否可以通过for/in循环返回该属性。
3、 可配置,表明是否可以删除或修改该属性。
每个对象拥有三个相关的对象特性:
1、 对象原型(prototype)指向另外一个对象,本对象的属性继承自它的原型对象。
2、 对象的类(class)是一个标识对象类型的字符串。
3、 对象的扩展标记指明了是否可以向该对象添加新属性。
三类对象和两类属性:
【内置对象】是由ECMAScript规范定义的对象或类。例如,数组、函数、日期和正则表达式都是内置对象。
【宿主对象】是由解释器所嵌入的宿主环境(比如Web浏览器)定义的。客户端中表示网页结构的HTMLElement对象均是宿主对象。既然宿主环境定义的方法可以当成普通函数对象,那么宿主对象也可以当成内置对象。
【自定义对象】是由运行中的代码创建的对象。
【自有属性】是直接在对象中定义的属性。
【继承属性】是在对象的原型对象中定义的属性。
可以通过对象直接量、关键字new和Object.create()函数来创建对象。
对象直接量是由若干名/值对组成的映射表,名/值对中间用冒号分隔,名/值对之间用逗号分隔,整个映射表用花括号括起来。
【图】对象直接量例子
对象直接量是一个表达式,这个表达式的每次运算都创建并初始化一个新的对象。每次计算对象直接量的时候,也都会计算它的每个属性的值。
new运算符创建并初始化一个新对象。关键字new后跟随一个函数调用。这里的函数称做构造函数,构造函数用以初始化一个新创建的对象。
每一个对象(null除外)都和另一个对象相关联。“另一个”对象就是我们熟知的原型,每一个对象都从原型继承属性。
所有通过对象直接量创建的对象都具有同一个原型对象,并可以通过Object.prototype获得对原型对象的引用。通过new和构造函数调用创建的对象的原型就是构造函数的prototype属性的值。
没有原型的对象为数不多,Object.prototype就是其中之一。它不继承任何属性。其他原型对象都是普通对象,普通对象都具有原型。
Object.create()创建一个对象,其中第一个参数是这个对象的原型。第二个可选参数,用以对对象的属性进行进一步描述。
var o1 = Object.create({x : 1, y : 2});//o1继承了属性x和y
var o2 = Object.create(null); //o2不继承任何属性和方法
var o3 = Object.create(Object.prototype);//o3和{}和new Object()一样
可以通过任意原型创建新对象,换句话说,可以使任意对象可继承。
【图】通过原型继承创建一个新对象
如果一个对象的属性名是保留字,则必须使用方括号的形式访问它们。但是ECMAScript5对此放宽了限制。
关联数组(散列、映射、字典):数组元素是通过字符串索引而不是数字索引。对象都是关联数组。
只有在查询属性时才会体会到继承的存在,而设置属性则和继承无关,可以有选择地覆盖继承的属性。
属性赋值要么失败,要么创建一个属性,要么在原始对象中设置属性,但有一个例外,如果o继承自属性来x,而这个属性是一个具有setter方法的accessor属性,那么这时将调用setter方法而不是给o创建一个属性x。setter方法是由对象o调用的,而不是定义这个属性的原型对象调用的。因此如果setter方法定义任意属性,这个操作只是针对o本身,并不会修改原型链。
查询一个不存在的属性并不会报错,如果在对象o自身的属性或继承的属性中均未找到属性x,属性访问表达式o.x返回undefined。但是如果对象不存在,试图查询这个不存在的对象的属性就会报错。
delete只是断开属性和宿主对象的联系,而不会去操作属性中的属性。
delete只能删除自有属性,不能删除继承属性。
当delete表达式删除成功或删除不存在的属性时,它返回true。如果delete后不是一个属性访问了表达式,delete同样返回true。
【图】delete例子
判断某个属性是否存在于某个对象中。用通过in运算符、hasOwnPreperty()和propertyIsEnumerable()方法来完成这个工作,甚至仅通过属性检查也可以做到这一点。还有一种更简便的方法是使用“!==”判断一个属性是否是undefined,但是它不能判断值为undefined的属性存在性。
in:如果对象的自有属性或继承属性中包含这个属性则返回true。
hasOwnProperty():自身属性返回true,继承属性返回false。
propertyIsEnumerable()是hasOwnProperty()的增强版,只有检测到是自有属性且这个属性的可枚举性为true时它才返回true。
for/in循环可以在循环体中遍历对象中所有可枚举的属性(包括自身属性和继承属性),把属性名称赋值给循环变量。对象继承的内置方法不可枚举的,但在代码中给对象添加的属性都是可枚举的。
除了for/in循环之外,定义了两个用以枚举属性名称的函数。第一个是Object.keys(),它返回一个数组,这个数组由对象中可枚举的自身属性的名称组成,他的工作原理和工具函数keys()类似。
第二个枚举属性的函数是Object.getOwnPropertyNames(),它和Object.keys()类似,只是它返回对象的所有自有属性的名称,而不仅仅是可枚举的属性。
在ECMAScript5中,属性值可以用一个或两个方法替代,这两个方法就是getter和setter定义的属性称做“取值器属性”,它不同于“数据属性”,数据属性只有一个简单的值。
var 0 = {
//普通的数据属性
data_prop: value,
//存取器属性都是成对定义的函数
getaccessor_prop() { /*这里是函数体*/},
setaccessor_prop(value) { /*这里是函数体*/}
}
为了实现属性特性的查询和设置操作,定义了“属性描述符”对象。这个对象代表那4个特性,属性描述符的属性有value、writable、enumerable和configurable。存取器属性描述符对象则用get和set属性替代value和writable。
【图】Object.getOwnPropertyDescriptor()例子
要想设置属性特性,或者想让新建属性具有某种特性,则需要调用Object.definePeoperty(),传入要修改的对象、要创建或修改的属性的名称以及属性描述符对象。
【图】Object.definePeoperty()例子
传入Object.defineProperty()的属性描述符对象不必包含所有4可特性。对于新创建的属性来说,默认的特性值是false或undefined。对于修改的已有属性来说,默认的特性值没有做任何修改。注意,这个方法要么修改已有属性要么新建自有属性,但不能修改继承属性。
如果要同时修改或创建多个属性,则需要使用Object.defineProperties()。
【图】Object.definePeoperties()例子
每一个对象都有与之相关的原型、类和可扩展性。
对象的原型属性是用来继承属性的。原型属性实在实例对象创建之初就设置好的。通过对象直接量创建的对象使用Object.prototype作为它们的原型。通过new创建的对象使用构造函数的prototype属性作为它们的原型。通过Object.create()创建的对象使用第一个参数(也可以是null)作为它们的原型。
将对象作为参数传入Object.getPrototypeOf()可以查询它的原型。实现的功能和instanceof运算符非常类似。
要想检测一个对象是否是另一个对象的原型(或处于原型链中),请使用isPrototypeOf()方法。
例如,p.isPrototypeOf(o)来检测p是否是o的原型。
对象的类属性是一个字符串,用以表示对象的类型信息。一种间接方法可以查询它,默认的toString()方法返回[object clsss]。提取已返回字符串的第8个到倒数第二个位置之间的字符。
很多对象继承的toString()返回重写了,所以必须用
Object.prototype.toString.call(o).slice(8,-1)
自定义构造函数创建的对象,类属性是“Object”,因此没方法通过类属性来区分对象的类。
【图】对象类的例子
对象的可扩展性用来表示是否可以给对象添加新属性。所有内置对象和自定义对象都是显式可扩展的,宿主对象的可扩展性是由解释器决定的。
通过将对象传入Object.esExtensible(),来判断该对象是否是可扩展的。如果想将对象转换为不可扩展的,需要调用Object.preventExtensions(),将待转换的对象作为参数传进去。一旦将对象转换为不可扩展的,就无法再将其转换回可扩展的了。preventExtensions()只影响到对象本身的可扩展性。如果给一个不可扩展的对象的原型添加属性,这个不可扩展的对象同样会继承这些新属性。
Object.seal()在preventExtensions()的基础上,将对象的所有只有属性都设置为不可配置的。Object.isSeale()检测对象是否封闭。
Object.freeze()在Object.seal()的基础上,设置自身的所有数据属性为只读。用Object.isFrozen()检测。
对象的序列化是指将对象的状态转换为字符串,也可以将字符串还原为对象。
内置函数JSON.stringify()和JSON.parse()用来序列化和还原对象。
JSON.stringify()只能序列化对象的可枚举的自身属性,对于一个不能序列化的属性来说,在序列化后的输出字符串中会将这个属性省略掉。
toString()没有参数,它将返回一个表示调用这个方法的对象值的字符串。
toLocaleString()返回一个表示这个对象的本地化字符串。Object中默认它的效果等同于toString()。
文档信息
- 版权声明:自由转载-非商用-非衍生-保持署名 | Creative
Commons BY-NC-ND 3.0
- 最后修改时间:2014年05月29日 14:35
JavaScript学习笔记【2】表达式和运算符、语句、对象,布布扣,bubuko.com