JavaScript标准参考教程
第一章
JS宿主环境 最常见的
- 浏览器
- 服务器环境如node
==JavaScript核心语法== 只有两部份
- 基本的语法构造(操作符、控制结构、语句等)
- 标准库(一系列具有各种功能的对象比如
Array
、Date
、Math
等)
除此之外,有宿主环境提供的API
==浏览器提供的API==
- 浏览器控制类:操作浏览器
- DOM类:操作网页各种元素
- Web类:实现互联网各种功能
==服务器环境提供的API==
- 文件操作API
- 网络通信API
- 等等
第二章
基本语法
变量【重要】
js区分大小写变量名
undefined “未定义” var声明但不赋值
==JS是动态类型语言==
变量的类型没有限制,可以随时更改类型,例如:
var a = 1;
a = ‘hello’;
用var重新声明(但不赋值)一个已存在的变量是无效的,例如:
var x = 1;
var x; //此句无效,等于没写,x等于1,x不会等于undefined
==但是==
var x = 1;
var x = undefined;//你瞅瞅会输出啥
==然后==
var x = 1;
function py(){console.log(x);var x = 2;};py();//你瞅瞅会输出啥
相当于
var x = 1;
function py(){
var x;
console.log(x);
x = 2;
}
py();
变量提升(hoisting)
JavaScript 引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部。
console.log(a);
var a = 1;
//等同于以下,输出undefined
var a;
console.log(a);
a = 1;
标识符
==标识符(identifier)指的是用来识别各种值的合法名称。==
第一个字符:任意Unicode字母(英文和其他语言字母),以及$
和_
第二个字符以及后面字符,除了可用Unicode字母、美元符、下划线,还可用数字
一些保留字(关键字)不能用作标识符,例如for、true、class、case、yield、super等。
以下合法标识符
π arg0 _tmp $elem 临时变量
以下是不合法标识符
1a // 第一个字符不能是数字
23 // 同上
*** // 标识符不能包含星号
a+b // 标识符不能包含加号
-d // 标识符不能包含减号或连词线
注释
//
和/* */
此外,由于历史上 JavaScript 可以兼容 HTML 代码的注释,所以<!--
和-->
也被视为合法的单行注释。
例如:以下三种写法都是注释
- <!-- x= 2;
- --> x = 3;
- <!-- x= 4;-->
n --> 0 相当于 n-- > 0 (一个判断语句,先判断是否大于0,再做减减操作)
条件语句
if结构和if...else...结构
var x = 1;
var y = 2;
if (x = y) {
console.log(x);
}
// "2"
实际含义:将y
赋值给变量x
,再判断变量x
的值(等于2)的布尔值(结果为true
)。
如果碰上下面这个例子:
var x = 1;
var y = "";
if (x = y) {
console.log(x);
}else{
console.log('i win')
}
想一想,结果会打印出什么呢?动手在浏览器试试吧。
==else
代码块总是与离自己最近的那个if
语句配对。==
var m = 1;
var n = 2;
if (m !== 1)
if (n === 2) console.log('hello');
else console.log('world');
上面代码不会有任何输出,else
代码块不会得到执行,因为它跟着的是最近的那个if
语句,相当于下面这样。
if (m !== 1) {
if (n === 2) {
console.log('hello');
} else {
console.log('world');
}
}
💡 所以一般不这么写有点令人费解的语句。
switch结构
💡 每个case之后都要加上break,否则会发生穿透,一路走到底(走到加了break的语句)
default:
如果是最后一个语句,那可以不加break
而且,swicth语句中的case用的是严格的“===”(严格相等运算符)判断,也会判断类型。
💡 switch(x) {case y:} x和y还可以是表达式
例如这么写:
switch(true){
case 1>0 : xxxx;break; //执行这一句
case 1<0 : yyyy;break;
}
这么写:
switch(1 < 0){
case true : xxxx;break;
case false : yyyy;break; //执行这一句
}
三元运算符 ?:
(条件) ? 表达式1 : 表达式2
上面代码中,如果“条件”为true
,则返回“表达式1”的值,否则返回“表达式2”的值。
var even = (n % 2 === 0) ? true : false;
上面代码中,如果n
可以被2整除,则even
等于true
,否则等于false
。它等同于下面的形式。
var even;
if (n % 2 === 0) {
even = true;
} else {
even = false;
}
三元运算符可以被视为if...else...
的简写形式,因此可以用于多种场合。
循环语句
for循环
for
语句的三个部分(initialize、test、increment),可以省略任何一个,也可以全部省略。
for( ; ; ){console.log(‘无限循环’)} 全部省略,无限循环。
do...while循环
do
语句
while (条件);
与while
循环类似,唯一的区别就是先运行一次循环体,然后判断循环条件。
break语句和continue语句
break
语句用于跳出代码块或循环。
continue
语句用于立即终止本轮循环,返回循环结构的头部,开始下一轮循环。
💡 这两个关键字是==针对本层循环==而言
数据类型
==6种数据类型==
- 数值number
- 字符串string
- 布尔值boolean
-
undefined
: 表示“未定义”或不存在,即由于目前没有定义,所以此处暂时没有任何值。 -
null
: 表示空值,即此处的值为空。 - 对象object: 各种值组成的集合。
第7种类型symbol
,为ES6新增类型。
💡 number|string|boolean 原始类型,无法再细分
💡 object 合成类型(complex type),往往是多个原始类型的值的合成,可以看成是一个存放各种值的容器。
确定值类型的三种方法
typeof
运算符instanceof
运算符Object.prototype.toString
方法
typeof的取值有以下:都是小写的,要和instanceof区分开来。
“number”、“string”、“boolean”、“object”、“undefined”、“function”和新的“symbol”
💡 打醒精神:
typeof window // "object"
typeof {} // "object"
typeof [] // "object"
var o = {};
var a = [];
o instanceof Array // false
a instanceof Array // true
typeof这儿,数组也是object类型,
typeof null //输出的竟然不是‘null’而是‘object’,历史原因,因为95年第一版时没有null类型,为了兼容以前代码,就一直没更改。
typeof不够精确,所以有了instanceof。
type of object //错误用法,并不是查看object类型是不是object类型,object在这儿就是一个未被声明的字符串
==number类型解读==
- 普通数值
- NaN (not a number) e.g 0/0 | Math.sqrt(-4) 负数开平方
- Infinity e.g 1/0 1.5/0 -1/0 (为-Infinity) 非零数除以0 视为无穷
==null和undefined解读==
null表示空值,即该处的值现在为空。
undefined表示“未定义”。
以下情况都是undefined典型场景
// 变量声明了,但没有赋值
var i;
i // undefined
// 调用函数时,应该提供的参数没有提供,该参数等于 undefined
function f(x) {
return x;
}
f() // undefined
// 对象没有赋值的属性
var o = new Object();
o.p // undefined
// 函数没有返回值时,默认返回 undefined
function f() {}
f() // undefined
函数没有返回值,默认返回undefined,可以解释在浏览器控制台时,为什么总是出现undefined了。(?对么)
null的典型场景
- 作为函数的参数,表示该函数的参数不是对象
- 作为对象原型链的终点
- 在JS的DOM元素获取中,如果没有获取到指定的元素对象,结果是null
(对象有那个属性,但那个属性值为空)
==布尔值==
最常用的,if(xxx),xxx什么时候为真,什么时候为假呢?
转换规则:
undefined
null
false
0
-
NaN
(not a number 不是一个数字) -
""
或''
(空字符串)
以上==6个值==都被转为false
,其他的值都是true
。
==记忆:5种类型,除了object和symbol都到了==
注意:空值是false,但是空数组[]和空对象{}对应的都是true
数据类型转换
var x = y ? 1 : 'a';
独自解读一下这句话(然后到http://javascript.ruanyifeng....
强制转换:Number() | String() | Boolean()
Number()转换返回值: 数值 | NaN | 0
⌛思考下:[]/{}/[5]/null/undefined 分别转换成什么
// 数值:转换后还是原来的值
Number(324) // 324
Number([5]) // 5
// 字符串:如果可以被解析为数值,则转换为相应的数值
Number('324') // 324
// 字符串:如果不可以被解析为数值,返回 NaN 非数
Number('324abc') // NaN
// 空字符串转为0
Number('') // 0
// 布尔值:true 转成 1,false 转成 0
Number(true) // 1
Number(false) // 0
// undefined或对象object(除包含单数值的数组)或函数:转成 NaN
Number(undefined) // NaN
Number({}) //NaN 空对象
Number({a: 1}) // NaN
Number([1, 2, 3]) // NaN
Number([5]) // 5
// null:转成0 | 空数组:转成0
Number(null) // 0
Number([])
Number()比parseInt()严格:
parseInt('42 cats') // 42
Number('42 cats') // NaN
String() 返回值:字符串(带引号)
⌛思考:[]、{}、null、undefined、false、[1,2,3]
String(123) // "123"
String('abc') // "abc"
String(true) // "true"
String(undefined) // "undefined"
String(null) // "null"
//转换对象
String({}) //[object Object]
String({x:1,y:2})
//转换数组
String([1,2,3]) // "1,2,3"
String([]) //""
//转换函数
function kv(){console.log(1)}
String(kv) // "function kv(){console.log(1)}"
Boolean() 返回值:false | true
⌛思考:Boolean()、Boolean({})、Boolean([])、Boolean(0/0)、Boolean(-4/0)、Boolean(false)、
Boolean(new Boolean(false))
转换规则:除了以下五个值的转换结果为
false
,其他的值全部转换为true
。
undefined
null
-0
或+0
NaN
''
(空字符串)
附:
if(xxx)其实就是if(Boolean()),判断if句式中条件的真假即可。
转换实例:
Boolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean(NaN) // false
Boolean('') // false
//对象的转换
Boolean({}) // true
Boolean([]) // true
Boolean(new Boolean(false)) // true
自动转换
自动转换以强制转换为基础,转换自动完成,用户不可见。
==有以下三种情况==
- 不同类型的数据互相运算。
- 对非布尔值类型的数据求布尔值。
- 对非数值类型的值使用一元运算符(即
+
和-
)。
规则
预期什么类型的值,就调用该类型的转换函数。比如,某个位置预期为字符串,就调用String
函数进行转换。
特别注意
如果该位置既可以是字符串,也可能是数值,那么默认转为数值。
由于自动转换具有不确定性,而且不易排除错误,建议在预期为布尔值、数值、字符串的地方,全部使用Boolean
、Number
和String
函数进行显式转换。
自动转换为布尔值
if ( !undefined
&& !null
&& !0
&& !NaN
&& !''
) {
console.log('true');
} //
转换为布尔值除了Boolean()以外的另外两种写法:
// 写法一
expression ? true : false
// 写法二
!! expression
实际内部调用也是Boolean()
自动转换为字符串
'5' + 1 // '51'
'5' + true // "5true"
'5' + false // "5false"
'5' + {} // "5[object Object]"
'5' + [] // "5"
'5' + function (){} // "5function (){}"
'5' + undefined // "5undefined"
'5' + null // "5null"
这种自动转换很容易出错,要小心,例如下面代码,预期110,结果变成中国联通
var obj = {
width: '100'
};
obj.width + 10 // "10020"
自动转换为数值
⌛思考:null+1 | ‘null’+1 | ‘5’-1 | ‘5’+1 | null+1 | undefined+1
除了加法运算符(+
)有可能把运算子转为字符串,其他运算符都会把运算子自动转成数值。
+
可能造成字符串拼接。 例如: ‘5’-2 //3 || ‘5’+2 //“52”
+
如果其中有运算子是字符串,可能会发生字符串拼接,如果运算子没有字符串,则会把运算子转为数值。
先调用Number转成数值,再做运算(+
可能会比较特殊,有字符串时,要注意)。
'5' - '2' // 3
'5' * '2' // 10
true - 1 // 0
false - 1 // -1
'1' - 1 // 0
'5' * [] // 0
false / '5' // 0
'abc' - 1 // NaN
null + 1 // 1
undefined + 1 // NaN
上面代码中,运算符两侧的运算子,都被转成了数值。
一元运算符也会把运算子转成数值
+'abc' // NaN
-'abc' // NaN
+true // 1
-false // 0
💡 ==引申出一个小技巧==
字符串(里头是数值)和数值之间快速互转。
字符串=>数值:+
string e.g +“123” //输出123
数值=>字符串:num+‘’
e.g 123+‘’ //输出“123”
关于Number()、String()、Boolean() 实际调用xx.toString()、xx.valueOf()的先后顺序问题
数值
前缀常识
- 0b表示二进制
- 0d表示十进制(或者不写)
- 0表示八进制
- 0x表示十六进制
JavaScript的底层没有整数,d 所有数字都是小数(64位浮点数)。
1 === 1.0 // true
JavaScript浮点数的64个二进制位的组成
- 第1位:符号位,
0
表示正数,1
表示负数- 第2位到第12位(共11位):指数部分
- 第13位到第64位(共52位):小数部分(即有效数字)
64位浮点数的指数部分的长度是11个二进制位,意味着指数部分的最大值是2047(2的11次方减1)
JavaScript 能够表示的数值范围为2^1024^到2^-1023^(开区间),超出这个范围的数无法表示。
如果一个数大于等于2的1024次方,那么就会发生“正向溢出”,即 JavaScript 无法表示这么大的数,这时就会返回Infinity
。
如果一个数小于等于2的-1075次方(指数部分最小值-1023,再加上小数部分的52位),那么就会发生为“负向溢出”,即 JavaScript 无法表示这么小的数,这时会直接返回0。
Math.pow(2, 1024) // Infinity
Math.pow(2, -1075) // 0
JavaScript 提供Number
对象的MAX_VALUE
和MIN_VALUE
属性,返回可以表示的具体的最大值和最小值。
Number.MAX_VALUE // 1.7976931348623157e+308
Number.MIN_VALUE // 5e-324
数值的表示法
字面形式直接表示,比如35
(十进制)和0xFF
(十六进制)
科学计数法表示,如下:
123e3 // 123000
123e-3 // 0.123
-3.1E+12
.1e-23
以下两种情况,JavaScript 会自动将数值转为科学计数法表示,其他情况都采用字面形式直接表示。
- 小数点前的数字多于21位
- 小数点后的零多于5个
1234567890123456789012
// 1.2345678901234568e+21
123456789012345678901
// 123456789012345680000
// 小数点后紧跟5个以上的零,就自动转为科学计数法
0.0000003 // 3e-7
// 否则,就保持原来的字面形式
0.000003 // 0.000003
数值的进制
- 十进制:没有前导0的数值。
- 八进制:有前缀
0o
或0O
的数值,或者有前导0、且只用到0-7的八个阿拉伯数字的数值。- 十六进制:有前缀
0x
或0X
的数值。- 二进制:有前缀
0b
或0B
的数值。
运算符
加法运算符
特殊的加法运算符
==强大的字符串==
只要有一个运算子是字符串,加法运算符就变成连接运算符
加法运算符是在运行时决定,到底是执行相加,还是执行连接。也就是说,运算子的不同,导致了不同的语法行为,这种现象称为“重载”(overload)。由于加法运算符存在重载,可能执行两种运算,使用的时候必须很小心。
'3' + 4 + 5 // "345"
3 + 4 + '5' // "75"
3 + (4 + '5') // "345"
除了加法运算符,其他算术运算符(比如减法、除法和乘法)都不会发生重载。它们的规则是:==所有运算子一律转为数值,再进行相应的数学运算。==
如果运算子是对象,必须先转成原始类型的值,然后再相加。
对象转成原始类型的值,规则如下:
- 自动调用对象的
valueOf
方法。 - 对象的
valueOf
方法总是返回对象自身,这时再自动调用对象的toString
方法,将其转为字符串。
var obj = { p: 1 };
obj.valueOf().toString() // "[object Object]"
可以自己定义valueOf
方法或toString
方法,得到想要的结果。
var obj = {
valueOf: function () {
return 1;
}
};
obj + 2 // 3
上面代码中,我们定义obj
对象的valueOf
方法返回1
,于是obj + 2
就得到了3
。这个例子中,由于valueOf
方法直接返回一个原始类型的值,所以不再调用toString
方法。
下面是自定义toString
方法的例子。
var obj = {
toString: function () {
return 'hello';
}
};
obj + 2 // "hello2"
上面代码中,对象obj
的toString
方法返回字符串hello
。前面说过,只要有一个运算子是字符串,加法运算符就变成连接运算符,返回连接后的字符串。
这里有一个特例,如果运算子是一个Date
对象的实例,那么会优先执行toString
方法。
var obj = new Date();
obj.valueOf = function () { return 1 };
obj.toString = function () { return 'hello' };
obj + 2 // "hello2"
上面代码中,对象obj
是一个Date
对象的实例,并且自定义了valueOf
方法和toString
方法,结果toString
方法优先执行。
算术运算符
余数运算符
运算结果的正负号由第一个运算子的正负号决定。
-1 % 2 // -1
1 % -2 // 1
自增和自减运算符
++
和--
放在变量之后,会先返回变量操作前的值,再进行自增/自减操作;放在变量之前,会先进行自增/自减操作,再返回变量操作后的值。
数值运算符
+
和-
一元运算符,作用在于可以将任何值转为数值(与Number
函数的作用相同)。
-(-X) 相当于 +X
比较运算符
<
小于运算符>
大于运算符<=
小于或等于运算符>=
大于或等于运算符==
相等运算符===
严格相等运算符!=
不相等运算符!==
严格不相等运算符
字符串的比较
字符串按照字典顺序进行比较。JavaScript 引擎内部首先比较首字符的 Unicode 码点。如果相等,再比较第二个字符的 Unicode 码点,以此类推。
'cat' > 'Cat' // true'
上面代码中,小写的c
的 Unicode 码点(99
)大于大写的C
的 Unicode 码点(67
),所以返回true
。
例如: ‘a11’ 和 ‘a2’ 先对比第一个字母a相同,再对比第二个字符‘1’和‘2’ ‘2’大 所以 ‘a2’胜出
由于所有字符都有 Unicode 码点,因此汉字也可以比较。
'大' > '小' // false
上面代码中,“大”的 Unicode 码点是22823,“小”是23567,因此返回false
。
非字符串的比较
原始类型值比较
两个原始类型的值的比较,除了相等运算符(==
)和严格相等运算符(===
),其他比较运算符都是先转成数值再比较。
特殊情况
即任何值(包括NaN
本身)与NaN
比较,返回的都是false
。
六亲不认,凶起来连自己都翻脸。
对象比较
运算子是对象,会转为原始类型的值,再进行比较。
先调用valueOf
方法;如果返回的还是对象,再接着调用toString
方法。
举个栗子:
var x = [2];
x > '11' // true
// 等同于 [2].valueOf().toString() > '11'
// 即 '2' > '11'
x.valueOf = function () { return '1' };
x > '11' // false
// 等同于 [2].valueOf() > '11'
// 即 '1' > '11' false
两个对象之间的比较也是如此。
[2] > [1] // true
// 等同于 [2].valueOf().toString() > [1].valueOf().toString()
// 即 '2' > '1'
[2] > [11] // true
// 等同于 [2].valueOf().toString() > [11].valueOf().toString()
// 即 '2' > '11'
{x: 2} >= {x: 1} // true
// 等同于 {x: 2}.valueOf().toString() >= {x: 1}.valueOf().toString()
// 即 '[object Object]' >= '[object Object]'
注意,Date 对象实例用于比较时,是先调用toString
方法。如果返回的不是原始类型的值,再接着对返回值调用valueOf
方法。
相等运算符和严格相等运算符
严格相等运算符
如果两个值不是同一类型,严格相等运算符(===
)直接返回false
,而相等运算符(==
)会将它们转换成同一个类型,再用严格相等运算符进行比较。
比较算法如下:
- 不同类型的值,直接返回false
-
同一类型原始值(数值、字符串、布尔值),值相同就返回true,反之false。
特殊地
NaN === NaN // false +0 === -0 // true
-
两个复合类型(对象、数组、函数)的数据比较时,不是比较它们的值是否相等,而是比较它们是否指向同一个地址。
{} === {} // false [] === [] // false (function () {} === function () {}) // false
看起来相等,实际上存在不同的内储存地址,所以false。
如果两个变量引用同一个对象,则它们相等。
var v1 = {}; var v2 = v1; v1 === v2 // true
严格相等运算符比较的是地址,而大小于运算符比较的是值。
-
undefined
和null
与自身严格相等。var v1; var v2; v1 === v2 // true,都是undefined
严格相等运算符有一个对应的“严格不相等运算符”(!==
),它的算法就是先求严格相等运算符的结果,然后返回相反值。
相等运算符
==比较相同类型的数据时,与严格相等运算符完全一样。==
比较不同类型的数据时,相等运算符会先将数据进行类型转换,然后再用严格相等运算符比较。
类型转换规则如下:
- 原始类型的数据(数值、字符串、布尔值),转换成数值类型再进行比较。
-
对象(指广义对象,包括数组、对象和函数)和原始类型的值比较时,对象转换成原始类型的值,然后再进行原始类型值之间的比较。其实就是把对象与原始类型都转换成数值类型,然后比较。
[1] == '1' // true // 等同于 Number([1]) == Number('1') [1] == true // true // 等同于 Number([1]) == Number(true)
- 对象(指广义对象)和对象比较时,同严格相等运算符,比较的是地址而不是值。
-
特殊的==undefined 和 null==
undefined
和null
与其他类型的值比较时,结果都为false
,它们互相比较时结果为true
。而不是转为数值类型再比较。切记切记。
false == null // false false == undefined // false 0 == null // false 0 == undefined // false undefined == null // true
绝大多数情况下,对象与
undefined
和null
比较,都返回false
。只有在对象转为原始值得到undefined
时,才会返回true
,这种情况是非常罕见的。
一些反直觉的比较结果,好好感受下:
0 == '' // true
0 == '0' // true
2 == true // false
2 == false // false
false == 'false' // false
false == '0' // true
false == undefined // false
false == null // false
null == undefined // true
' \t\r\n ' == 0 // true
相等运算符有一个对应的“不相等运算符”(!=
),两者的运算结果正好相反。
布尔运算符
- 取反运算符:
!
- 且运算符:
&&
- 或运算符:
||
- 三元运算符:
?:
!!x
// 等同于
Boolean(x)
if (i) {
doSomething();
}
// 等同于
i && doSomething(); //短路机制
//或的短路机制
var x = 1;
true || (x = 2) // true,不再运算x=2
x // 1
或的短路机制常有以下用法:
function saveText(text) {
text = text || '';
// ...
}
// 或者写成
saveText(this.text || '')
如果函数调用时,没有提供参数,则该参数默认设置为空字符串。
text为真就用text,否则为空字符串。
其他好用的运算符
void运算符
void
运算符的作用是执行一个表达式,然后不返回任何值,或者说返回undefined
。
void(0) //undefined
主要用途是浏览器的书签工具(bookmarklet),以及在超级链接中插入代码防止网页跳转。
<a href="javascript: void(document.form.submit())">
提交
</a>
<!--用户点击提交表单,但是不产生页面跳转-->
逗号运算符
逗号运算符用于对两个表达式求值,并返回后一个表达式的值。
'a', 'b' // "b"
var x = 0;
var y = (x++, 10); //var y = x++, 10 为错误写法
x // 1
y // 10
圆括号作用
==圆括号不是运算符,而是一种语法结构==
两种用法:
- 把表达式放在圆括号之中,提升运算的优先级
- 跟在函数的后面,作用是调用函数。
var x = 1;
(x) = 2;
//不报错,因为(x)只是改优先级,不进行求值
圆括号之中,只能放置表达式,如果将语句放在圆括号之中,就会报错。
函数中,圆括号有很多妙用。
左结合与右结合
对于优先级别相同的运算符,大多数情况,计算顺序总是从左到右,这叫做运算符的“左结合”(left-to-right associativity),即从左边开始计算。
少数运算符的计算顺序是从右到左,即从右边开始计算,这叫做运算符的“右结合”(right-to-left associativity)。其中,最主要的是赋值运算符(=
)和三元条件运算符(?:
)。从右往左。
思考:
w = x = y = z;
q = a ? b : c ? d : e ? f : g;
公布答案:
w = (x = (y = z));
q = a ? b : (c ? d : (e ? f : g));
字符串
==定义==
如果要在单引号字符串的内部,使用单引号,就必须在内部的单引号前面加上反斜杠,用来转义。双引号字符串内部使用双引号,也是如此。否则报错
'Did she say \'Hello\'?'
// "Did she say 'Hello'?"
"Did she say \"Hello\"?"
// "Did she say "Hello"?"
如果长字符串必须分成多行,可以在每一行的尾部使用反斜杠。
var longString = 'Long \
long \
long \
string';
longString
// "Long long long string"
上面代码表示,加了反斜杠以后,原来写在一行的字符串,可以分成多行书写。但是,输出的时候还是单行,效果与写在同一行完全一样。注意,反斜杠的后面必须是换行符,而不能有其他字符(比如空格),否则会报错。
不加反斜杠会报错。
想输出多行字符串,可利用多行注释技巧来改造
(function () { /*
line 1
line 2
line 3
*/}).toString().split('\n').slice(1, -1).join('\n')
// "line 1
// line 2
// line 3"
==转义符==
\0
:null(\u0000
)\b
:后退键(\u0008
)\f
:换页符(\u000C
)\n
:换行符(\u000A
)\r
:回车键(\u000D
)\t
:制表符(\u0009
)\v
:垂直制表符(\u000B
)\'
:单引号(\u0027
)\"
:双引号(\u0022
)\\
:反斜杠(\u005C
)
如果在非特殊字符前面使用反斜杠,则反斜杠会被省略。
'\a'
// "a"
如果字符串的正常内容之中,需要包含反斜杠,则反斜杠前面需要再加一个反斜杠,用来对自身转义。
"Prev \\ Next"
// "Prev \ Next"
字符串与数组
字符串可以被视为字符数组,因此可以使用数组的方括号运算符,用来返回某个位置的字符(位置编号从0开始)。 e.g var s = ‘hello’; s[0] //‘h’
方括号中的数字超过字符串的长度,或者方括号中根本不是数字,则返回undefined
。
注意:实际上,无法改变字符串之中的单个字符。
var s = 'hello';
delete s[0];
s // "hello"
s[1] = 'a';
s // "hello"
s[5] = '!';
s // "hello"
==上述操作说明,字符串内的单个字符无法改变和增加删除。==
length属性
length属性返回字符串长度,但是无法改变。
var s = 'hello';
s.length // 5
s.length = 7;
s.length // 5
//赋值失败
字符集
JavaScript 使用 Unicode 字符集。JavaScript 引擎内部,所有字符都用 Unicode 表示。
不仅以 Unicode 储存字符,还允许直接在程序中使用 Unicode 码点表示字符,即将字符写成\uxxxx
的形式,其中xxxx
代表该字符的 Unicode 码点。
JavaScript 会自动识别一个字符是字面形式表示,还是 Unicode 形式表示。输出给用户的时候,所有字符都会转成字面形式。
var s = '\u00A9';
s // "©"
var f\u006F\u006F = 'abc';
foo // "abc"
每个字符在 JavaScript 内部都是以16位(即2个字节)的 UTF-16 格式储存。也就是说,JavaScript 的单位字符长度固定为16位长度,即2个字节。(历史由来)
但实际上,UTF-16 有两种长度:对于码点在U+0000
到U+FFFF
之间的字符,长度为16位(即2个字节);对于码点在U+10000
到U+10FFFF
之间的字符,长度为32位(即4个字节)。
JavaScript对UTF-16支持不完整。由于历史原因,只支持两字节的字符,不支持四字节的字符。这是因为 JavaScript 第一版发布的时候,Unicode 的码点只编到U+FFFF
,因此两字节足够表示了。
例如:四字节字符𝌆
,JavaScript 认为𝌆
的长度为2,而不是1。即2个两字节字符。
Base64转码
btoa()
:任意值转为 Base64 编码atob()
:Base64 编码转为原来的值
对象
对象就是一组键值对(key-value)集合,是无序的复合数据集合。类似map结构。
一个对象的键值对之间,用逗号分隔。
💫键名如果是数值,会被自动转为字符串。
如果键名不符合标识名的条件(比如第一个字符为数字,或者含有空格或运算符),且也不是数字,则必须加上引号,否则会报错。
// 报错
var obj = {
1p: 'Hello World'
};
// 不报错
var obj = {
'1p': 'Hello World',
'h w': 'Hello World',
'p+q': 'Hello World'
};
对象两种调用属性方式,obj.pro | obj[‘pro’]。在上面这个栗子,只能用[‘’]的方式调用。
对象的每一个键名又称为“属性”(property),它的“键值”可以是任何数据类型。如果一个属性的值为函数,通常把这个属性称为“方法”,它可以像函数那样调用。
var obj = {
p: function (x) {
return 2 * x;
}
};
obj.p(1) // 2
obj["p"](1) // 2 ,第二种调用方法
表达式还是语句?
如果行首是一个大括号,它到底是表达式还是语句?
{ foo: 123 }
JavaScript 引擎读到上面这行代码,会发现可能有两种含义。第一种可能是,这是一个表达式,表示一个包含foo
属性的对象;第二种可能是,这是一个语句,表示一个代码区块,里面有一个标签foo
,指向表达式123
。
==外面加上圆括号,理解为对象;不加圆括号,理解为代码块。==
这种差异在eval
语句(作用是对字符串求值)中反映得最明显。
eval('{foo: 123}') // 123
eval('({foo: 123})') // {foo: 123}
没有圆括号,eval
将其理解为一个代码块;加上圆括号以后,就理解成一个对象。
属性的操作
读取属性
点运算符和方括号运算符。
var obj = {
p: 'Hello World'
};
obj.p // "Hello World"
obj['p'] // "Hello World"
上面代码分别采用点运算符和方括号运算符,读取属性p
。
请注意,如果使用方括号运算符,键名必须放在引号里面,否则会被当作变量处理。
var foo = 'bar';
var obj = {
foo: 1,
bar: 2
};
obj.foo // 1
obj[foo] // 2
方括号更加灵活多样,方括号运算符内部还可以使用表达式,点号不行。
obj['hello' + ' world']
obj[3 + 3]
数字键可以不加引号,因为自动转为字符串。数字键不能用点运算符,会被当成小数点,报错。
var obj = {
0.7: 'Hello World'
};
obj['0.7'] // "Hello World"
obj[0.7] // "Hello World"
obj.0.7 //报错,不可用点运算符。
属性的赋值
点运算符和方括号运算符,除了读取,还可用于赋值。
var obj = {};
obj.foo = 'Hello';
obj['bar'] = 'World';
JavaScript 允许==属性的“后绑定”==,也就是说,你可以在任意时刻新增属性,没必要在定义对象的时候,就定义好属性。
查看所有属性
Object.keys
,查看一个对象本身的所有属性(所有‘键’),返回的是一个数组(Array)。
💡 技巧:想查看一个对象中有多少个键,使用Object.keys(objxx).length
方法即可。
(或Object.keys(objxx)[‘length’])
var obj = {
key1: 1,
key2: 2
};
Object.keys(obj);
// ['key1', 'key2']
delete命令
用于删除对象的某个属性,删除成功后返回true
删除存在或不存在的属性,都会返回true;只有一种情况,该属性存在而且不得删除,delete才会返回false。
var obj = { p: 1 };
Object.keys(obj) // ["p"]
delete obj.p // true
obj.p // undefined
Object.keys(obj) // []
var obj = Object.defineProperty({}, 'p', {
value: 123,
configurable: false
});
obj.p // 123
delete obj.p // false
上面代码之中,对象obj
的p
属性是不能删除的,所以delete
命令返回false
(关于Object.defineProperty
方法的介绍,请看《标准库》的 Object 对象一章)。
var obj = {};
delete obj.toString // true
obj.toString // function toString() { [native code] }
另外,需要注意的是,delete
命令只能删除对象本身的属性,无法删除继承的属性(关于继承参见《面向对象编程》章节)。
上面代码中,toString
是对象obj
继承的属性,虽然delete
命令返回true
,但该属性并没有被删除,依然存在。这个例子还说明,即使delete
返回true
,该属性依然可能读取到值。
in运算符
in
运算符用于检查对象是否包含某个属性(注意,检查的是键名,不是键值),如果包含就返回true
,否则返回false
。
var obj = { p: 1 };
'p' in obj // true
不能识别哪些属性是对象自身的,哪些属性是继承的。toString
方法不是对象obj
自身的属性,而是继承的属性。但是,in
运算符不能识别,对继承的属性也返回true
。
var obj = {};
'toString' in obj // true
for...in循环
该循环用来遍历一个对象的全部属性。
var obj = {a: 1, b: 2, c: 3};
for (var i in obj) {
console.log(obj[i]); | console.log(i)
}
// 1 | a
// 2 | b
// 3 | c
下面是一个使用for...in
循环,提取对象属性名的例子。
var obj = {
x: 1,
y: 2
};
var props = [];
var i = 0;
for (var p in obj) {
props[i++] = p
}
props // ['x', 'y']
对象obj
继承了toString
属性,该属性不会被for...in
循环遍历到,因为它默认是“不可遍历”的。
==hasOwnProperty==
判断某个属性是不是对象自身的属性(还是继承的?),返回真假。
var person = { name: '老张' };
for (var key in person) {
if (person.hasOwnProperty(key)) {
console.log(key);
}
}
// name
with语句
作用是操作同一个对象的多个属性时,提供一些书写的方便。
// 例一
var obj = {
p1: 1,
p2: 2,
};
with (obj) {
p1 = 4;
p2 = 5;
}
// 等同于
obj.p1 = 4;
obj.p2 = 5;
// 例二
with (document.links[0]){
console.log(href);
console.log(title);
console.log(style);
}
// 等同于
console.log(document.links[0].href);
console.log(document.links[0].title);
console.log(document.links[0].style);
弊端:绑定对象不明确。
可用一个临时变量代替with
with(obj1.obj2.obj3) {
console.log(p1 + p2);
}
// 可以写成
var temp = obj1.obj2.obj3;
console.log(temp.p1 + temp.p2);
函数
💡 函数若无返回值,会默认返回undefined,所以控制台总是能看到一个undefinedfunction fu(){console.log(666)}
看到的打印结果是:666
undefined
函数的声明有三种方式
-
function命令
function print(s) { console.log(s); }
-
函数表达式
var print = function(s) { console.log(s); };
-
Function 构造函数
var add = new Function( 'x', 'y', 'return x + y' ); // 等同于 function add(x, y) { return x + y; }
Function
构造函数接受三个参数,除了最后一个参数是add
函数的“函数体”,其他参数都是add
函数的参数。可以传递任意数量的参数给
Function
构造函数,只有最后一个参数会被当做函数体,如果只有一个参数,该参数就是函数体。var foo = new Function( 'return "hello world"' ); // 等同于 function foo() { return 'hello world'; }
方式1与方式2近乎等价,方式3甚少使用
function testf(){console.log(‘666’);}
testf //输出 f testf(){console.log(‘666’);}
函数的重复声明
同一个函数被多次声明,后面的声明会覆盖前面的声明。
第一等公民
JavaScript 语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同。凡是可以使用值的地方,就能使用函数。比如,可以把函数赋值给变量和对象的属性,也可以当作参数传入其他函数,或者作为函数的结果返回。函数只是一个可以执行的值,此外并无特殊之处。
==由于函数与其他数据类型地位平等,所以在 JavaScript 语言中又称函数为第一等公民。==
function add(x, y) {
return x + y;
}
// 将函数作为参数和返回值
function a(op){
return op;
}
a(add)(1, 1)
// 2
==函数运算次序从左到右==,上面代码中其实是(a(add))(1,1),不同于a(add(1,1)),不过此处结果一样。
函数名提升 hoisting
js引擎将函数名视同变量名,所以采用function
命令声明函数时,函数会像变量声明一样,被提升到代码头部。
function方式的函数提升,除了函数名,把函数的定义也给提升到了头部。
==函数声明会连同命名和函数体一起被提升至作用域顶部==
🔯经典操作
函数会首先被提升,然后再到变量,即函数会提升到变量之前(因为f在v前...function更牛逼)但是变量的声明var a只是定义并不会覆盖变量,不会将前面的a改为undefined。
函数提升更前原理解释
这个例子:
test2 //输出啥
var test2 = 666
function test2(){
console.log(678)
}
console.log(foo); //输出啥
function foo(){
console.log("函数声明");
}
var foo = "变量";
不能在非函数的代码块中声明函数,例如条件语句中不可声明函数
if (condition) {
function f() {}
}
f() // 不报错,但是由于函数声明提升,if语句失效了
函数的属性和方法
name属性
返回函数名
function f1() {}
f1.name // "f1"
var f2 = function () {};
f2.name // "f2",匿名函数返回的是变量名,具名函数则返回function关键字后的函数名
var f3 = function myName() {};
f3.name // 'myName' 注意:真正的函数名还是f3,myName这个名字只在函数体内部可用。
myName // 报错,Uncaught ReferenceError: myName is not defined
name
属性的一个用处,就是获取参数函数的名字。
函数test
内部通过name
属性,就可以知道传入的参数是什么函数。
var myFunc = function () {};
function test(f) {
console.log(f.name);
}
test(myFunc) // myFunc
length属性
函数的length
属性返回函数预期传入的参数个数,即函数定义之中的参数个数。
function f(a, b) {}
f.length // 2
上面代码定义了空函数f
,它的length
属性就是==定义时的参数个数==。
不管调用时输入了多少个参数,length
属性始终等于2。
length
属性提供了一种机制,判断定义时和调用时参数的差异,以便实现面向对象编程的”方法重载“(overload)。
toString()属性
函数的toString
方法返回一个字符串,内容是函数的源码。
function f() {
a();
b();
c();
}
f.toString()
// function f() {
// a();
// b();
// c();
// }
function f() {/*
这是一个
多行注释
*/}
f.toString()
// "function f(){/*
// 这是一个
// 多行注释
// */}"
利用这一点,可以变相实现多行字符串。
var multiline = function (fn) {
var arr = fn.toString().split('\n');
return arr.slice(1, arr.length - 1).join('\n');
};
function f() {/*
这是一个
多行注释
*/}
multiline(f);
// " 这是一个
// 多行注释"
函数作用域
定义
- 全局作用域,变量在整个程序中一直存在,所有地方都可以读取
- 函数作用域,变量只在函数内部存在
//局部变量
function f(){
var v = 1;
}
v // ReferenceError: v is not defined
var v = 1;
function f(){
var v = 2;
console.log(v);
}
f() // 2
v // 1
上面代码中,变量v
同时在函数的外部和内部有定义。在函数内部定义时,函数内部的局部变量v
覆盖了全局变量v
。
❗注意:对于var
命令来说,局部变量只能在函数内部声明,在其他区块中声明,一律都是全局变量。
if (true) {
var x = 5;
}
console.log(x); // 5
函数内部的变量提升
function foo(x) {
if (x > 100) {
var tmp = x - 100;
}
}
// 等同于
function foo(x) {
var tmp;
if (x > 100) {
tmp = x - 100;
};
}
函数本身的作用域 ㊙
函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。 函数执行时作用域绑定声明时的作用域。
函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。里面的能访问外面的,外面的不能访问里面的
==总结==
看最终要输出的变量名,先从函数声明时函数体内部找,找不到就往外一层找(从上到下,下面的赋值会把上面的覆盖掉,并不是往上找到第一个就对了,但切记不能找到函数调用位置之下,而是函数调用之上的这一层的最后的值才是对的。
看不懂找下面的犯糊涂和面试题看看加深理解。
var a = 1;
var x = function () {
console.log(a);
};
function f() {
var a = 2;
x();
}
f() // 1
上面代码中,函数x
是在函数f
的外部声明的,所以它的作用域绑定外层,内部变量a
不会到函数f
体内取值,所以输出1
,而不是2
。
==必须注意!==
函数f()内有与全局变量a同名的局部变量a
时,会优先使用自己的变量
,只不过第一次alert时未赋值
所以这个和同作用域下同名变量重复声明var a无效不同;函数内如果重复声明(var a)了函数外声明过的a,那a就真的成了undefined,可怕!
==犯糊涂==
var xyx = 1;
function fx(){console.log(xyx)}
var xyx = 2;
fx();
==作用域面试题==
http://book.jirengu.com/fe/%E...
虽然函数会提升到变量之前,但是并不会导致输出的xyx没定义过,因为fx()执行的时候xyx已经被赋值了,找位置只是找到所在的那一层,并不是认为函数在声明的时候当场执行导致xyx出错,观念要扭过来,然后到时再看下作用域链。
作用域相关资料【加精,难懂】
https://www.cnblogs.com/moqiu...
用作用域链来解释:从函数声明(创建)时的自身内部找,然后从上面开始找,而不是从调用时的上面开始找。
https://www.jianshu.com/p/858...
==易错==
容易犯错的一点是,如果函数A
调用函数B
,却没考虑到函数B
不会引用函数A
的内部变量。
var x = function () {
console.log(a);
};
function y(f) {
var a = 2;
f();
}
y(x)
// ReferenceError: a is not defined
上面代码将函数x
作为参数,传入函数y
。但是,函数x
是在函数y
体外声明的,作用域绑定外层,因此找不到函数y
的内部变量a
,导致报错。
function foo() {
var x = 1;
function bar() {
console.log(x);
}
var x = 3;
return bar;
}
var x = 2;
var f = foo();
f() // 1
上面代码中,函数foo
内部声明了一个函数bar
,bar
的作用域绑定foo
。当我们在foo
外部取出bar
执行时,变量x
指向的是foo
内部的x
,而不是foo
外部的x
。正是这种机制,构成了下文要讲解的“闭包”现象。
参数
参数的省略
function f(a, b) {
return a;
}
f(1, 2, 3) // 1
f(1) // 1
f() // undefined
f.length // 2
上面代码的函数f
定义了两个参数,但是运行时无论提供多少个参数(或者不提供参数),JavaScript 都不会报错。
==省略的参数的值就变为undefined
。==
需要注意的是,函数的length
属性与实际传入的参数个数无关,只反映函数预期传入的参数个数(定义时候的参数个数)。
可以省略靠后的参数,但没办法只省略靠后的参数。如果一定要省略靠前的参数,只有显式传入undefined
。
function f(a, b) {
return a;
}
f( , 1) // SyntaxError: Unexpected token ,(…)
f(undefined, 1) // undefined
💡传递方式
如果是原始类型的值(数值、字符串、布尔值),传递方式是传值传递(passes by value)。这意味着,在函数体内修改参数值,不会影响到函数外部。
var p = 2;
function f(p) {
p = 3;
}
f(p);
p // 2
But,如果函数参数是复合类型的值(数组、对象、其他函数),传递方式是传址传递(pass by reference)。也就是说,传入函数的原始值的地址,因此在函数内部修改参数,将会影响到原始值。
var obj = { p: 1 };
function f(o) {
o.p = 2;
}
f(obj);
obj.p // 2
上面代码中,传入函数f
的是参数对象obj
的地址。因此,在函数内部修改obj
的属性p
,会影响到原始值。
==特殊地,==如果函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值。
var obj = [1, 2, 3];
function f(o) {
o = [2, 3, 4];
}
f(obj);
obj // [1, 2, 3]
上面代码中,在函数f
内部,参数对象obj
被整个替换成另一个值。这时不会影响到原始值。这是因为,形式参数(o
)的值实际是参数obj
的地址,重新对o
赋值导致o
指向另一个地址,保存在原地址上的值当然不受影响。
同名参数
//有同名的参数,则取最后出现的那个值。
function f(a, a) {
console.log(a);
}
f(1, 2) // 2
f(1) // undefined
调用函数f
的时候,没有提供第二个参数,a
的取值就变成了undefined
。这时,如果要获得第一个a
的值,可以使用arguments
对象。如下:
function f(a, a) {
console.log(arguments[0]);
}
f(1) // 1
arguments对象
arguments
对象包含了函数运行时的所有参数,arguments[0]
就是第一个参数,arguments[1]
就是第二个参数,以此类推。这个对象==只有在函数体内部,才可以使用。==
var f = function (one) {
console.log(arguments[0]);
console.log(arguments[1]);
console.log(arguments[2]);
}
f(1, 2, 3)
// 1
// 2
// 3
正常模式下,arguments
对象可以在运行时修改。
var f = function(a, b) {
arguments[0] = 3;
arguments[1] = 2;
return a + b;
}
f(1, 1) // 5
上面代码中,函数f
调用时传入的参数,在函数内部被修改成3
和2
。
var f = function(a, b) {
'use strict'; // 开启严格模式
arguments[0] = 3; // 无效
arguments[1] = 2; // 无效
return a + b;
}
f(1, 1) // 2
上面代码中,函数体内是严格模式,这时修改arguments
对象就是无效的。
💡技巧:通过arguments
对象的length
属性,可以判断函数调用时到底带几个参数。
function f() {
return arguments.length;
}
f(1, 2, 3) // 3
f(1) // 1
f() // 0
arguments和数组的关系
虽然arguments
很像数组,但==它是一个对象==。数组专有的方法(比如slice
和forEach
),不能在arguments
对象上直接使用。
如果要让arguments
对象使用数组方法,真正的解决方法是将arguments
转为真正的数组。下面是两种常用的转换方法:slice
方法和逐一填入新数组。
var args = Array.prototype.slice.call(arguments);
// 或者
var args = [];
for (var i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
}
calee属性
arguments
对象带有一个callee
属性,返回它所对应的原函数。
var f = function () {
console.log(arguments.callee === f);
}
f() // true
可以通过arguments.callee
,达到调用函数自身的目的。这个属性在严格模式里面是禁用的,因此不建议使用。
函数的其他知识点
闭包
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。