《JavaScript语言入门教程》记录整理:运算符、语法和标准库
本系列基于阮一峰老师的《JavaScrip语言入门教程》或《JavaScript教程》记录整理,教程采用知识共享 署名-相同方式共享 3.0协议。这几乎是学习js最好的教程之一(去掉之一都不过分)
最好的教程而阮一峰老师又采用开源方式共享出来,之所以重新记录一遍,一是强迫自己重新认真读一遍学一遍;二是对其中知识点有个自己的记录,加深自己的理解;三是感谢这么好的教程,希望更多人阅读了解
运算符
算数运算符
- js提供了10种运算符
- 加法运算符:
x + y
- 减法运算符:
x - y
- 乘法运算符:
x * y
- 除法运算符:
x / y
- 指数运算符:
x ** y
- 余数运算符:
x % y
- 自增运算符:
++x
或者x++
- 自减运算符:
--x
或者x--
- 数值运算符:
+x
- 负数值运算符:
-x
- js中非数值可以相加,比如布尔值与数值相加,字符串相加用于连接两个字符串
true + true // 2
1 + true // 2
1 + 'a' // "1a"
false + 'a' // "falsea"
加法运算符是在运行时决定,到底是执行相加,还是执行连接。运算子的不同,导致了不同的语法行为,这种现象称为“重载”(overload)。
'3' + 4 + 5 // "345"
3 + 4 + '5' // "75"
加法运算符存在重载。减法、除法和乘法等运算符不会重载:所有运算子一律转为数值,再进行相应的数学运算。
- 对象的相加:运算子是对象时,会先转成原始类型的值,然后再相加。
对象默认转成原始类型的值是[object Object]
var obj = { p: 1 };
obj+5 // "[object Object]5"
对象转成原始类型的值,规则:
- 自动调用对象的
valueOf
方法。对象的valueOf
方法默认返回对象自身 - 再调用
toString
方法转为字符串。对象的toString
方法默认返回[object Object]
自定义valueOf
方法或toString
方法(同时改写两个方法时要小心),改变对象相加的结果
obj.valueOf() // {p: 1}
obj.valueOf().toString() // "[object Object]"
obj.valueOf=function () {
return 1;
}
obj+5 // 6
唯一的特例是,当运算子是Date
对象时,会优先执行toString
方法
var obj = new Date();
obj.valueOf = function () { return 1 };
obj.toString = function () { return 'hello' };
obj + 5 // "hello5"
- 余数运算符(
%
)返回前一个运算子被后一个运算子除所得的余数。结果的正负号由第一个运算子决定
-1 % 2 // -1
1 % -2 // 1
可以使用绝对值,获得负数的正确余数值
// 正确的写法
function isOdd(n) {
return Math.abs(n % 2) === 1;
}
isOdd(-5) // true
isOdd(-4) // false
- 自增和自减运算符是一元运算符,只有一个运算子。
运算之后,变量的值发生变化,这种效应叫做运算的副作用(
side effect
)。自增和自减运算符是仅有的两个具有副作用的运算符,其他运算符都不会改变变量的值。
自增/自减放在变量后面,会先返回变量操作前的值,再进行自增/自减操作
自增/自减放在变量之前,会先进行自增/自减操作,再返回变量操作后的值
- 数值运算符(
+
)的作用可以将任何值转为数值(与Number函数作用相同)
负数值运算符(-
),将一个值转为数值的负值
不会改变原始变量的值,而是返回新值
- 指数运算符(
**
)完成指数运算
指数运算符是右结合,而不是左结合。即多个指数运算符连用时,先进行最右边的计算。
// 相当于 2 ** (3 ** 2)
2 ** 3 ** 2 // 512
- 赋值运算符(
Assignment Operators
)用于给变量赋值。还有复合的赋值运算符,如x += y
、x -= y
比较运算符
- 比较运算符比较两个值的大小,并返回一个布尔值。js提供了8个比较运算符
>
大于运算符<
小于运算符<=
小于或等于运算符>=
大于或等于运算符==
相等运算符===
严格相等运算符!=
不相等运算符!==
严格不相等运算符
- 相等比较和非相等比较。
对于非相等的比较,算法是先看两个运算子是否都是字符串,如果是的,就按照字典顺序比较(实际上是比较 Unicode 码点);否则,将两个运算子都转成数值,再比较数值的大小。
- 相等运算符(
==
)比较两个值是否相等,严格相等运算符(===
)比较两个值是否为“同一个值”。
如果两个值不是同一类型,严格相等运算符
===
直接返回false,而相等运算符==
会将它们转换成同一个类型,再进行比较
- 严格相等运算符:类型不同返回false;同一类型的原始类型值,会比较两者的值是否相等;复合类型的值(对象、数组、函数)比较的是是否指向同一个地址;undefined和null与自身严格相等
两个对象的比较,严格相等运算符比较的是地址,而大于或小于运算符比较的是值
var obj1 = {};
var obj2 = {};
obj1 > obj2 // 比较的是值 false
obj1 < obj2 // 比较的是值 false
obj1 === obj2 // 比较的是地址 false
相等运算符比较是隐含了类型转换,建议最好只使用严格相等运算符(===
)。
布尔运算符
- 布尔运算符用于将表达式转为布尔值。一共有4个
- 取反运算符:
!
- 且运算符:
&&
- 或运算符:
||
- 三元运算符:
?:
- 取反运算符将布尔值变为相反值。两次取反就是将一个值转为布尔值的简便写法
- 且运算符
&&
常用于多个表达式的求值
且运算符
&&
运算规则是:如果第一个运算子的布尔值为true,则返回第二个运算子的值(注意是值,不是布尔值);如果第一个运算子的布尔值为false,则直接返回第一个运算子的值,且不再对第二个运算子求值。
&&
且运算可以用来取代if
语句
if (i) {
doSomething();
}
// 等价于
i && doSomething();
- 或运算符(
||
)也用于多个表达式的求值。
或运算符
||
的运算规则是:如果第一个运算子的布尔值为true
,则返回第一个运算子的值,且不再对第二个运算子求值;如果第一个运算子的布尔值为false
,则返回第二个运算子的值。
或运算符常用于为一个变量设置默认值。
function saveText(text) {
text = text || '';
// ...
}
// 或者写成
saveText(this.text || '')
- 且运算符和或运算符,这种通过第一个表达式(运算子)的值,控制是否运行第二个表达式(运算子)的机制,就称为“短路”(
short-cut
) - 三元条件运算符(
?:
)是js中唯一一个需要三个运算子的运算符
二进制位运算符
- 二进制位运算符用于直接对二进制位进行计算,一共有7个:
- 二进制或运算符(or):符号为
|
,表示若两个二进制位都为0,则结果为0,否则为1。 - 二进制与运算符(and):符号为
&
,表示若两个二进制位都为1,则结果为1,否则为0。 - 二进制否运算符(not):符号为
~
,表示对一个二进制位取反。 - 异或运算符(xor):符号为
^
,表示若两个二进制位不相同,则结果为1,否则为0。 - 左移运算符(left shift):符号为
<<
, - 右移运算符(right shift):符号为
>>
, - 头部补零的右移运算符(zero filled right shift):符号为
>>>
,
- 位运算符只对整数起作用,如果一个运算子不是整数,会自动转为整数后再执行。虽然在JavaScript内部,数值都是以64位浮点数的形式储存,但是做位运算的时候,是以32位带符号的整数进行运算的,并且返回值也是一个32位带符号的整数
利用这个特性,可以写出一个函数,将任意数值转为32位整数。
function toInt32(x) {
return x | 0;
}
- 位运算符可以用作设置对象属性的开关。(开关作用有些抽象,但很精巧)
假定某个对象有四个开关,每个开关都是一个变量。那么,可以设置一个四位的二进制数,它的每个位对应一个开关。A、B、C、D四个开关,每个开关占有一个二进制位
var FLAG_A = 1; // 0001
var FLAG_B = 2; // 0010
var FLAG_C = 4; // 0100
var FLAG_D = 8; // 1000
- 用二进制与运算,检查当前设置是否打开了指定开关
var flags = 5; // 二进制的0101
// 检验是否打开了开关C
if (flags & FLAG_C) { // 0101 & 0100 => 0100 => true
// ...
}
- 假设需要打开
A
、B
、D
三个开关,可以先构造一个掩码变量,然后通过二进制或运算掩码变量,可以确保打开这三个开关
var mask = FLAG_A | FLAG_B | FLAG_D;
// 0001 | 0010 | 1000 => 1011
flags = flags | mask; // 代表三个开关的二进制位都打开的变量
- 二进制与运算可以将当前设置中凡是与开关设置不一样的项,全部关闭
flags = flags & mask;
- 异或运算可以切换(
toggle
)当前设置,即第一次执行可以得到当前设置的相反值,再执行一次又得到原来的值。
flags = flags ^ mask;
- 二进制否运算可以翻转当前设置
flags = ~flags;
void和逗号运算符
- void运算符,执行一个表达式,然后不返回任何值,或者返回
undefined
void 0 // undefined
void(0) // undefined 推荐写法
void运算符的优先级很高,使用括号避免错误
var x = 3;
void (x = 5) //undefined
x // 5
- void运算符的主要用途是浏览器的书签工具(Bookmarklet),以及在超链接中插入代码防止网页跳转。
如下代码,点击链接后先执行onclick
,然后返回false
,所以浏览器不会跳转
<script>
function f() {
console.log('Hello World');
}
</script>
<a href="http://example.com" onclick="f(); return false;">点击</a>
void
运算符可以取代上面的写法:
<a href="javascript: void(f())">文字</a>
或者,实现点击链接提交表单,但不产生页面跳转
<a href="javascript: void(document.form.submit())">
提交
</a>
- 逗号运算符用于对两个表达式求值,并返回后一个表达式的值。
'a', 'b' // "b"
var x = 0;
var y = (x++, 10);
x // 1
y // 10
用途是:在返回一个值之前,进行一些辅助操作。
var value = (console.log('Hi!'), true);
// Hi!
value // true
运算顺序
- 运算符优先级别(
Operator Precedence
)高的先执行 - 圆括号
()
用来提高运算的优先级(它的优先级最高),即圆括号中的表达式会第一个运算
圆括号不是运算符,而是一种语法结构。它一共有两种用法:一种是把表达式放在圆括号之中,提升运算的优先级;另一种是跟在函数的后面,作用是调用函数。
函数放在圆括号中,会返回函数本身。圆括号紧跟在函数的后面,表示调用函数。
圆括号之中,只能放置表达式
- "左结合"(
left-to-right associativity
)运算符会先从左向右运算
"右结合"(right-to-left associativity
)运算符会先从右向左运算
js中赋值运算符(=
)、三元条件运算符(?:
)、指数运算符(**
)是"右结合"的
语法
数据类型的转换
- JavaScript 是一种动态类型语言,变量的类型无法在编译阶段确定,必须在运行时才能知道。而同时js的变量类型又可以随意改变,因此又属于弱类型语言
- JS中的运算符对数据类型有要求。因此常常发生类型自动转换
- 强制类型转换主要指使用
Number()
、String()
和Boolean()
手动将任意类型的值,分别转换成数字、字符串或者布尔值。 Number()
转换为数值。比parseInt函数严格
- 转换原始类型的值
// 数值:转换后还是原来的值
Number(324) // 324
// 字符串:如果可以被解析为数值,则转换为相应的数值
Number('324') // 324
// 字符串:如果不可以被解析为数值,返回 NaN
Number('324abc') // NaN
// 空字符串转为0
Number('') // 0
// 布尔值:true 转成 1,false 转成 0
Number(true) // 1
Number(false) // 0
// undefined:转成 NaN
Number(undefined) // NaN
// null:转成0
Number(null) // 0
// 忽略前后空格
Number('\t\v\r12.34\n') // 12.34
- 转换对象时,规则如下:
第一步,调用对象自身的valueOf
方法。如果返回原始类型的值,则直接对该值使用Number函数,不再进行后续步骤。
第二步,如果valueOf方法返回的还是对象,则改为调用对象自身的toString方法。如果toString方法返回原始类型的值,则对该值使用Number函数,不再进行后续步骤。
第三步,如果toString方法返回的是对象,就报错。
自定义valueOf
或toString
Number({
valueOf: function () {
return 2;
}
})
// 2
Number({
toString: function () {
return 3;
}
})
// 3
Number({
valueOf: function () {
return 2;
},
toString: function () {
return 3;
}
})
// 2
String()
转换字符串的规则如下:
-
原始类型的值
数值:相应的字符串。
字符串:原来的值。
布尔值:true-"true",false-"false"。
undefined:"undefined"。
null:"null"。 -
对象
String
参数如果是对象,返回一个类型字符串;如果是数组,返回该数组的字符串形式。
String({a: 1}) // "[object Object]"
String([1, 2, 3]) // "1,2,3"
转换规则如下:
第一步,先调用对象自身的toString方法。如果返回原始类型的值,则对该值使用String函数,不再进行以下步骤。
第二步,如果toString方法返回的是对象,再调用原对象的valueOf方法。如果valueOf方法返回原始类型的值,则对该值使用String函数,不再进行以下步骤。
第三步,如果valueOf方法返回的是对象,就报错。
String({
toString: function () {
return 3;
}
})
// "3"
String({
valueOf: function () {
return 2;
}
})
// "[object Object]"
String({
valueOf: function () {
return 2;
},
toString: function () {
return 3;
}
})
// "3"
Boolean()
转换为布尔值,规则简单,除了下面6个值结果为false,其余全部为true
undefined
、null
、0
(包含-0
和+0
)、NaN
、''
(空字符串)和false
所有对象(包括空对象)的转换结果都是true
,包括false
对应的布尔对象new Boolean(false)
也是true
- js中数据类型自动转换发生的情况:一、不同类型的数据相互运算时会自动转换。二、对非布尔值类型的数据求布尔值时。三、对非数值类型的值使用一元运算符(即
+
和-
)。转换时的规则是:预期什么类型的值,就调用该类型的转换函数。如果该位置既可以是字符串,又可以是数值,则默认转为数值 - JavaScript在预期为布尔值的地方(比如if语句的条件部分),会将非布尔值的参数自动转换为布尔值。系统内部会自动调用
Boolean
函数。
如下两个方法将一个表达式转为布尔值
// 写法一
expression ? true : false
// 写法二
!! expression
- 除了加法运算符(
+
)有可能把运算子转为字符串,其他运算符都会把运算子自动转成数值。
null
数值0
,undefined
数值NaN
错误处理机制
- JavaScript原生提供
Error
构造函数,所有抛出的错误都是这个构造函数的实例。当发生错误时,js引擎抛出Error
实例对象以后,整个程序就中断在发生错误的地方,不再往下执行。
var err = new Error('出错了');
err.message // "出错了"
Error
实例的属性:
message
:错误提示信息name
:错误名称(非标准属性)stack
:错误的堆栈(非标准属性)
function throwit() {
throw new Error('');
}
function catchit() {
try {
throwit();
} catch(e) {
console.log(e.stack); // print stack trace
}
}
catchit()
// Error
// at throwit (<anonymous>:2:9)
// at catchit (<anonymous>:7:5)
// at <anonymous>:1:1
Error
实例是最一般的错误类型,js还提供Error
的6个派生对象
SyntaxError
对象:解析代码时发生的语法错误ReferenceError
对象:引用一个不存在的变量时发生的错误。RangeError
对象:一个值超出有效范围时发生的错误。TypeError
对象:变量或参数不是预期类型时发生的错误。URIError
对象:URI
相关函数的参数不正确时抛出的错误。主要encodeURI()
、decodeURI()
、encodeURIComponent()
、decodeURIComponent()
、escape()
和unescape()
。EvalError
对象:已不再使用
- 自定义错误
function UserError(message) {
this.message = message || '默认信息';
this.name = 'UserError';
}
UserError.prototype = new Error();
UserError.prototype.constructor = UserError;
throw
语句:手动中断程序执行,抛出一个错误。
if (true) {
throw new Error('x 必须为正数');
}
// Uncaught Error: x 必须为正数
// at <anonymous>:2:9
throw
可以抛出任何类型的值
try...catch
结构用于对错误进行处理,选择是否往下执行。catch
代码块捕获错误后,程序不会中断。
try {
throw new Error('出错了!');
} catch (e) {
console.log(e.name + ": " + e.message);
console.log(e.stack);
}
// Error: 出错了!
// at <anonymous>:3:9
// ...
catch
代码块中加入判断语句,捕获不同类型的错误
try {
foo.bar();
} catch (e) {
if (e instanceof SyntaxError) {
console.log(e.name + ": " + e.message);
} else if (e instanceof RangeError) {
console.log(e.name + ": " + e.message);
}
// ...
}
try...catch...finally
结构中的finally
代码块,不管是否出现错误,都会在最后执行。
function cleansUp() {
try {
throw new Error('出错了……');
console.log('此行不会执行');
} finally {
console.log('完成清理工作');
}
}
finally
代码块前面即使有return
返回语句,依旧会执行完再返回。
function idle(x) {
try {
console.log(x);
return 'result';
} finally {
console.log('FINALLY');
}
}
idle('hello')
// hello
// FINALLY
如下说明:return
语句的执行在finally
代码之前,只是等到finally
执行完最终才返回
var count = 0;
function countUp() {
try {
return count;
} finally {
count++;
}
}
countUp()
// 0
count
// 1
finally
代码块的典型场景
openFile();
try {
writeFile(Data);
} catch(e) {
handleError(e);
} finally {
closeFile();
}
编程风格
- "编程风格"(
programming style
)指的是编写代码的样式规则。
你选择的,不是你喜欢的风格,而是一种能够清晰表达你的意图的风格 - 编程风格主要考虑的几点:缩进(
indent
)、区块(block
)、圆括号(parentheses
)、行尾的分号、变量声明、严格相等、语句的合并书写等 - 使用
{}
代码块时,js中要使用左大括号{
紧挨着语句在同一行中,不要换行写。这是因为JavaScript会自动添加句末的分号,从而产生一些难以察觉的错误。
block {
// ...
}
如下return
语句其实会变成两句,从而导致出问题
return
{
key: value
};
// 相当于
return;
{
key: value
};
// 正确写法
return {
key : value
};
- 行尾的分号:分号表示一条语句的结束。js允许省略。
有三种情况,语法规定不需要在结尾添加分号。如果添加,js引擎将分号解释为空语句
-
for
和while
循环
for ( ; ; ) {
} // 没有分号
while (true) {
} // 没有分号
但是do...while
要有分号
-
- 分支语句:
if
,switch
,try
- 分支语句:
if (true) {
} // 没有分号
switch () {
} // 没有分号
try {
} catch {
} // 没有分号
- 函数的声明语句
function f() {
} // 没有分号
函数表达式仍要使用分号
var f = function f() {
};
除了这三种情况,所有语句都应该使用分号。
在没有分号时JavaScript会自动添加,这种语法特性叫"分号的自动添加"(Automatic Semicolon Insertion
,简称ASI
)
但是,如果下一行的开始可以与本行的结尾连在一起解释,JavaScript就不会自动添加分号。
而是否自动添加分号无法预测,很有可能导致额外的错误。
一行的起首"自增"(++)或"自减"(--),则前面会自动添加分号
不应该省略结尾的分号,还有一个原因。有些JavaScript代码压缩器(
uglifier
)不会自动添加分号,因此遇到没有分号的结尾,就会让代码保持原状,而不是压缩成一行,使得压缩无法得到最优的结果。另外,不写结尾的分号,可能会导致脚本合并出错。所以,有的代码库在第一行语句开始前,会加上一个分号。可以避免与其他脚本合并时,前面的脚本最后一行语句没有分号,导致运行出错的问题。
;var a = 1; // ...
- 避免全局变量的使用,如果必须使用,考虑大写字母表示
- 变量声明,由于存在变量提升,许多语句会导致产生全局变量(比如
for
循环中)。
所有函数都应该在使用之前定义。函数内部的变量声明,都应该放在函数的头部。
- 建议只使用严格相等运算符(
===
) switch...case
结构可以用对象结构代替
switch...case
结构类似于goto
语句,容易造成程序流程的混乱,使得代码结构混乱不堪,不符合面向对象编程的原则。
console对象和控制台
console
对象是JavaScript的原生对象,可以输出各种信息到控制台console
的常见用途:调试程序,显示网页代码运行时的错误信息;提供了一个命令行接口,用来与网页代码互动。- 开发者工具的几个面板。
Elements
:查看网页的 HTML 源码和 CSS 代码。Resources
:查看网页加载的各种资源文件(比如代码文件、字体文件 CSS 文件等),以及在硬盘上创建的各种内容(比如本地缓存、Cookie、Local Storage等)。Network
:查看网页的 HTTP 通信情况。Sources
:查看网页加载的脚本源码,可进行断点debug。Timeline
:查看各种网页行为随时间变化的情况。Performance
:查看网页的性能情况,比如 CPU 和内存消耗。Console
:即控制台,用来运行js命令,和页面中js代码console方法的输出。
console.log()
,console.info()
,console.debug()
console.warn(),console.error()
console.table()
console.count()
debugger
语句主要用于除错,作用是设置断点。
标准库
下面基本都是js原生对象的介绍,里面许多属性和方法仅了解一下即可,有需要时再查询使用
Object对象
- JavaScript原生提供
Object
对象 - JavaScript的所有其他对象都继承自
Object
对象,都是Object
的实例。 Object
对象的原生方法分成两类:Object
本身的方法("静态方法")与Object
的实例方法。
Object
对象本身的方法:直接定义在Object
对象上的方法Object
的实例方法:定义在Object
原型对象Object.prototype
上的方法。它可以被Object
实例直接使用。
// 本身的方法
Object.selfPrint = function (o) { console.log(o) };
// 实例方法
Object.prototype.print = function () {
console.log(this);
};
var obj = new Object();
obj.print() // Object
Object
本身是一个函数,可以当作工具方法使用,将任意值转为对象。保证某个值一定是对象。Object
方法无参数或为undefined
、null
,返回一个空对象
var obj = Object();
// 等同于
var obj = Object(undefined);
var obj = Object(null);
obj instanceof Object // true
参数是原始类型,将原始类型的值转换为对应的包装对象的实例
参数是一个对象,则返回该对象(不进行转换)
var arr = [];
var obj = Object(arr); // 返回原数组
obj === arr // true
var value = {};
var obj = Object(value) // 返回原对象
obj === value // true
var fn = function () {};
var obj = Object(fn); // 返回原函数
obj === fn // true
- 判断变量是否为对象
function isObject(value) {
return value === Object(value);
}
isObject([]) // true
isObject(true) // false
instanceof
运算符验证一个对象是否为指定的构造函数的实例Object
构造函数用来生成新对象
var obj = new Object();
// 等价于
var obj = {};
Object
构造函数与工具方法类似。如果参数是一个对象,则直接返回该对象;如果是一个原始类型的值,则返回该值对应的包装对象- Object 的静态方法
Object.keys()
,Object.getOwnPropertyNames()
遍历对象的属性。两者都返回对象自身的(而不是继承的)所有属性名组成的数组。Object.keys
方法只返回可枚举的属性;Object.getOwnPropertyNames
还返回不可枚举的属性名。
通常使用Object.keys
遍历对象属性
计算对象属性的个数
var obj = {
p1: 123,
p2: 456
};
Object.keys(obj).length // 2
Object.getOwnPropertyNames(obj).length // 2
Object
实例对象的方法:
Object.prototype.valueOf()
:返回当前对象对应的值,默认情况下返回对象本身。Object.prototype.toString()
:返回当前对象对应的字符串形式,默认返回类型字符串。Object.prototype.toLocaleString()
:返回当前对象对应的本地字符串形式。Object.prototype.hasOwnProperty()
:判断某个属性是否为当前对象自身的属性,还是继承自原型对象的属性。Object.prototype.isPrototypeOf()
:判断当前对象是否为另一个对象的原型。Object.prototype.propertyIsEnumerable()
:判断某个属性是否可枚举。
- 数组、字符串、函数、Date对象都自定义了
toString
方法,覆盖了Object.prototype.toString
方法。
[1, 2, 3].toString() // "1,2,3"
'123'.toString() // "123"
(function () {
return 123;
}).toString()
// "function () {
// return 123;
// }"
(new Date()).toString()
// "Fri Jul 31 2020 21:24:16 GMT+0800 (中国标准时间)"
- 判断数据类型
关于如何正确的判断数据类型,由于typeof
仅能准确返回数值、字符串、布尔值、undefined的类型,其他返回object。所以无法借助它准确判断类型;而instanceof
对于继承的对象,除了判断当前对象实例时返回true
,判断继承的上级对象实例时也会返回true
,并且只能判断是否是某个对象的实例,无法判断基本类型。
因此最准确的办法是利用 Object.prototype.toString
方法返回对象的类型字符串 这一特点,判断一个值的类型
如下,空对象的toString
方法,返回字符串object Object
,第二个Object
表示当前值的构造函数。
var obj = {};
obj.toString() // "[object Object]"
Object.prototype.toString.call(value) // 对value这个值调用Object.prototype.toString方法
Object.prototype.toString
可以确认一个值是什么类型。如下,实现比typeof
运算符更准确的类型判断函数
var type = function (o){
var s = Object.prototype.toString.call(o);
return s.match(/\[object (.*?)\]/)[1].toLowerCase();
};
type({}); // "object"
type([]); // "array"
type(5); // "number"
type(null); // "null"
type(); // "undefined"
type(/abcd/); // "regex"
type(new Date()); // "date"
实现判断某种类型的方法:
var type = function (o){
var s = Object.prototype.toString.call(o);
return s.match(/\[object (.*?)\]/)[1].toLowerCase();
};
['Null',
'Undefined',
'Object',
'Array',
'String',
'Number',
'Boolean',
'Function',
'RegExp'
].forEach(function (t) {
type['is' + t] = function (o) {
return type(o) === t.toLowerCase();
};
});
type.isObject({}) // true
type.isNumber(NaN) // true
type.isRegExp(/abc/) // true
toLocaleString()
用来实现自定义的本地字符串。如Array.prototype.toLocaleString()
、
Number.prototype.toLocaleString()
、Date.prototype.toLocaleString()
等对象自定义这个方法
属性描述对象
- JS提供了叫做"属性描述对象"(
attributes object
)的内部数据结构,用来描述对象的属性,控制它的行为,比如该属性是否可写、可遍历等。 - 每个属性都有自己对应的属性描述对象,保存该属性的一些元信息。
- 如下为属性描述对象的例子:
{
value: 123, // 属性的属性值 默认undefined
writable: false, // 属性值(value)是否可改变(可写) 默认true
enumerable: true, // 属性是否可遍历,默认true
configurable: false,// 属性的可配置性,默认true 控制属性描述对象的可写性
get: undefined, // get该属性的取值函数(getter),默认undefined
set: undefined // set该属性的存值函数(setter),默认undefined。
}
定义了取值函数
get
(或存值函数set
),就不能将writable
属性设为true,或者同时定义value属性,否则会报错。
Object.getOwnPropertyDescriptor()
获取属性描述对象
var obj = { p: 'a' };
Object.getOwnPropertyDescriptor(obj, 'p')
// Object { value: "a",
// writable: true,
// enumerable: true,
// configurable: true
// }
Object.defineProperty()
通过属性描述对象,定义或修改属性,并返回修改后的对象。
Object.defineProperty(object, propertyName, attributesObject)
参数:
- object:属性所在的对象
- propertyName:字符串,属性名
- attributesObject:属性描述对象
Object.defineProperties()
可以一次定义多个属性
JSON.stringify
方法会排除enumerable
为false的属性,有时可以利用这一点。如果对象的JSON
格式输出要排除某些属性,可以把这些属性的enumerable
设为false。- 存取器(
accessor
,set-setter,get-getter)是另外定义属性的方式,定义存取器后,将会执行对应的函数。
除了defineProperty
方法中通过属性描述对象定义存取器,还提供如下的写法(且这种写法configurable
和enumerable
都为true,是可遍历的属性。更常用)
var obj = {
get p() {
return 'getter';
},
set p(value) {
console.log('setter: ' + value);
}
};
存取器常用于:属性的值依赖对象内部数据的场合。
var obj ={
$n : 5,
get next() { return this.$n++ },
set next(n) {
if (n >= this.$n) this.$n = n;
else throw new Error('新的值必须大于等于当前值');
}
};
obj.next // 5
obj.next = 10;
obj.next // 10
obj.next = 5;
// Uncaught Error: 新的值必须大于当前值
- 对象的拷贝:
由于对象是引用类型,数据存放在堆中,栈中值存放对象的地址。默认值类型的赋值是复制给另一个变量;但引用类型的赋值是直接将引用地址复制给另一个变量,赋值引用就是常说的浅拷贝(浅拷贝的对象共用一个内存地址)。而深拷贝指的是将引用类型的数据也完全复制一份给新的变量。
对象深拷贝的基本原理就是:通过遍历对象的属性,然后将属性和递归至不是对象的属性值重新赋值为另一个对象,如果属性值是对象,则递归执行当前函数。
- 方法一。 如下,缺点不能深拷贝
function
,对象存取器属性拷贝出来的是一个值
var DeepCopy = function dc(obj) {
if (obj===null) {
return obj;
}
else if (typeof obj === 'object') {
if (obj instanceof Array) {
var newArr = [], i, len = obj.length;
for (i = 0; i < len; i++) {
newArr[i] = dc(obj[i]);
}
return newArr;
} else {
var newObj = {};
for (var name in obj) {
newObj[name] = dc(obj[name]);
}
return newObj;
}
}
// 'number' 'string' 'boolean' undefined null
return obj;
}
var objFunction=function(){
//
}
var obj0={
p1:1,
get p2(){
return this.p1;
},
p3:objFunction
}
var obj1=DeepCopy(obj0);
// {p1: 1, p2: 1, p3: ƒ}
// p1: 1
// p2: 1
// p3: ƒ ()
obj1.p3===obj0.p3 // true
- 方法二。使用
defineProperty
设定属性描述器,完成拷贝属性,可实现拷贝对象存取器属性。但是此时复制的存取器属性函数属于浅拷贝
var DeepCopy = function dc(obj) {
if (obj===null) {
return obj;
}
else if(typeof obj === 'object'){
if (obj instanceof Array) {
var newArr = [], i, len = obj.length;
for (i = 0; i < len; i++) {
newArr[i] = dc(obj[i]);
}
return newArr;
} else {
var newObj = {};
for (var name in obj) {
if (obj.hasOwnProperty(name)) {
Object.defineProperty(
newObj,
name,
Object.getOwnPropertyDescriptor(obj, name)
);
}
}
return newObj;
}
}
return obj;
}
- 方法三。如下,利用
new Function
构造函数实现函数function的深拷贝。这也是处理js深拷贝最全的方法了,
var DeepCopy = function dc(obj) {
if (obj===null) {
return obj;
}
else if (typeof obj === 'object') {
if (obj instanceof Array) {
var newArr = [], i, len = obj.length;
for (i = 0; i < len; i++) {
newArr[i] = dc(obj[i]);
}
return newArr;
} else {
var newObj = {};
for (var name in obj) {
if (obj.hasOwnProperty(name)) {
//newObj[name] = dc(obj[name]);
if(typeof obj[name] === 'function'){
newObj[name] = dc(obj[name]);
}
else{
Object.defineProperty(
newObj,
name,
Object.getOwnPropertyDescriptor(obj, name)
);
}
}
}
return newObj;
}
}
else if(typeof obj === 'function'){
// var funStr="var f="+obj.toString()+";"
// return new Function(funStr+"return f;");
return new Function("return "+obj+";");
}
return obj;
}
obj1=DeepCopy(obj0);
// {p1: 1, p3: ƒ}p1: 1p2: (...)p3: ƒ anonymous( )get p2: ƒ p2()__proto__: Object
obj1.p3===obj0.p3 // false
obj1.p2===obj0.p2 // true
-
方法四。对于存取器属性函数的深拷贝,可以通过
getOwnPropertyDescriptor
获取的属性描述器对象,判断其get和set属性,完成其函数的深拷贝 -
方法五。还有一个简便的方法,使用
JSON.stringfy()
或JSON.parse()
序列化为json字符串然后解析为js对象,实现一个对象的深拷贝。但是它存在一个致命的问题,就是自定义的函数无法拷贝(JSON.stringfy()
方法无法将函数值转为json字符串。json无法表示函数类型)
var objFunction=function(){
//
}
var obj0={
p1:1,
get p2(){
return this.p1;
},
p3:objFunction,
p4:{
p5:5
}
}
var newObj = JSON.parse(JSON.stringify(obj0));
newObj
// {p1: 1, p2: 1, p4: {…}}
// p1: 1
// p2: 1
// p4:
// p5: 5
以上对象拷贝的都是可遍历属性,且可能改变不可写的属性为可写。最最重要的是,新对象和旧对象的原型对象obj.prototype
各自独立
ES6中实现对象复制的方式:比如Object.assign
(浅拷贝)、展开操作符…
(浅拷贝)
另:Array的slice
和concat
等方法不改变原数组,但是返回的也是浅拷贝了的新数组
另:$.extend
方法的第一个参数给bool值表示是否深拷贝:jQuery.extend( [deep ], target, object1 [, objectN ] )
- 控制对象状态
Object.preventExtensions
方法:使一个对象无法再添加新的属性Object.isExtensible
方法检查一个对象是否使用了Object.preventExtensions
方法。检查是否可以为一个对象添加属性。Object.seal
方法使得一个对象既无法添加新属性,也无法删除旧属性。Object.isSealed()
Object.freeze
方法使一个对象变成常量。无法添加新属性、无法删除旧属性、也无法改变属性的值。Object.isFrozen()
上面三个方法锁定对象的可写性有一个漏洞:可以通过改变原型对象,来为对象增加属性。解决方案是原型也冻结住。另外一个局限是,如果属性值是对象,这些方法只能冻结属性指向的对象,而不能冻结对象本身的内容。
Array 对象
Array
是JavaScript的原生对象,也是一个构造函数,用来生成新数组。
var arr = new Array(2); // 等同于 var arr = Array(2);
arr.length // 2
arr // [ empty x 2 ]
Array()
构造函数有很大的缺陷,不同的参数生成的结果会不一样。因此建议使用数组字面量的方式
// 不建议的方式
var arr = new Array(1, 2);
// 推荐
var arr = [1, 2];
Array.isArray()
静态方法,判断是否是数组
var arr = [1, 2, 3];
typeof arr // "object"
Array.isArray(arr) // true
- 数组对象的实例方法:
valueOf()
返回数组本身toString()
返回数组的字符串形式push()
在数组的末端添加一个或多个元素,返回添加后的数组长度——(在数组末尾压入元素)。该方法改变原数组。pop()
删除数组的最后一个元素,并返回该元素——(弹出最后一个元素)。该方法改变原数组。
push
和pop
结合使用,可构成"后进先出"的栈结构(stack
)。
var arr = [];
arr.push(1, 2);
arr.push(3);
arr.pop();
arr // [1, 2]
shift()
删除数组的第一个元素,并返回该元素——(弹出第一个元素)。该方法改变原数组。
shift()
方法可以遍历并清空一个数组。
var list = [1, 2, 3, 4];
while (list.length) {
console.log(list.shift());
}
list // []
push()
和shift()
结合使用,就构成了"先进先出"的队列结构(queue
)。
unshift()
在数组的第一个位置添加元素,并返回添加后的数组长度——(数组头部压入一个元素)。该方法会改变原数组。
var arr = [ 'c', 'd' ];
arr.unshift('a', 'b') // 4
arr // [ 'a', 'b', 'c', 'd' ]
join()
以指定参数作为分隔符,将所有数组成员连接为一个字符串返回。默认用逗号分隔。
var a = [1, 2, 3, 4,undefined, null];
a.join(' ') // '1 2 3 4 '
a.join(' | ') // "1 | 2 | 3 | 4 | | "
a.join() // "1,2,3,4,,"
undefined或null或空位被转为空字符串
通过call
方法,join也可以用于字符串或类似数组的对象
concat()
用于多个数组的合并。将新数组的成员,添加到原数组成员的后部,并返回一个新数组,原数组不变。
['hello'].concat(['world'])
// ["hello", "world"]
['hello'].concat(['world'], ['!'])
// ["hello", "world", "!"]
[].concat({a: 1}, {b: 2})
// [{ a: 1 }, { b: 2 }]
concat连接的数组中有对象时,返回的浅拷贝
reverse()
翻转数组,用于颠倒排列数组元素,返回改变后的数组。该方法将改变原数组。slice()
用于提取数组的一部分,返回一个新数组。原数组不变。
左闭右开,返回结果不包含end位置的元素。
arr.slice(start, end);
省略第二个参数,会一直返回数组最后的成员;或省略全部参数,返回元素组;第一个参数大于等于数组长度,或者第二个参数小于第一个参数,则返回空数组。
slice()
一个重要应用,是将类似数组的对象转为真正的数组:Array.prototype.slice.call(likeArrayObj)
。
splice()
用于删除原数组的一部分成员,并可以在删除的位置添加新的数组成员,返回值是被删除的元素。该方法会改变原数组。
参数为起始位置、删除的元素个数,添加到删除位置的新元素
arr.splice(start, count, addElement1, addElement2, ...);
第二个参数设为0,可实现插入元素
var a = [1, 1, 1];
a.splice(1, 0, 2) // []
a // [1, 2, 1, 1]
只提供第一个参数,将"剪切"到数组末尾
sort()
对数组成员进行排序,默认按照字典顺序排序。原数组将被改变。
['d', 'c', 'b', 'a'].sort()
// ['a', 'b', 'c', 'd']
[4, 3, 2, 1].sort()
// [1, 2, 3, 4]
[11, 101].sort()
// [101, 11]
[10111, 1101, 111].sort()
// [10111, 1101, 111]
通过传入一个函数,可以让sort方法按照自定义方式排序
[10111, 1101, 111].sort(function (a, b) {
return a - b;
})
// [111, 1101, 10111]
[
{ name: "张三", age: 30 },
{ name: "李四", age: 24 },
{ name: "王五", age: 28 }
].sort(function (o1, o2) {
return o1.age - o2.age;
})
// [
// { name: "李四", age: 24 },
// { name: "王五", age: 28 },
// { name: "张三", age: 30 }
// ]
sort
参数函数接受两个参数,表示进行比较的两个数组成员。如果函数的返回值大于0,表示第一个成员排在第二个成员后面;如果函数的返回值小于等于0,则第一个元素排在第二个元素前面。
自定义的排序函数应该返回数值
map()
将数组的所有成员依次传入参数函数,然后把每一次的执行结果组成一个新数组返回。元素组不变
var numbers = [1, 2, 3];
numbers.map(function (n) {
return n + 1;
});
// [2, 3, 4]
numbers // [1, 2, 3]
map
参数函数的三个参数:当前成员、当前位置和数组本身。
[1, 2, 3].map(function(elem, index, arr) {
return elem * index;
});
// [0, 2, 6]
map
的第二个参数,用来绑定回调函数内部的this
变量
forEach
与map
相似,对数组的所有成员依次执行参数函数,但不返回值。
如果数组遍历是为了得到返回值,可以使用map方法,否则使用forEach方法。
forEach方法无法中断执行。如果想要中断,可使用for循环、或some
、every
方法。
some()
,every()
方法类似"断言"(assert
),返回布尔值,表示数组成员是否符合某种条件
some方法是只要一个成员的返回值是true,则整个some方法的返回值就是true,否则返回false。
every方法是所有成员的返回值都是true,整个every方法才返回true,否则返回false。
借助这一点,可以循环执行数组每个元素时,some方法的参数函数中判断某个条件然后返回true,every方法的参数函数中判断某个条件然后返回false,即可起到类似for循环中break中断的作用;
var arr = [1, 2, 3, 4, 5];
arr.some(function (elem, index, arr) {
console.log(elem); //执行操作
return elem >= 3;
});
// 1
// 2
// 3
// true
arr.every(function (elem, index, arr) {
if(elem<=3){
console.log(elem);
return true;
}
else{
return false;
}
});
// 1
// 2
// 3
// false
对于空数组,some方法返回false,every方法返回true,回调函数都不会执行
filter()
过滤数组成员,满足条件的成员组成一个新数组返回——即filter的参数函数返回true的成员保留下来组成新数组。不会改变原数组。
参数函数的三个参数:当前成员,当前位置和整个数组。
[1, 2, 3, 4, 5].filter(function (elem) {
return (elem > 3);
})
[1, 2, 3, 4, 5].filter(function (elem, index, arr) {
return index % 2 === 0;
});
reduce()
、reduceRight()
依次处理数组的每个成员,最终累计为一个值。处理的是上一次累计值和当前元素执行结果的累计值。区别是,reduce
是从左到右处理(从第一个成员到最后一个成员),reduceRight
则是从右到左(从最后一个成员到第一个成员)。
[1, 2, 3, 4, 5].reduce(function (a, b) {
console.log("上一次的累计值:"+a, "当前值:"+b);
return a + b;
})
// 上一次的累计值:1 当前值:2
// 上一次的累计值:3 当前值:3
// 上一次的累计值:6 当前值:4
// 上一次的累计值:10 当前值:5
// 15
第一次执行时,累计值a是数组的第一个元素,之后就是累计值和元素值
其参数函数可接受四个变量:累积变量,默认为数组的第一个成员;当前变量,默认为数组的第二个成员;当前位置(从0开始);原数组。前两个必须
reduce
和reduceRight
的第二个参数可指定执行时的初始值
[1, 2, 3, 4, 5].reduce(function (a, b) {
console.log("上一次的累计值:"+a, "当前值:"+b);
return a + b;
},10)
// 上一次的累计值:10 当前值:1
// 上一次的累计值:11 当前值:2
// 上一次的累计值:13 当前值:3
// 上一次的累计值:16 当前值:4
// 上一次的累计值:20 当前值:5
// 25
空数组执行reduce
或reduceRight
时会报错,可指定第二个参数初始值解决
借助reduce
(或reduceRight
)可以实现一些遍历操作,比如找出字符长度最大的数组元素
function findLongest(entries) {
return entries.reduce(function (longest, entry) {
return entry.length > longest.length ? entry : longest;
}, '');
}
findLongest(['aaa', 'bb', 'c']) // "aaa"
indexOf()
返回给定元素在数组中第一次出现的位置,没有则返回-1
。第二个参数表示搜索开始的位置lastIndexOf()
返回给定元素在数组中最后一次出现的位置,没有则返回-1
。
- 链式调用,如果数组方法返回的还是数组,就可以接着调用数组方法,实现链式调用
var users = [
{name: 'tom', email: 'tom@example.com'},
{name: 'peter', email: 'peter@example.com'}
];
users.map(function (user) {
return user.email;
})
.filter(function (email) {
return /^t/.test(email);
})
.forEach(function (email) {
console.log(email);
});
// "tom@example.com"
包装对象
- js的三种原始类型的值——数值、字符串、布尔值——在一定条件下会自动转为对象,这就是原始类型的"包装对象"(
wrapper
) - "包装对象"指的是与数值、字符串、布尔值分别相对应的
Number
、String
、Boolean
三个原生对象。这三个原生对象可以把原始类型的值变成(包装成)对象。
var v1 = new Number(123);
var v2 = new String('abc');
var v3 = new Boolean(true);
typeof v1 // "object"
typeof v2 // "object"
typeof v3 // "object"
v1 === 123 // false
v2 === 'abc' // false
v3 === true // false
- 包装对象的设计目的:首先,使得"对象"这种类型可以覆盖JavaScript所有的值,整门语言有一个通用的数据模型。其次,使得原始类型的值也有办法调用自己的方法。
Number
、String
和Boolean
作为普通函数调用时用以类型转换,将任意类型的值转为数值、字符串和布尔值等原始类型的值;作为构造函数使用(带有new
)时,将原始类型的值转为对象- 包装对象继承了
Object
对象的valueOf()
——返回包装对象实例对应的原始类型的值、toString()
——返回对应的字符串形式方法 - 原始类型与实例对象的自动转换:有时,原始类型的值会自动当作包装对象调用,即调用包装对象的属性和方法。JavaScript 引擎会自动将原始类型的值转为包装对象实例,并在使用后立刻销毁实例。
比如字符串调用length
属性:
'abc'.length // 3
abc
是一个字符串,本身不是对象,不能调用length
属性。JavaScript引擎自动将其转为包装对象,在这个对象上调用length
属性。调用结束后,这个临时对象就会被销毁。这就叫原始类型与实例对象的自动转换。
自动转换生成的包装对象是只读的,无法修改。所以,字符串无法添加新属性。同时调用结束后,包装实例会自动销毁,所以每次调用其实都是一个新的包装对象。
var s = 'Hello World';
s.x = 123;
s.x // undefined
如果要为字符串添加属性,只有在它的原型对象String.prototype
上定义
- 可以在包装对象的原型对象
prototype
上添加自定义方法或属性
Boolean对象
- 通过
valueOf()
获取包装对象对应的原始类型值
new Boolean(false).valueOf()
Number对象
- Number对象的静态属性:
Number.POSITIVE_INFINITY
:正的无限,指向Infinity
。Number.NEGATIVE_INFINITY
:负的无限,指向-Infinity
。Number.NaN
:表示非数值,指向NaN
Number.MIN_VALUE
:表示最小正数(即最接近0的正数,在64位浮点数体系中为5e-324),相应的,最接近0的负数为-Number.MIN_VALUE
。Number.MAX_VALUE
:表示最大正数Number.MAX_SAFE_INTEGER
:表示能够精确表示的最大整数,即9007199254740991
。Number.MIN_SAFE_INTEGER
:表示能够精确表示的最小整数,即-9007199254740991
。
- 实例方法
Number.prototype.toString()
,用于将一个数值转为字符串形式。该方法可以接受一个参数,表示输出的进制
(10).toString() // "10"
(10).toString(2) // "1010"
(10).toString(8) // "12"
(10).toString(16) // "a"
调用时,数值必须用括号括起来,否则js引擎会把.
解读为小数点,从而混淆。任何不至于误读的写法都可以
10.toString(2)
// SyntaxError: Unexpected token ILLEGAL
10.5.toString() // "10.5"
10.5.toString(2) // "1010.1"
10.5.toString(8) // "12.4"
10.5.toString(16) // "a.8"
可使用方括号调用
10['toString'](2) // "1010"
如果想将其他进制的数转为十进制,使用parseInt
Number.prototype.toFixed()
将一个数转为指定位数的小数,然后返回个这小数对应的字符串。
(10).toFixed(2) // "10.00"
10.005.toFixed(2) // "10.01"
由于浮点数的原因,js中小数5的四舍五入是不确定的,使用的时候必须小心。
-
Number.prototype.toExponential()
将一个数转为科学计数法形式 -
Number.prototype.toLocaleString()
接受地区码作为参数,返回当前数字在该地区的当地书写形式。
(123).toLocaleString('zh-Hans-CN-u-nu-hanidec')
// "一二三"
toLocaleString()
第二个参数是配置对象,可以定制返回的字符串。比如style属性指定输出样式,默认值decimal
(十进制形式),还可取值percent
(百分比)、currency
(货币格式)
(123).toLocaleString('zh-Hans-CN', { style: 'percent' })
// "12,300%"
(123).toLocaleString('zh-Hans-CN', { style: 'currency', currency: 'CNY' })
// "¥123.00"
(123).toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })
// "123,00 €"
(123).toLocaleString('en-US', { style: 'currency', currency: 'USD' })
// "$123.00"
Number.prototype
对象上可以自定义方法
Number.prototype.add = function (x) {
return this + x;
};
Number.prototype.subtract = function (x) {
return this - x;
};
(8).add(2).subtract(4) // 6
String对象
- 静态方法
String.fromCharCode()
返回Unicode码点组成的字符串
Unicode码点不能大于0xFFFF
,码点大于0xFFFF
的字符占用四个字节,而JavaScript默认支持的是两个字节的字符。比如0x20BB7
需要拆成两个字符来写
String.fromCharCode(0xD842, 0xDFB7) // "𠮷"
- 实例属性
String.prototype.length
返回字符串长度 - 实例方法
String.prototype.charAt()
返回指定位置的字符。和字符串的数组下标等同String.prototype.concat()
连接两个字符串slice()
用于从原字符串取出子字符串并返回,不改变原字符串。slice(start,end)
左闭右开。省略第二个参数会一直取到字符串结束substring()
从原字符串取出子字符串并返回,不改变原字符串,和slice
很相像。但substring()
第一个参数大于第二个参数时取得结果是更换两者位置的结果,且负数会被转为0,由于这些反直觉的行为,因此推荐使用slice
substr()
从原字符串取出子字符串并返回,不改变原字符串。第一个参数是字符串的开始位置,第二个参数是长度。省略第二个参数一直取到结尾;第二个参数为负值会被转为0
'JavaScript'.substr(4, 6) // "Script"
'JavaScript'.substr(4) // "Script"
'JavaScript'.substr(-6) // "Script"
'JavaScript'.substr(4, -1) // ""
trim()
去除字符串两端的空格,返回新字符串。不改变原字符串。indexOf()
返回一个字符串在另一个字符串中第一次出现的位置。不匹配返回-1。第二个参数表示查找的起始位置。lastIndexOf()
从尾部开始匹配toLowerCase()
将一个字符串全部转为小写,toUpperCase()
全部转为大写。均不改变原字符串match()
查找原字符串是否匹配某个子字符串,返回匹配的第一个字符串组成的数组。没有匹配返回null。返回的数组包含index属性(匹配字符串的开始位置)和input属性(原始字符串)
var matches = 'cat, bat, sat, fat'.match('at');
matches.index // 1
matches.input // "cat, bat, sat, fat"
match()
参数可以是正则表达式
search()
返回匹配字符串的第一个位置。无匹配返回-1。参数可以是正则表达式replace()
替换匹配的子字符串,一般情况下只替换第一个匹配(除非使用带有g
修饰符的正则表达式)
'aaa'.replace('a', 'b') // "baa"
split()
根据参数分割字符串,返回分割后子字符串组成的数组。分割参数为空字符串,则分割每个字符;如果省略参数,返回原字符串组成的数组
'a|b|c'.split('|') // ["a", "b", "c"]
'a|b|c'.split('') // ["a", "|", "b", "|", "c"]
'a|b|c'.split() // ["a|b|c"]
'a||c'.split('|') // ['a', '', 'c']
'|b|c'.split('|') // ["", "b", "c"]
'a|b|'.split('|') // ["a", "b", ""]
split第二个参数表示返回数组的(最大)长度
localeCompare()
比较两个字符串
Math对象
Math
提供各种数学功能。该对象不是构造函数,不能生成实例,必须在Math对象上调用属性和方法。- 静态属性,提供数学常数,只读。
Math.E
:常数e。Math.LN2
:2 的自然对数。Math.LN10
:10 的自然对数。Math.LOG2E
:以 2 为底的e的对数。Math.LOG10E
:以 10 为底的e的对数。Math.PI
:常数π。Math.SQRT1_2
:0.5 的平方根。Math.SQRT2
:2 的平方根。
- 静态方法:
Math.abs()
:绝对值Math.ceil()
:向上取整Math.floor()
:向下取整Math.max()
:最大值Math.min()
:最小值Math.pow()
:幂运算Math.sqrt()
:平方根Math.log()
:自然对数Math.exp()
:e的指数Math.round()
:四舍五入Math.random()
:随机数
Math.ceil
和Math.floor
两个方法结合实现返回一个数值的整数部分:
function ToInteger(x) {
x = Number(x);
return x < 0 ? Math.ceil(x) : Math.floor(x);
}
ToInteger(3.2) // 3
ToInteger(-3.2) // -3
Math.round
四舍五入处理负数时有所不同
Math.round(1.5) //2
Math.round(1.6) //2
Math.round(-1.5) //-1
Math.round(-1.6) //-2
求圆的面积
var radius = 20;
var area = Math.PI * Math.pow(radius, 2);
Math.sqrt
求参数值的平方根,参数不能为负数
Math.random()
返回0到1之间的一个伪随机数,Math.random()>=0
且Math.random()<1
。
任意范围的随机数生成函数
function getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}
任意范围的随机整数生成函数
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
生成随机字符的例子
function random_str(length) {
var ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
ALPHABET += 'abcdefghijklmnopqrstuvwxyz';
ALPHABET += '0123456789-_';
var str = '';
for (var i = 0; i < length; ++i) {
var rand = Math.floor(Math.random() * ALPHABET.length);
str += ALPHABET.substr(rand, 1);
}
return str;
}
random_str(6) // "NdQKOr"
- 三角函数
Math.sin()
:返回参数的正弦(参数为弧度值)Math.cos()
:返回参数的余弦(参数为弧度值)Math.tan()
:返回参数的正切(参数为弧度值)Math.asin()
:返回参数的反正弦(返回值为弧度值)Math.acos()
:返回参数的反余弦(返回值为弧度值)Math.atan()
:返回参数的反正切(返回值为弧度值)
弧度和角度的转换可以参考如下:arc——弧度,angle——角度
Math.PI=180度
arc=(Math.PI/180)angle //角度转弧度
angle=(180/Math.PI)arc //弧度转角度
Date对象
- Date对象是js原生的时间库。以国际标准时间(UTC)1970年1月1日00:00:00作为时间的零点,可表示的时间范围是前后各1亿天(单位为毫秒)
- 作为普通函数
Date()
使用时返回当前时间的字符串,且此时传递参数无效 - Date作为构造函数,使用
new
返回Date对象的实例。
var today = new Date();
构造函数使用的Date可接受多种参数
// 参数为时间零点开始计算的毫秒数
new Date(1578218728000)
// Sun Jan 05 2020 18:05:28 GMT+0800 (中国标准时间)
// 参数为日期字符串
new Date('January 30, 2020');
// Thu Jan 30 2020 00:00:00 GMT+0800 (中国标准时间)
// 参数为多个整数,
// 代表年、月、日、小时、分钟、秒、毫秒
new Date(2020, 6, 1, 0, 0, 0, 0)
// Wed Jul 01 2020 00:00:00 GMT+0800 (中国标准时间)
Date
构造函数的参数
- 参数是负整数,表示1970年元旦之前的时间。
- 只要是能被
Date.parse()
方法解析的字符串,都可以当作参数。 - 参数为年、月、日等多个整数时,年和月不能省略。只有一个参数Date会将其解释为毫秒数。
- 各个参数的取值范围:
年:使用四位数年份,比如2000。如果写成两位数或个位数,则加上1900,即10代表1910年。如果是负数,表示公元前。
月:0表示一月,依次类推,11表示12月。
日:1到31。
小时:0到23。
分钟:0到59。
秒:0到59
毫秒:0到999。
参数使用负数,表示扣去的时间
获取Date实例的值时返回的是字符串。即Date对象求值时调用的是
toString()
方法,而其他对象求值时调用的是valueOf()
方法var today = new Date(); today // Sun Aug 02 2020 09:30:29 GMT+0800 (中国标准时间) // 等同于 today.toString() // "Sun Aug 02 2020 09:30:29 GMT+0800 (中国标准时间)"
- 日期运算:类型自动转换时,Date实例如果转为数值,则等于对应的毫秒数;如果转为字符串,则等于对应的日期字符串。所以,两个日期实例对象进行减法运算时,返回的是它们间隔的毫秒数;进行加法运算时,返回的是两个字符串连接而成的新字符串。
- 静态方法:
Date.now()
返回当前时间距离时间零点(1970年1月1日 00:00:00 UTC)的毫秒数Date.parse()
解析日期字符串,返回距离时间零点(1970年1月1日 00:00:00)的毫秒数。解析失败返回NaN
Date.UTC()
接受年、月、日等参数,返回距离时间零点的毫秒数。被当作UTC时间处理,而new Date()
是当地时间
// 格式
Date.UTC(year, month[, date[, hrs[, min[, sec[, ms]]]]])
// 用法
Date.UTC(2011, 0, 1, 2, 3, 4, 567) // 1293847384567
- 实例方法
-
valueOf()
返回毫秒数 -
toString()
返回完整的日期字符串 -
toUTCString()
返回UTC时间(比北京晚8小时) -
toISOString()
返回ISO8601写法 -
toJSON()
与toISOString()
结果一样 -
toDateString()
返回日期字符串(不含时间) -
toTimeString()
返回时间字符串(不含日期) -
toLocaleString()
本地日期时间 -
toLocaleDateString()
本地日期 -
toLocaleTimeString()
本地时间 -
getTime()
:距离1970年1月1日00:00:00的毫秒数,等同于valueOf方法。 -
getDate()
:返回实例对象对应每个月的几号(从1开始)。 -
getDay()
:返回星期几,星期日为0,星期一为1,以此类推。 -
getFullYear()
:返回四位的年份。 -
getMonth()
:返回月份(0表示1月,11表示12月)。 -
getHours()
:返回小时(0-23)。 -
getMilliseconds()
:返回毫秒(0-999)。 -
getMinutes()
:返回分钟(0-59)。 -
getSeconds()
:返回秒(0-59)。 -
getTimezoneOffset()
:返回当前时间与 UTC 的时区差异,以分钟表示,返回结果考虑到了夏令时因素。 -
setDate(date)
:设置实例对象对应的每个月的几号(1-31),返回改变后毫秒时间戳。 -
setFullYear(year [, month, date])
:设置四位年份。 -
setHours(hour [, min, sec, ms])
:设置小时(0-23)。 -
setMilliseconds()
:设置毫秒(0-999)。 -
setMinutes(min [, sec, ms])
:设置分钟(0-59)。 -
setMonth(month [, date])
:设置月份(0-11)。 -
setSeconds(sec [, ms])
:设置秒(0-59)。 -
setTime(milliseconds)
:设置毫秒时间戳。
RegExp对象
- 正则表达式(
regular expression
)是一种表达文本模式(即字符串结构)的方法,用来按照“给定模式”匹配文本。 - 使用字面量新建正则表达式。斜杠表示开始结束。编译代码时新建正则表达式
var regex = /xyz/;
使用RegExp
构造函数新建正则。运行时新建正则表达式
var regex = new RegExp('xyz');
修饰符
var regex = new RegExp('xyz', 'i');
// 等价于
var regex = /xyz/i;
- 实例属性
修饰符相关的属性
RegExp.prototype.ignoreCase
:布尔值,是否设置了i修饰符。RegExp.prototype.global
:布尔值,是否设置了g修饰符。RegExp.prototype.multiline
:布尔值,是否设置了m修饰符。RegExp.prototype.flags
:字符串,包含了已经设置的所有修饰符,按字母排序。
var r = /abc/igm;
r.ignoreCase // true
r.global // true
r.multiline // true
r.flags // 'gim'
其他属性
RegExp.prototype.lastIndex
:下一次开始搜索的位置。属性可读写RegExp.prototype.source
:正则表达式的字符串形式(不包括反斜杠),属性只读。
- 实例方法
test()
返回布尔值,表示当前模式是否能匹配参数字符串。
正则带g
修饰符时,test方法不要连续对不同的字符串匹配调用多次
exec()
执行一个正则匹配并返回匹配结果。匹配成功,返回一个匹配成功的字符串组成的数组;否则返回null
var s = '_x_x';
var r1 = /x/;
r1.exec(s)
// ["x", index: 1, input: "_x_x", groups: undefined]
// 0: "x"
// groups: undefined
// index: 1
// input: "_x_x"
// length: 1
// __proto__: Array(0)
如果正则表达式包含圆括号(即含有“组匹配”),则返回的数组第一个成员是整个匹配成功的结果,后面的成员就是圆括号对应的匹配成功的组。也就是说,第二个成员对应第一个括号,第三个成员对应第二个括号,以此类推。整个数组的length属性等于组匹配的数量再加1。
这一点对于获取匹配非常有用,通常在字符串的匹配方法中获取
g
修饰符允许正则的实例方法多次匹配。即对同一个正则执行多次test或exec等方法,每次会记住匹配到的位置,下次继续匹配- 字符串的
match()
、search()
、replace()
、split()
等方法均可使用正则表达式作为参数
match()
的正则使用g时,会返回所有匹配成功的结果
var s = 'abba';
var r = /a/g;
s.match(r) // ["a", "a"]
正则的lastIndex属性对match无效
-
search()
返回第一个匹配的位置 -
str.replace(search, replacement)
。使用g
修饰符替换所有匹配成功的值
消除字符串首尾两端的空格
str.replace(/^\s+|\s+$/g, '')
replace
的第二个参数中可以使用美元符号$
,用来指代所替换的内容。
$&
:匹配的子字符串。- $`:匹配结果前面的文本。
$'
:匹配结果后面的文本。$n
:匹配成功的第n组内容,n是从1开始的自然数。$$
:指代美元符号$。
'输出hello world是编程的第一步'.replace(/(\w+)\s(\w+)(是)/, " ($'$3$2 $1$`——$$——匹配项是:$&) ")
// "输出 (编程的第一步是world hello输出——$——匹配项是:hello world是) 编程的第一步"
replace
的第二个参数还可以是一个函数,将每一个匹配内容替换为函数返回值。
'3 and 5'.replace(/[0-9]+/g, function (match) {
return 2 * match;
})
// "6 and 10"
var a = 'The quick brown fox jumped over the lazy dog.';
var pattern = /quick|brown|lazy/ig;
a.replace(pattern, function replacer(match) {
return match.toUpperCase();
});
// The QUICK BROWN fox jumped over the LAZY dog.
replace
第二个参数函数还可接受多个参数,第二个参数表示第一个组匹配
- 正则表达式的匹配规则
- 如果在正则表达式之中,某个字符只表示它字面的含义(比如/a/表示匹配a),则称为"字面量字符"(
literal characters
)。 - 除字面量字符外,还有一部分字符有特殊含义,不代表字面的意思。称为"元字符"(
metacharacters
)
(1)点字符(.
):匹配除回车(\r
)、换行(\n
) 、行分隔符(\u2028
)和段分隔符(\u2029
)以外的所有字符。
(2)位置字符:提示字符所处的位置。^
表示开始位置;$
表示结束位置
// test必须出现在开始位置
/^test/.test('test123') // true
// test必须出现在结束位置
/test$/.test('new test') // true
// 从开始位置到结束位置只有test
/^test$/.test('test') // true
/^test$/.test('testtest') // false
/^test$/.test('test test') // false
(3)选择符(|
):竖线符号|
表示“或关系”(OR),即cat|dog
表示匹配cat
或dog
。
选择符会包括它前后的多个字符,比如
/ab|cd/
指的是匹配ab
或者cd
,而不是匹配b
或者c
。使用圆括号可以改变匹配的范围。
实际匹配是可以成功的,只是含义上有所不同/a( |\t)b/.test('a\tb') // true
- 如果想要匹配特殊含义的"元字符",则需要会用转义字符
\
/1+1/.test('1+1') // false
/1\+1/.test('1+1') // true
需要转义的字符有^
、.
、[
、$
、(
、)
、|
、*
、+
、?
、{
和\
使用RegExp生成正则对象时,转义需要使用两个斜杠,因为字符串内部会先转义一次。
- 对不能打印的特殊字符正则表达式使用如下方式表示:
(1) \cX
表示Ctrl-[X]
,其中的X
是A-Z
之中任一个英文字母,用来匹配控制字符。
(2) [\b]
匹配退格键(U+0008
),不要与\b
混淆。
(3) \n
匹配换行键。
(4) \r
匹配回车键。
(5) \t
匹配制表符 tab(U+0009)。
(6) \v
匹配垂直制表符(U+000B)。
(7) \f
匹配换页符(U+000C)。
(8) \0
匹配null字符(U+0000)。
(9) \xhh
匹配一个以两位十六进制数(\x00
-\xFF
)表示的字符。
(10) \uhhhh
匹配一个以四位十六进制数(\u0000
-\uFFFF
)表示的 Unicode 字符。
- 字符类(
class
)表示有一系列字符可供选择,匹配[]
中字符的任意一个
匹配abc
中的任意一个即为true
/[abc]/.test('hello world') // false
/[abc]/.test('apple') // true
字符类中的特殊字符——脱字符^
:如方括号内第一个字符是[^]
,则表示否定,指除了字符类中的其他字符。
比如[^xyz]
表示除了x
、y
、z
之外都可以匹配。
如果方括号内没有其他字符,即只有[^]
,就表示匹配一切字符,包括换行符。相比之下,点号元字符(.
)不包括换行符。
var s = 'Please yes\nmake my day!';
s.match(/yes.*day/) // null
s.match(/yes[^]*day/) // [ 'yes\nmake my day']
字符类中的特殊字符——连字符(-
),在[]
中连字符-
表示字符的连续范围,提供连续多字符的简写。比如[abc]
可写成[a-c]
,[0123456789]
可写成[0-9]
。连字号(dash)只有在[]
中才表示连续范围的简写
/[a-b-]/.test('-') // true
/[a-b]/.test('-') // false
[A-z]不能表示A-z之间的52个字母,因为ASCII中大写和小写字母之间还有其他字符
/[A-z]/.test('\\') // true
- 预定义模式指的是某些常见模式的简写方式。
(1) \d
匹配0-9之间的任一数字,相当于[0-9]
。
(2) \D
匹配所有0-9以外的字符,相当于[^0-9]
。
(3) \w
匹配任意的字母、数字和下划线,相当于[A-Za-z0-9_]
。
(4) \W
除所有字母、数字和下划线以外的字符,相当于[^A-Za-z0-9_]
。
(5) \s
匹配空字符(包括换行符、制表符、空格符等),相等于[ \t\r\n\v\f]
。
(6) \S
匹配非空字符,相当于[^ \t\r\n\v\f]
。
(7) \b
匹配词的边界。
(8) \B
匹配非词边界,即在词的内部。
使用[\s\S]
匹配所有字符
var html = "<b>Hello</b>\n<i>world!</i>";
// .不匹配换行符
/.*/.exec(html)[0] // "<b>Hello</b>"
// 匹配所有字符
/[\S\s]*/.exec(html)[0] // "<b>Hello</b>\n<i>world!</i>"
- 重复类
大括号{}
表示模式的匹配次数,{n}
表示恰好重复n
次,{n,}
表示至少重复n
次,{n,m}
表示重复不少于n
次,不多于m
次。
/lo{2}k/.test('look') // true
/lo{2,5}k/.test('looook') // true
- 量词符用来设定某个模式出现的次数
?
问号表示某个模式出现0次或1次,等同于{0, 1}
。
*
星号表示某个模式出现0次或多次,等同于{0,}
。
+
加号表示某个模式出现1次或多次,等同于{1,}
。
// t 出现0次或1次
/t?est/.test('test') // true
/t?est/.test('est') // true
// t 出现1次或多次
/t+est/.test('test') // true
/t+est/.test('ttest') // true
/t+est/.test('est') // false
// t 出现0次或多次
/t*est/.test('test') // true
/t*est/.test('ttest') // true
/t*est/.test('tttest') // true
/t*est/.test('est') // true
- 贪婪模式:如上的量词符,默认情况下都是最大可能匹配,即匹配到下一个字符不满足匹配规则为止。这被称为贪婪模式。
如下,/a+/
匹配1个或多个a
,贪婪模式下会一直匹配到不符合这个规则为止;/a+?/
中通过添加?
改为非贪婪模式,会在第一次满足条件时就不再继续匹配
var s = 'aaa';
s.match(/a+/) // ["aaa"]
s.match(/a+?/) // ["a"]
非贪婪模式的符号+?
、*?
、??
- 修饰符(
modifier
)表示模式的附加规则,放在正则模式的最尾部。
(1) g
修饰符表示全局匹配(global)。正则对象将匹配全部符合条件的结果
(2) i
修饰符表示忽略大小写(ignoreCase)
(3) m
修饰符表示多行模式(multiline)。默认情况下(不加m修饰符时),^
和$
匹配字符串的开始处和结尾处,加上m
修饰符以后,^
和$
还会匹配行首和行尾,即^
和$
会识别换行符(\n)。
加上m
后,$
可以匹配行尾,相当于把\n
看作另一行,之前的为上一行(行尾)
/world$/.test('hello world\n') // false
/world$/m.test('hello world\n') // true
加上m
后,换行符\n
被认为是一行的开始,所以后面的字符表示另一行的行首
/^b/m.test('a\nb') // true
- 组匹配:括号表示分组匹配,括号中的模式可以用来匹配分组的内容。
()
表示组匹配
/fred+/.test('fredd') // true
/(fred)+/.test('fredfred') // true
使用组匹配时,不宜同时使用
g
修饰符,否则match
方法不会捕获分组的内容。var m = 'abcabc'.match(/(.)b(.)/g); m // ['abc', 'abc']
如上,使用
g
,match
方法只捕获了匹配整个表达式的部分。使用正则表达式的
exec
方法,配合循环,才能读到每一轮匹配的组捕获var str = 'abcabc'; var reg = /(.)b(.)/g; while (true) { var result = reg.exec(str); if (!result) break; console.log(result); } // ["abc", "a", "c"] // ["abc", "a", "c"]
正则表达式内部,可以用\n
引用括号匹配的内容,n
是从1开始的自然数
/(.)b(.)\1b\2/.test("abcabc") // true
括号可以嵌套,如下\1
指向外层括号,\2
指向内层括号
/y((..)\2)\1/.test('yabababab') // true
如下匹配标签
var tagName = /<(([^>]+))>[^<]*<\/\1>/;
tagName.exec("<b>bold</b>")[1] // 'b'
实现匹配带有属性的标签
var html = '<h1 class="hello">Hello<i>world</i></h1>';
var tag = /<(\w+)([^>]*)>(.*?)<\/\1>/g;
var match = tag.exec(html);
match
// (4) ["<b class="hello">Hello</b>", "b", " class="hello"", "Hello", index: 0, input: "<b class="hello">Hello</b><i>world</i>", groups: undefined]
// 0: "<b class="hello">Hello</b>"
// 1: "b"
// 2: " class="hello""
// 3: "Hello"
// groups: undefined
// index: 0
// input: "<b class="hello">Hello</b><i>world</i>"
// length: 4
// match[0]中再匹配是否满足标签
match = tag.exec(html);
> (4) ["<i>world</i>", "i", "", "world", index: 26, input: "<》b class="hello">Hello</b><i>world</i>", groups: undefined]
> 0: "<i>world</i>"
> 1: "i"
> 2: ""
> 3: "world"
> groups: undefined
> index: 26
> input: "<b class="hello">Hello</b><i>world</i>"
> length: 4
- 非捕获组:
(?:x)
称为非捕获组(Non-capturing group
),表示不返回该组匹配的内容,即匹配的结果中不计入这个括号。
非捕获组的作用请考虑这样一个场景,假定需要匹配foo
或者foofoo
,正则表达式就应该写成/(foo){1, 2}/
,但是这样会占用一个组匹配。这时,就可以使用非捕获组,将正则表达式改为/(?:foo){1, 2}/
,它的作用与前一个正则是一样的,但是不会单独输出括号内部的内容。
var m = 'abc'.match(/(?:.)b(.)/);
m // ["abc", "c"]
正则拆解网址
// 正常匹配
var url = /(http|ftp):\/\/([^/\r\n]+)(\/[^\r\n]*)?/;
url.exec('http://google.com/');
// ["http://google.com/", "http", "google.com", "/"]
// 非捕获组匹配
var url = /(?:http|ftp):\/\/([^/\r\n]+)(\/[^\r\n]*)?/;
url.exec('http://google.com/');
// ["http://google.com/", "google.com", "/"]
- 先行断言:
x(?=y)
称为先行断言(Positive look-ahead
),x
后面跟着y
才匹配,y
不会被计入返回结果。比如,要匹配后面跟着百分号的数字,可以写成/\d+(?=%)/
。
"先行断言"中,括号的部分不会返回。
var m = 'abc'.match(/b(?=c)/);
m // ["b"]
- 先行否定断言:
x(?!y)
称为先行否定断言(Negative look-ahead
),x
后面不跟着y
才匹配,y
不会被计入返回结果。比如,要匹配后面跟的不是百分号的数字,就要写成/\d+(?!%)/。
/\d+(?!\.)/.exec('3.14') // ["14"]
JSON对象
- JSON(JavaScript对象表示法
JavaScript Object Notation
的缩写)是一种用于数据交换的文本格式,2001年Douglas Crockford提出,目的是取代繁琐笨重的XML格式。 - Json的显著优点:书写简单;符合js原生语法,js引擎可直接处理
- 每个JSON对象就是一个值,可能是数组、对象、或原始类型
- Json的类型和格式:
- 复合类型的值只能是数组或对象,不能是函数、正则表达式对象、日期对象。
- 原始类型的值只有四种:字符串、数值(必须以十进制表示)、布尔值和null(不能使用
NaN
,Infinity
,-Infinity
和undefined
)。 - 字符串必须使用双引号表示,不能使用单引号。
- 对象的键名必须放在双引号里面。
- 数组或对象最后一个成员的后面,不能加逗号。
null
、空数组[]
和空对象{}
都是合法的 JSON 值
如下为合法的json
["one", "two", "three"]
{ "one": 1, "two": 2, "three": 3 }
{"names": ["张三", "李四"] }
[ { "name": "张三"}, {"name": "李四"} ]
- JSON对象是js的原生对象,用来处理JSON格式数据
- JSON静态方法:
JSON.stringify()
将一个值转为JSON字符串,且可以被JSON.parse()
还原。
使用和介绍
如果对象的属性是undefined
、函数或XML
对象,该属性会被JSON.stringify
过滤
var obj = {
a: undefined,
b: function () {}
};
JSON.stringify(obj) // "{}"
如果数组的成员是undefined
、函数或XML对象,则被转成null
var arr = [undefined, function () {}];
JSON.stringify(arr) // "[null,null]"
正则对象会被转为空对象
JSON.stringify(/foo/) // "{}"
对象的不可遍历属性会被JSON.stringify
忽略
var obj = {};
Object.defineProperties(obj, {
'foo': {
value: 1,
enumerable: true
},
'bar': {
value: 2,
enumerable: false
}
});
JSON.stringify(obj); // "{"foo":1}"
JSON.stringify
第二个参数
JSON.stringify
方法第二个参数如果是数组,用来指定需要转成字符串的属性。只对对象的属性有效
var obj = {
'prop1': 'value1',
'prop2': 'value2',
'prop3': 'value3'
};
var selectedProperties = ['prop1', 'prop2'];
JSON.stringify(obj, selectedProperties) // "{"prop1":"value1","prop2":"value2"}"
第二个参数如果为函数,可用来更改JSON.stringify
返回值。
function f(key, value) {
if (typeof value === "number") {
value = 2 * value;
}
return value;
}
JSON.stringify({ a: 1, b: 2 }, f) // '{"a": 2,"b": 4}'
如果处理函数返回undefined
或没有返回值,则该属性会被忽略
function f(key, value) {
if (typeof(value) === "string") {
return ; // return undefined;
}
return value;
}
JSON.stringify({ a: "abc", b: 123 }, f)
JSON.stringify第三个参数
JSON.stringify
可以接受第三个参数,用于增加返回的 JSON 字符串的可读性,格式化返回的json字符串。数字表示每个属性前面添加的空格(最多不超过10个);字符串(不超过10个字符),则该字符串会添加在每行前面。
JSON.stringify({ p1: 1, p2: 2 }, null, 4);
/*
"{
"p1": 1,
"p2": 2
}"
*/
JSON.stringify({ p1:1, p2:2 }, null, '==||==');
/*
"{
==||=="p1": 1,
==||=="p2": 2
}"
*/
参数对象的toJSON方法
如果参数对象有自定义的toJSON
方法,JSON.stringify
会使用这个方法的返回值作为参数,而忽略原对象。
Date
对象就有一个自己的toJSON
方法
var date = new Date('2020-01-01');
date.toJSON() // "2020-01-01T00:00:00.000Z"
JSON.stringify(date) // ""2020-01-01T00:00:00.000Z""
toJSON()
的一个应用是,将正则对象自动转为字符串。
var obj = {
reg: /foo/
};
// 不设置 toJSON 方法时
JSON.stringify(obj) // "{"reg":{}}"
// 设置 toJSON 方法
RegExp.prototype.toJSON = RegExp.prototype.toString;
JSON.stringify(obj) // "{"reg":"/foo/"}"
JSON.parse(JSON.stringify(obj)) // {reg: "/foo/"}
- JSON静态方法:
JSON.parse()
将JSON字符串转换成对应的js值。字符串必须是有效的JSON格式
try {
JSON.parse("'String'");
} catch(e) {
console.log('parsing error');
}
JSON.parse()
第二个参数可接受一个函数,与JSON.stringify
方法类似。
function f(key, value) {
if (key === 'a') {
return value + 10;
}
return value;
}
JSON.parse('{"a": 1, "b": 2}', f)
// {a: 11, b: 2}
非原创文章若有需要,建议直接联系原文作者或保留声明情况下转载原文
![](https://blog-static.cnblogs.com/files/codemissing/onlyqr_codemissing.gif)