词法结构
- JavaScript程序是用Unicode字符集编写的
- JavaScript是区分大小写的, 而HTML不区分大小写.
- JavaScript会忽略程序中标识之间的空格.
- JavaScript支持"//"和"/.../"两种注释.
- 直接量: 程序中直接使用的数据值. JavaScript中的直接量为: 数值, 字符串, 布尔型和正则表达式.
- JavaScript的标识符必须以字母, 下划线或者美元符号开始, 后续跟字母, 数字, 下划线或者美元符号.
- JavaScript中关于填补分号的规则: 只要在缺少了分号就无法正确解析代码的时候, JavaScript才会填补分号.
类型, 值和变量
概述
- JavaScript的数据类型分为两类: 原始类型和对象类型. JavaScript的原始类型包括数字, 字符串和布尔值. 而对象是属性的集合, 每个属性都由"名/值对"组成.
- JavaScript中有两个特殊的原始值:** null和undefined**, 它们不是数字,字符串和布尔值. 它们通常分别代表了各自特殊类型的唯一的成员. 原始类型, null和undefined均为不可变类型.
- 普通的JavaScript对象是"命名值"的无序集合, 可以使用Object.keys()获取其keys(新版本可以使用Object.values()获取其values). 而JavaScript定义了一种有序集合, 为数组.
var o={
name:'张三',
sex:'男',
sayHello:function(){
conosle.log("sayHello");
}
};
console.log(Object.keys(o));
console.log(Object.values(o));
结果:
Array [ "name", "sex", "sayHello" ]
Array [ "张三", "男", o.sayHello() ]
- JavaScript定义了另一种特殊对象--函数. 函数是具有与它相关联的可执行代码的对象, 通过调用函数来运行可执行代码, 并返回运算结果.
- 如果函数用来初始化(使用new运算符)一个新建的对象, 我们称之为构造函数. 每个构造函数定义了一类对象--由构造函数初始化的对象组成. 类可以看做是对象类型的子类型. 除了数组类和函数类之外, JavaScript语言核心还定义了三种有用的类: 日期(Date)类定义了代表日期的对象. 正则(RegExp)类定义了表示正则表达式的对象. 错误(Error)类定义了那些表示JavaScript程序中运行时错误和语法错误的对象.
- JavaScript解释器有自己的内存管理机制, 可以自动对内存进行垃圾回收.
- JavaScript变量是无类型的, 变量可以被赋予任何类型的值. 使用var关键字来声明变量, JavaScript采用词法作用域, 不在任何函数内声明的变量称作全局变量, 它在JavaScript程序中的任何地方都是可见的. 在函数内声明的变量具有函数作用域, 并且只在函数内可见.
数字
- JavaScript不区分整数值和浮点数值,** 所有数字均以浮点数值表示.**
- JavaScript中除以0并不报错, 只是返回无穷大(Infinity)/负无穷大(-Infinity). 而0 / 0是没有任何意义的, 所以用NaN表示(NaN代表非数字, 可用isNaN()判断, 例如isNaN("hello")为true).
- JavaScript中浮点数依旧存在精度问题:(不能去比较浮点数是否相等)
var x=0.3-0.2;
var y=0.2-0.1;
console.log(x);//0.09999999999999998
console.log(y);//0.1
x == y -->false
x == 0.1-->false
y == 0.1-->true
0.07*100-->7.000000000000001
文本
转译字符
转义字符 | 含义
----|------|----
\o | NUL字符
\b | 退格符
\t| 水平制表符
\n| 换行符
\v| 垂直制表符
\f| 换页符
\r| 回车符
"| 双引号
'| 单引号
\|反斜线
\xXX| 由两位十六进制数xx指定的Latin-1字符
\uXXXX| 由四位十六进制XXXX指定的Unicode字符
字符串的使用
var s = "hello world"
s.charAt(0) ==> "h": 第一个字符
s.charAt(s.length - 1) ==> "d": 最后一个字符
s.substring(1, 4) ==> "ell": 第2~4个字符
s.slice(1, 4) ==> "ell": 同上
s.slice(-3) ==> "rld": 最后三个字符
s.indexOf("l") ==> 2: 字符l首次出现的位置
s.lastIndexOf("l") ==> 9: 字符l最后出现的位置
s.indexOf("l", 3) ==> 3: 在位置3及之后首次出现字符l的位置.
s.split(" ") ==> ["hello", "world"]: 分割字符串
s.replace("h", "H") ==> "Hello world": 替换
s.toUpperCase() ==> "HELLO WORLD": 转换成大写
布尔值
转换为false的变量: undefined, null, NaN, 0, -0, ""
null和undefined
null代表空指针, 通常用来表示数字, 字符串和对象的"无值状态"
undefined代表未初始化.
全局对象
全局属性: 比如undefined, Infinity和NaN
全局函数: 比如isNaN(), parseInt()和eval()
构造函数: 比如Date(), RegExp(), String(), Object()和Array()
全局对象: 比如Math和JSON
包装对象
对于字符串, 以下代码是正确的:
var s = "hello world"
var word = s.substring(1, 4)
//此时s已被封装成了对象,但是这句话执行完后,该对象立即销毁
既然字符串不是对象, 它为什么会有substring方法呢? 因为引用字符串s的属性, JavaScript就会将字符串通过调用new String(s)的方式装换成对象, 一旦属性引用结束, 这个新创建的对象就会销毁(同理于Number()和Boolean()):
var s = "test"
s.len = 4 //给对象设置属性
var t = s.len
t ==> undefined
而:
"hello" == new String("hello") ==> true//自动封装
"hello" === new String("hello") ==> false//全等,一个值,一个是对象
不可变的原始值和可变的对象引用
原始值: undefined, null, 数字, 布尔值, 字符串. 它们均不可改变.
对象: 数组和函数, 可改变
原始值的比较只要通过"=="即可(原始值所存储的内存地址是相同的, 而"=="是用于比较值是否相同, 所以对于原始值来说, 值相同+地址相同 == 它们相同)
对象的本质是引用, 所以只有引用同一个基对象(相同的内存地址)时, 它们才相同:
var o = {x: 1}, p = {x: 1}
o === p ==> false
o == p ==> false
q = o ==> Object {x: 1}//将o的引用赋值给q,此时他们指向同一个对象
q["y"] = 2 //q对象增加一个y=2的属性,此时0对象也会增加该属性
q === o ==> true
这里会造成一个困惑是: 为什么 o == p ==> false?
因为o和p的值本质上并不相同, 因为o/p中存储的是{x: 1}的引用, 而非具体的值. 而{x: 1}和另一个{x: 1}的引用是不相等的.
备注: 我们一般比较对象是否具有某个属性, in代表属性存在于实例+原型中, 而hasOwnProperty代表属性是否存在于实例中.
类型转换
值 | 字符串 | 数字 | 布尔值 | 对象 |
---|---|---|---|---|
undefined | "undefined" | NaN | false | throws TypeError |
null | "null" | 0 | false | throws TypeError |
true | "true" | 1 | new Boolean(true) | |
false | "false" | 0 | new Boolean(false) | |
"" | 0 | false | new String("") | |
"1.2" | 1.2 | true | new String("1.2") | |
"one" | NaN | true | new String("one") | |
0 | "0" | false | new Number(0) | |
-0 | "0" | false | new Number(-0) | |
NaN | "NaN" | false | new Number(NaN) | |
Infinity | "Infinity" | true | new Number(Infinity) | |
-Infinity | "-Infinity" | true | new Number(-Infinity) | |
1 | "1" | true | new Number(1) | |
{} | 后面解释 | 后面解释 | true | |
[] | "" | 0 | true | |
[9] | "9" | 9 | true | |
['a'] | 使用join方法 | NaN | true | |
['a','b'] | "a,b" | NaN | true | |
function(){} | 后面解释 | NaN | true |
注意:无穷大Infinity首字母必须大写,不然认为是变量
转换和相等性
进行"=="判断时, JavaScript会进行类型转换.
null == undefined ==> true
"0" == 0 ==> true
这里类型转换代表的意思是: a == b, 则a转换为b类型, 或者b转换为a类型, 再次进行比较. 所以对于null == undefined, 本质上是undefined转换为一个Object, 然后在和null进行比较.
而"0" == 0, 是将数字0转换为字符串"0", 然后再进行比较.
但可以成功进行类型转换, 不一定表示它们相等, 如undefined可转换为false, 但undefined == false的结果为false.
显示类型转换
JavaScript在需要字符串情况下, 会将变量转换为字符串; 其次, 在需要数字情况下, 会将变量转换为数字.
Number类定义的toString()方法可以接收表示转换基数的可选参数. 如果不指定此参数, 转换规则将是基于十进制:
var n = 17
n.toString() ==> "17"
n.toString(2) ==> "10001"
n.toString(8) ==> "21"
n.toString(16) ==> "11"
toFixed(): 根据小数点后的指定位数将数字转换为字符串, 它从不使用指数计数法.
toExponential(): 使用指数计数法将数字转换为指数形式的字符串, 其中小数点前只有一位, 小数点后的位数则由参数指定.
toPrecision(): 根据指定的有效数字位数将数字转换成字符串. 如果有效数字的位数少于数字部分的位数, 则转为为指数形式.
var n = 123456.789
n.toFixed(0) ==> "123457"
n.toFixed(2) ==> "123456.79"
n.toFixed(5) ==> "123456.78900"
n.toExponential(1) ==> "1.2e+5"
n.toExponential(3) ==> "1.235e+5"
n.toPrecision(4) ==> "1.235e+5"
n.toPrecision(7) ==> "123456.8"
n.toPrecision(10) ==> "123456.7890"
parseInt(): 尽可能的将字符串转换为整数, 可接收第二个可选参数, 这个参数指定数字转换的基数.
parseFloat(): 尽可能的将字符串转换为浮点数.
console.log(parseInt("111ads")) //111
对象转换为原始值
toString(): 返回一个反映这个对象的字符串.
valueOf(): 如果存在任意原始值, 它就默认将对象转换为表示它的原始值.
JavaScript中对象到字符串的转换经历以下步骤:
如果对象具有toString()方法, 则调用这个方法. 如果它返回一个原始值, JavaScript将这个值转换为字符串, 并返回这个字符串结果.
如果对象没有toString()方法, 或者这个方法并不返回一个原始值, 那么JavaScript会调用valueOf()方法, 如果存在这个方法, 则JavaScript调用它. 如果返回值是原始值, JavaScript将这个值转换为字符串, 并返回这个字符串结果.
否则, JavaScript抛出类型错误.
var o={
toString:function(){
return 9;
},
valueOf:function(){
return 10;
}
};
console.log(""+o);//结果是“10”
对象到数字的转换经历以下步骤:
如果对象具有valueOf()方法, 返回一个原始值, 则JavaScript将这个原始值转换为数字.
否则, 如果对象具有toString()方法, 返回一个原始值, 则JavaScript将其转换并返回(如果返回的原始值是boolean类型,会转成0或1)
否则, JavaScript抛出类型错误.
var o={
toString:function(){
return 9;
},
valueOf:function(){
return 10;
}
};
console.log(+o);//结果是10
变量和作用域
JavaScript没有块级作用域, 它使用了函数作用域.
在JavaScript中, 使用var声明的全局变量是不可配置的(变量本身具有属性, 如是否可删除, 编辑, 是否只读等):
var a = "hello"
b = "world" //相当于给window对象添加了属性b
delete a ==> false
delete b ==> true
a ==> "hello"
b ==> VM920:1 Uncaught ReferenceError: b is not defined(…)
表达式和运算符
表达式
数组初始化时, 如果使用逗号(","), 则初始化为undefined:
var arr = [1,,,2]
arr.length ==> 4
arr ==> [1, undefined × 2, 2]
如果针对对象来说, 使用字面量来初始化, 那么其key/value中的key代表的是字符串:
var key = "hello"
var d1 = {key: "world"}
d1 ==> Object {key: "world"}
这里d1的声明等价于:
var d1 = {"key": "world"}
但如果使用索引来初始化, 则不一样:
var key = "hello"
var d2 = {}
d2[key] = "world"
d2 ==> Object {hello: "world"}
数组本身就是一个支持下表索引的对象:
var arr = [12, 13, 14]
arr["1"] ==> 13
arr[1] ==> 13
使用new构造出一个对象不同于使用字面量构造出一个对象, 考虑如下的代码:
var MY_APP_1 = function() {
this.firstMethod = function() {};
this.secondMethod = function() {};
};
var MY_APP_2 = {
firstKey: functon() {},
secondKey: function() {}
};
主要的不同点在于: 通过new, 我们可以创建多个MY_APP_1实例, 而每个实例均绑定到内部的this; 而对于MY_APP_2来说, 它仅仅只是一个变量而已.
针对MY_APP_1来说, 还有以下特殊的几点:
firstMethod/secondMethod是绑定到具体的实例上, 非绑定状态下它们并不存在.
在创建具体实例后, 如果没有绑定一个具体的对象, 则this默认绑定到window对象上:
var app1 = new MY_APP_1();
app1.firstMethod();
// ERROR
// window.firstMethod();
var app2 = MY_APP_1();
// ERROR
// app2.firstMethod();
window.firstMethod();
运算符
- JavaScript运算符通常会根据操作数进行类型转换, 所以"3" * "5"是合法的, 为数字15.
- 加号既可用于字符串连接, 也可用于数字的相加, 但优先于字符串连接. 所以将数组转换为字符串时, 我们可以编写如下的代码:
var arr = [1,2,3]
"" + arr ==> "1,2,3"
但我们如果仅仅将一个字符串转换为数字, 也可以使用如下的技巧:
+"3" ==> 3
1.in运算符用于判断属性是否存在于对象实例+原型中.
2.instanceof运算符用于判断左边对象是否为右边类的实例.
3.typeof运算符用于判断对象的类型:
x | typeof x |
---|---|
undefined | "undefined" |
null | "object" |
true 或false | "boolean" |
任意数字或NaN | "number" |
任意字符串 | "string" |
任意函数 | "function" |
任意内置对象(非函数) | "object" |
任意宿主对象 | 由编译器自身决定 |
- delete用于删除一个对象的属性, 对数组来说相当于赋值undefined:
var o = {x: 1, y:2}
delete o.x
o ==> Object {y: 2}
var arr = [11, 12, 13]
delete arr[1]
arr ==> [11, undefined × 1, 13]
语句
空语句(单独一个分号)通常用在初始化一个数组中, 例如:
for (var i = 0; i < a.length; a[i++] = 0) ;
var和function都是用来声明语句的, 而function通常有两种写法:
var f = function(x) { return x + 1; } //将表达式赋值给一个变量
function f(x) { return x + 1; }
for/in循环并不会遍历对象的所有属性, 只有"可枚举"的属性才会遍历到.
"use strict"
- 在严格模式中禁止使用with语句.
- 在严格模式中, 所有的变量都要先声明, 否则将会抛出一个引用错误异常.
- 在严格模式中, 调用的函数中的一个this值是undefined.
- 在严格模式中, 当通过call()/apply()来调用函数时, 其中的this值就是通过call()或apply()传入的第一个参数(在非严格模式中, null和undefined值被全局对象和转换为对象的非对象值所代替)
- 在严格模式中, 给只读属性赋值和给不可扩展的对象创建新成员都将抛出一个类型错误异常.
- 在严格模式中, 传入eval()的代码不能在调用程序所在的上下文中声明变量或定义函数, 而在非严格模式中是可以这样做的.
- 在严格模式中, 函数里的arguments对象拥有传入函数值的静态副本, 即不可修改:
"use strict"
function f(arr) {
f.arguments[0][0] = 11;
}
var arr = [1, 2, 3];
f(arr);
console.log(arr);
这里代码将会报错.
针对delete, 后跟非法的标识符将抛出一个语法错误异常; 如果试图删除一个不可配置的属性将会抛出一个类型错误异常.