《JavaScript语言入门教程》记录整理:运算符、语法和标准库

本系列基于阮一峰老师的《JavaScrip语言入门教程》或《JavaScript教程》记录整理,教程采用知识共享 署名-相同方式共享 3.0协议。这几乎是学习js最好的教程之一(去掉之一都不过分)

最好的教程而阮一峰老师又采用开源方式共享出来,之所以重新记录一遍,一是强迫自己重新认真读一遍学一遍;二是对其中知识点有个自己的记录,加深自己的理解;三是感谢这么好的教程,希望更多人阅读了解

运算符

算数运算符

  1. js提供了10种运算符
  • 加法运算符:x + y
  • 减法运算符:x - y
  • 乘法运算符:x * y
  • 除法运算符:x / y
  • 指数运算符:x ** y
  • 余数运算符:x % y
  • 自增运算符:++x 或者 x++
  • 自减运算符:--x 或者 x--
  • 数值运算符: +x
  • 负数值运算符:-x
  1. js中非数值可以相加,比如布尔值与数值相加,字符串相加用于连接两个字符串
true + true // 2
1 + true // 2

1 + 'a' // "1a"
false + 'a' // "falsea"

加法运算符是在运行时决定,到底是执行相加,还是执行连接。运算子的不同,导致了不同的语法行为,这种现象称为“重载”(overload)

'3' + 4 + 5 // "345"
3 + 4 + '5' // "75"

加法运算符存在重载。减法、除法和乘法等运算符不会重载:所有运算子一律转为数值,再进行相应的数学运算

  1. 对象的相加:运算子是对象时,会先转成原始类型的值,然后再相加。

对象默认转成原始类型的值是[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. 余数运算符(%)返回前一个运算子被后一个运算子除所得的余数。结果的正负号由第一个运算子决定
-1 % 2 // -1
1 % -2 // 1

可以使用绝对值,获得负数的正确余数值

// 正确的写法
function isOdd(n) {
  return Math.abs(n % 2) === 1;
}
isOdd(-5) // true
isOdd(-4) // false
  1. 自增和自减运算符是一元运算符,只有一个运算子。

运算之后,变量的值发生变化,这种效应叫做运算的副作用(side effect)。自增和自减运算符是仅有的两个具有副作用的运算符,其他运算符都不会改变变量的值。

自增/自减放在变量后面,会先返回变量操作前的值,再进行自增/自减操作
自增/自减放在变量之前,会先进行自增/自减操作,再返回变量操作后的值

  1. 数值运算符(+)的作用可以将任何值转为数值(与Number函数作用相同)

负数值运算符(-),将一个值转为数值的负值

不会改变原始变量的值,而是返回新值

  1. 指数运算符(**)完成指数运算

指数运算符是右结合,而不是左结合。即多个指数运算符连用时,先进行最右边的计算。

// 相当于 2 ** (3 ** 2)
2 ** 3 ** 2  // 512
  1. 赋值运算符(Assignment Operators)用于给变量赋值。还有复合的赋值运算符,如x += yx -= y

比较运算符

  1. 比较运算符比较两个值的大小,并返回一个布尔值。js提供了8个比较运算符
  • > 大于运算符
  • < 小于运算符
  • <= 小于或等于运算符
  • >= 大于或等于运算符
  • == 相等运算符
  • === 严格相等运算符
  • != 不相等运算符
  • !== 严格不相等运算符
  1. 相等比较和非相等比较。

对于非相等的比较,算法是先看两个运算子是否都是字符串,如果是的,就按照字典顺序比较(实际上是比较 Unicode 码点);否则,将两个运算子都转成数值,再比较数值的大小。

  1. 相等运算符(==)比较两个值是否相等,严格相等运算符(===)比较两个值是否为“同一个值”。

如果两个值不是同一类型,严格相等运算符===直接返回false,而相等运算符==会将它们转换成同一个类型,再进行比较

  1. 严格相等运算符:类型不同返回false;同一类型的原始类型值,会比较两者的值是否相等;复合类型的值(对象、数组、函数)比较的是是否指向同一个地址;undefined和null与自身严格相等

两个对象的比较,严格相等运算符比较的是地址,而大于或小于运算符比较的是值

var obj1 = {};
var obj2 = {};

obj1 > obj2 //  比较的是值 false
obj1 < obj2 //  比较的是值 false
obj1 === obj2 // 比较的是地址 false

相等运算符比较是隐含了类型转换,建议最好只使用严格相等运算符(===)。

布尔运算符

  1. 布尔运算符用于将表达式转为布尔值。一共有4个
  • 取反运算符:!
  • 且运算符:&&
  • 或运算符:||
  • 三元运算符:?:
  1. 取反运算符将布尔值变为相反值。两次取反就是将一个值转为布尔值的简便写法
  2. 且运算符&&常用于多个表达式的求值

且运算符&&运算规则是:如果第一个运算子的布尔值为true,则返回第二个运算子的值(注意是值,不是布尔值);如果第一个运算子的布尔值为false,则直接返回第一个运算子的值,且不再对第二个运算子求值。

&&且运算可以用来取代if语句

if (i) {
  doSomething();
}

// 等价于

i && doSomething();
  1. 或运算符(||)也用于多个表达式的求值。

或运算符||的运算规则是:如果第一个运算子的布尔值为true,则返回第一个运算子的值,且不再对第二个运算子求值;如果第一个运算子的布尔值为false,则返回第二个运算子的值。

或运算符常用于为一个变量设置默认值。

function saveText(text) {
  text = text || '';
  // ...
}

// 或者写成
saveText(this.text || '')
  1. 且运算符和或运算符,这种通过第一个表达式(运算子)的值,控制是否运行第二个表达式(运算子)的机制,就称为“短路”(short-cut)
  2. 三元条件运算符(?:)是js中唯一一个需要三个运算子的运算符

二进制位运算符

  1. 二进制位运算符用于直接对二进制位进行计算,一共有7个:
  • 二进制或运算符(or):符号为|,表示若两个二进制位都为0,则结果为0,否则为1。
  • 二进制与运算符(and):符号为&,表示若两个二进制位都为1,则结果为1,否则为0。
  • 二进制否运算符(not):符号为~,表示对一个二进制位取反。
  • 异或运算符(xor):符号为^,表示若两个二进制位不相同,则结果为1,否则为0。
  • 左移运算符(left shift):符号为<<
  • 右移运算符(right shift):符号为>>
  • 头部补零的右移运算符(zero filled right shift):符号为>>>
  1. 位运算符只对整数起作用,如果一个运算子不是整数,会自动转为整数后再执行。虽然在JavaScript内部,数值都是以64位浮点数的形式储存,但是做位运算的时候,是以32位带符号的整数进行运算的,并且返回值也是一个32位带符号的整数

利用这个特性,可以写出一个函数,将任意数值转为32位整数。

function toInt32(x) {
  return x | 0;
}
  1. 位运算符可以用作设置对象属性的开关。(开关作用有些抽象,但很精巧)

假定某个对象有四个开关,每个开关都是一个变量。那么,可以设置一个四位的二进制数,它的每个位对应一个开关。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
  // ...
}
  • 假设需要打开ABD三个开关,可以先构造一个掩码变量,然后通过二进制或运算掩码变量,可以确保打开这三个开关
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和逗号运算符

  1. void运算符,执行一个表达式,然后不返回任何值,或者返回undefined
void 0 // undefined
void(0) // undefined   推荐写法

void运算符的优先级很高,使用括号避免错误

var x = 3;
void (x = 5) //undefined
x // 5
  1. 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>
  1. 逗号运算符用于对两个表达式求值,并返回后一个表达式的值。
'a', 'b' // "b"

var x = 0;
var y = (x++, 10);
x // 1
y // 10

用途是:在返回一个值之前,进行一些辅助操作。

var value = (console.log('Hi!'), true);
// Hi!

value // true

运算顺序

  1. 运算符优先级别(Operator Precedence)高的先执行
  2. 圆括号()用来提高运算的优先级(它的优先级最高),即圆括号中的表达式会第一个运算

圆括号不是运算符,而是一种语法结构。它一共有两种用法:一种是把表达式放在圆括号之中,提升运算的优先级;另一种是跟在函数的后面,作用是调用函数。

函数放在圆括号中,会返回函数本身。圆括号紧跟在函数的后面,表示调用函数。

圆括号之中,只能放置表达式

  1. "左结合"(left-to-right associativity)运算符会先从左向右运算
    "右结合"(right-to-left associativity)运算符会先从右向左运算

js中赋值运算符(=)、三元条件运算符(?:)、指数运算符(**)是"右结合"的

语法

数据类型的转换

  1. JavaScript 是一种动态类型语言,变量的类型无法在编译阶段确定,必须在运行时才能知道。而同时js的变量类型又可以随意改变,因此又属于弱类型语言
  2. JS中的运算符对数据类型有要求。因此常常发生类型自动转换
  3. 强制类型转换主要指使用Number()String()Boolean()手动将任意类型的值,分别转换成数字、字符串或者布尔值。
  4. 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方法返回的是对象,就报错。

自定义valueOftoString

Number({
  valueOf: function () {
    return 2;
  }
})
// 2

Number({
  toString: function () {
    return 3;
  }
})
// 3

Number({
  valueOf: function () {
    return 2;
  },
  toString: function () {
    return 3;
  }
})
// 2
  1. 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"
  1. Boolean()转换为布尔值,规则简单,除了下面6个值结果为false,其余全部为true

undefinednull0(包含-0+0)、NaN''(空字符串)和false

所有对象(包括空对象)的转换结果都是true,包括false对应的布尔对象new Boolean(false)也是true

  1. js中数据类型自动转换发生的情况:一、不同类型的数据相互运算时会自动转换。二、对非布尔值类型的数据求布尔值时。三、对非数值类型的值使用一元运算符(即+-)。转换时的规则是:预期什么类型的值,就调用该类型的转换函数。如果该位置既可以是字符串,又可以是数值,则默认转为数值
  2. JavaScript在预期为布尔值的地方(比如if语句的条件部分),会将非布尔值的参数自动转换为布尔值。系统内部会自动调用Boolean函数。

如下两个方法将一个表达式转为布尔值

// 写法一
expression ? true : false

// 写法二
!! expression
  1. 除了加法运算符(+)有可能把运算子转为字符串,其他运算符都会把运算子自动转成数值。

null数值0undefined数值NaN

错误处理机制

  1. JavaScript原生提供Error构造函数,所有抛出的错误都是这个构造函数的实例。当发生错误时,js引擎抛出Error实例对象以后,整个程序就中断在发生错误的地方,不再往下执行。
var err = new Error('出错了');
err.message // "出错了"
  1. 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
  1. Error实例是最一般的错误类型,js还提供Error的6个派生对象
  • SyntaxError对象:解析代码时发生的语法错误
  • ReferenceError对象:引用一个不存在的变量时发生的错误。
  • RangeError对象:一个值超出有效范围时发生的错误。
  • TypeError对象:变量或参数不是预期类型时发生的错误。
  • URIError对象:URI相关函数的参数不正确时抛出的错误。主要encodeURI()decodeURI()encodeURIComponent()decodeURIComponent()escape()unescape()
  • EvalError对象:已不再使用
  1. 自定义错误
function UserError(message) {
  this.message = message || '默认信息';
  this.name = 'UserError';
}

UserError.prototype = new Error();
UserError.prototype.constructor = UserError;
  1. throw语句:手动中断程序执行,抛出一个错误。
if (true) {
  throw new Error('x 必须为正数');
}
// Uncaught Error: x 必须为正数
//    at <anonymous>:2:9

throw可以抛出任何类型的值

  1. 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);
  }
  // ...
}
  1. 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();
}

编程风格

  1. "编程风格"(programming style)指的是编写代码的样式规则。
    你选择的,不是你喜欢的风格,而是一种能够清晰表达你的意图的风格
  2. 编程风格主要考虑的几点:缩进(indent)、区块(block)、圆括号(parentheses)、行尾的分号、变量声明、严格相等、语句的合并书写等
  3. 使用{}代码块时,js中要使用左大括号{紧挨着语句在同一行中,不要换行写。这是因为JavaScript会自动添加句末的分号,从而产生一些难以察觉的错误。
block {
  // ...
}

如下return语句其实会变成两句,从而导致出问题

return
{
  key: value
};

// 相当于
return;
{
  key: value
};

// 正确写法
return {
  key : value
};
  1. 行尾的分号:分号表示一条语句的结束。js允许省略。

有三种情况,语法规定不需要在结尾添加分号。如果添加,js引擎将分号解释为空语句

    1. forwhile 循环
for ( ; ; ) {
} // 没有分号

while (true) {
} // 没有分号

但是do...while要有分号

    1. 分支语句:ifswitchtry
if (true) {
} // 没有分号

switch () {
} // 没有分号

try {
} catch {
} // 没有分号
  • 函数的声明语句
function f() {
} // 没有分号

函数表达式仍要使用分号

var f = function f() {
};

除了这三种情况,所有语句都应该使用分号。

在没有分号时JavaScript会自动添加,这种语法特性叫"分号的自动添加"(Automatic Semicolon Insertion,简称ASI)

但是,如果下一行的开始可以与本行的结尾连在一起解释,JavaScript就不会自动添加分号。

而是否自动添加分号无法预测,很有可能导致额外的错误。

一行的起首"自增"(++)或"自减"(--),则前面会自动添加分号

不应该省略结尾的分号,还有一个原因。有些JavaScript代码压缩器(uglifier)不会自动添加分号,因此遇到没有分号的结尾,就会让代码保持原状,而不是压缩成一行,使得压缩无法得到最优的结果。

另外,不写结尾的分号,可能会导致脚本合并出错。所以,有的代码库在第一行语句开始前,会加上一个分号。可以避免与其他脚本合并时,前面的脚本最后一行语句没有分号,导致运行出错的问题。

;var a = 1;
// ...
  1. 避免全局变量的使用,如果必须使用,考虑大写字母表示
  2. 变量声明,由于存在变量提升,许多语句会导致产生全局变量(比如for循环中)。

所有函数都应该在使用之前定义。函数内部的变量声明,都应该放在函数的头部。

  1. 建议只使用严格相等运算符(===)
  2. switch...case结构可以用对象结构代替

switch...case结构类似于goto语句,容易造成程序流程的混乱,使得代码结构混乱不堪,不符合面向对象编程的原则。

console对象和控制台

  1. console对象是JavaScript的原生对象,可以输出各种信息到控制台
  2. console的常见用途:调试程序,显示网页代码运行时的错误信息;提供了一个命令行接口,用来与网页代码互动。
  3. 开发者工具的几个面板。
  • Elements:查看网页的 HTML 源码和 CSS 代码。
  • Resources:查看网页加载的各种资源文件(比如代码文件、字体文件 CSS 文件等),以及在硬盘上创建的各种内容(比如本地缓存、Cookie、Local Storage等)。
  • Network:查看网页的 HTTP 通信情况。
  • Sources:查看网页加载的脚本源码,可进行断点debug。
  • Timeline:查看各种网页行为随时间变化的情况。
  • Performance:查看网页的性能情况,比如 CPU 和内存消耗。
  • Console:即控制台,用来运行js命令,和页面中js代码console方法的输出。
  1. console 对象的静态方法
  • console.log()console.info()console.debug()
  • console.warn(),console.error()
  • console.table()
  • console.count()
  1. debugger语句主要用于除错,作用是设置断点。

标准库

下面基本都是js原生对象的介绍,里面许多属性和方法仅了解一下即可,有需要时再查询使用

Object对象

  1. JavaScript原生提供Object对象
  2. JavaScript的所有其他对象都继承自Object对象,都是Object的实例。
  3. 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
  1. Object本身是一个函数,可以当作工具方法使用,将任意值转为对象。保证某个值一定是对象。
  2. Object方法无参数或为undefinednull,返回一个空对象
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
  1. instanceof运算符验证一个对象是否为指定的构造函数的实例
  2. Object构造函数用来生成新对象
var obj = new Object();

// 等价于
var obj = {};
  1. Object构造函数与工具方法类似。如果参数是一个对象,则直接返回该对象;如果是一个原始类型的值,则返回该值对应的包装对象
  2. 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
  1. Object实例对象的方法:
  • Object.prototype.valueOf():返回当前对象对应的值,默认情况下返回对象本身。
  • Object.prototype.toString():返回当前对象对应的字符串形式,默认返回类型字符串。
  • Object.prototype.toLocaleString():返回当前对象对应的本地字符串形式。
  • Object.prototype.hasOwnProperty():判断某个属性是否为当前对象自身的属性,还是继承自原型对象的属性。
  • Object.prototype.isPrototypeOf():判断当前对象是否为另一个对象的原型。
  • Object.prototype.propertyIsEnumerable():判断某个属性是否可枚举。
  1. 数组、字符串、函数、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 (中国标准时间)"
  1. 判断数据类型

关于如何正确的判断数据类型,由于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
  1. toLocaleString()用来实现自定义的本地字符串。如Array.prototype.toLocaleString()
    Number.prototype.toLocaleString()Date.prototype.toLocaleString()等对象自定义这个方法

属性描述对象

  1. JS提供了叫做"属性描述对象"(attributes object)的内部数据结构,用来描述对象的属性,控制它的行为,比如该属性是否可写、可遍历等。
  2. 每个属性都有自己对应的属性描述对象,保存该属性的一些元信息。
  3. 如下为属性描述对象的例子:
{
  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属性,否则会报错。

  1. Object.getOwnPropertyDescriptor()获取属性描述对象
var obj = { p: 'a' };

Object.getOwnPropertyDescriptor(obj, 'p')
// Object { value: "a",
//   writable: true,
//   enumerable: true,
//   configurable: true
// }
  1. Object.defineProperty()通过属性描述对象,定义或修改属性,并返回修改后的对象。
Object.defineProperty(object, propertyName, attributesObject)

参数:

  • object:属性所在的对象
  • propertyName:字符串,属性名
  • attributesObject:属性描述对象

Object.defineProperties()可以一次定义多个属性

  1. JSON.stringify方法会排除enumerable为false的属性,有时可以利用这一点。如果对象的 JSON格式输出要排除某些属性,可以把这些属性的enumerable设为false。
  2. 存取器(accessor,set-setter,get-getter)是另外定义属性的方式,定义存取器后,将会执行对应的函数。

除了defineProperty方法中通过属性描述对象定义存取器,还提供如下的写法(且这种写法configurableenumerable都为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: 新的值必须大于当前值
  1. 对象的拷贝:

由于对象是引用类型,数据存放在堆中,栈中值存放对象的地址。默认值类型的赋值是复制给另一个变量;但引用类型的赋值是直接将引用地址复制给另一个变量,赋值引用就是常说的浅拷贝(浅拷贝的对象共用一个内存地址)。而深拷贝指的是将引用类型的数据也完全复制一份给新的变量。

对象深拷贝的基本原理就是:通过遍历对象的属性,然后将属性和递归至不是对象的属性值重新赋值为另一个对象,如果属性值是对象,则递归执行当前函数。

  • 方法一。 如下,缺点不能深拷贝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的sliceconcat等方法不改变原数组,但是返回的也是浅拷贝了的新数组
另:$.extend方法的第一个参数给bool值表示是否深拷贝:jQuery.extend( [deep ], target, object1 [, objectN ] )

  1. 控制对象状态
  • Object.preventExtensions方法:使一个对象无法再添加新的属性
  • Object.isExtensible方法检查一个对象是否使用了Object.preventExtensions方法。检查是否可以为一个对象添加属性。
  • Object.seal方法使得一个对象既无法添加新属性,也无法删除旧属性。Object.isSealed()
  • Object.freeze方法使一个对象变成常量。无法添加新属性、无法删除旧属性、也无法改变属性的值。Object.isFrozen()

上面三个方法锁定对象的可写性有一个漏洞:可以通过改变原型对象,来为对象增加属性。解决方案是原型也冻结住。另外一个局限是,如果属性值是对象,这些方法只能冻结属性指向的对象,而不能冻结对象本身的内容。

Array 对象

  1. Array是JavaScript的原生对象,也是一个构造函数,用来生成新数组。
var arr = new Array(2);  // 等同于 var arr = Array(2);
arr.length // 2
arr // [ empty x 2 ]
  1. Array()构造函数有很大的缺陷,不同的参数生成的结果会不一样。因此建议使用数组字面量的方式
// 不建议的方式
var arr = new Array(1, 2);

// 推荐
var arr = [1, 2];
  1. Array.isArray()静态方法,判断是否是数组
var arr = [1, 2, 3];

typeof arr // "object"
Array.isArray(arr) // true
  1. 数组对象的实例方法:
  • valueOf()返回数组本身
  • toString()返回数组的字符串形式
  • push()在数组的末端添加一个或多个元素,返回添加后的数组长度——(在数组末尾压入元素)。该方法改变原数组。
  • pop()删除数组的最后一个元素,并返回该元素——(弹出最后一个元素)。该方法改变原数组。

pushpop结合使用,可构成"后进先出"的栈结构(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变量

  • forEachmap相似,对数组的所有成员依次执行参数函数,但不返回值。

如果数组遍历是为了得到返回值,可以使用map方法,否则使用forEach方法。

forEach方法无法中断执行。如果想要中断,可使用for循环、或someevery方法。

  • 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开始);原数组。前两个必须

reducereduceRight的第二个参数可指定执行时的初始值

[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

空数组执行reducereduceRight时会报错,可指定第二个参数初始值解决

借助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
  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"

包装对象

  1. js的三种原始类型的值——数值、字符串、布尔值——在一定条件下会自动转为对象,这就是原始类型的"包装对象"(wrapper)
  2. "包装对象"指的是与数值、字符串、布尔值分别相对应的NumberStringBoolean三个原生对象。这三个原生对象可以把原始类型的值变成(包装成)对象。
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
  1. 包装对象的设计目的:首先,使得"对象"这种类型可以覆盖JavaScript所有的值,整门语言有一个通用的数据模型。其次,使得原始类型的值也有办法调用自己的方法
  2. NumberStringBoolean作为普通函数调用时用以类型转换,将任意类型的值转为数值、字符串和布尔值等原始类型的值;作为构造函数使用(带有new)时,将原始类型的值转为对象
  3. 包装对象继承了Object对象的valueOf()——返回包装对象实例对应的原始类型的值、toString()——返回对应的字符串形式方法
  4. 原始类型与实例对象的自动转换:有时,原始类型的值会自动当作包装对象调用,即调用包装对象的属性和方法。JavaScript 引擎会自动将原始类型的值转为包装对象实例,并在使用后立刻销毁实例

比如字符串调用length属性:

'abc'.length // 3

abc是一个字符串,本身不是对象,不能调用length属性。JavaScript引擎自动将其转为包装对象,在这个对象上调用length属性。调用结束后,这个临时对象就会被销毁。这就叫原始类型与实例对象的自动转换

自动转换生成的包装对象是只读的,无法修改。所以,字符串无法添加新属性。同时调用结束后,包装实例会自动销毁,所以每次调用其实都是一个新的包装对象。

var s = 'Hello World';
s.x = 123;
s.x // undefined

如果要为字符串添加属性,只有在它的原型对象String.prototype上定义

  1. 可以在包装对象的原型对象prototype上添加自定义方法或属性

Boolean对象

  1. 通过valueOf()获取包装对象对应的原始类型值
new Boolean(false).valueOf()

Number对象

  1. 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
  1. 实例方法
  • 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对象

  1. 静态方法String.fromCharCode()返回Unicode码点组成的字符串

Unicode码点不能大于0xFFFF,码点大于0xFFFF的字符占用四个字节,而JavaScript默认支持的是两个字节的字符。比如0x20BB7需要拆成两个字符来写

String.fromCharCode(0xD842, 0xDFB7)   // "𠮷"
  1. 实例属性String.prototype.length返回字符串长度
  2. 实例方法
  • 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对象

  1. Math提供各种数学功能。该对象不是构造函数,不能生成实例,必须在Math对象上调用属性和方法。
  2. 静态属性,提供数学常数,只读。
  • 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 的平方根。
  1. 静态方法:
  • Math.abs():绝对值
  • Math.ceil():向上取整
  • Math.floor():向下取整
  • Math.max():最大值
  • Math.min():最小值
  • Math.pow():幂运算
  • Math.sqrt():平方根
  • Math.log():自然对数
  • Math.exp():e的指数
  • Math.round():四舍五入
  • Math.random():随机数

Math.ceilMath.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()>=0Math.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"
  1. 三角函数
  • 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对象

  1. Date对象是js原生的时间库。以国际标准时间(UTC)1970年1月1日00:00:00作为时间的零点,可表示的时间范围是前后各1亿天(单位为毫秒)
  2. 作为普通函数Date()使用时返回当前时间的字符串,且此时传递参数无效
  3. 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 (中国标准时间)"
  1. 日期运算:类型自动转换时,Date实例如果转为数值,则等于对应的毫秒数;如果转为字符串,则等于对应的日期字符串。所以,两个日期实例对象进行减法运算时,返回的是它们间隔的毫秒数;进行加法运算时,返回的是两个字符串连接而成的新字符串。
  2. 静态方法:
  • 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
  1. 实例方法
  • 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对象

  1. 正则表达式(regular expression)是一种表达文本模式(即字符串结构)的方法,用来按照“给定模式”匹配文本。
  2. 使用字面量新建正则表达式。斜杠表示开始结束。编译代码时新建正则表达式
var regex = /xyz/;

使用RegExp构造函数新建正则。运行时新建正则表达式

var regex = new RegExp('xyz');

修饰符

var regex = new RegExp('xyz', 'i');
// 等价于
var regex = /xyz/i;
  1. 实例属性

修饰符相关的属性

  • 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:正则表达式的字符串形式(不包括反斜杠),属性只读。
  1. 实例方法
  • 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。

这一点对于获取匹配非常有用,通常在字符串的匹配方法中获取

  1. g修饰符允许正则的实例方法多次匹配。即对同一个正则执行多次test或exec等方法,每次会记住匹配到的位置,下次继续匹配
  2. 字符串的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第二个参数函数还可接受多个参数,第二个参数表示第一个组匹配

  1. 正则表达式的匹配规则
  • 如果在正则表达式之中,某个字符只表示它字面的含义(比如/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表示匹配catdog

选择符会包括它前后的多个字符,比如/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],其中的XA-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]表示除了xyz之外都可以匹配。

如果方括号内没有其他字符,即只有[^],就表示匹配一切字符,包括换行符。相比之下,点号元字符(.)不包括换行符。

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']

如上,使用gmatch方法只捕获了匹配整个表达式的部分。

使用正则表达式的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对象

  1. JSON(JavaScript对象表示法JavaScript Object Notation的缩写)是一种用于数据交换的文本格式,2001年Douglas Crockford提出,目的是取代繁琐笨重的XML格式。
  2. Json的显著优点:书写简单;符合js原生语法,js引擎可直接处理
  3. 每个JSON对象就是一个值,可能是数组、对象、或原始类型
  4. Json的类型和格式:
  • 复合类型的值只能是数组或对象,不能是函数、正则表达式对象、日期对象。
  • 原始类型的值只有四种:字符串、数值(必须以十进制表示)、布尔值和null(不能使用NaN, Infinity, -Infinityundefined)。
  • 字符串必须使用双引号表示,不能使用单引号。
  • 对象的键名必须放在双引号里面。
  • 数组或对象最后一个成员的后面,不能加逗号。
  • null、空数组[]和空对象{}都是合法的 JSON 值

如下为合法的json

["one", "two", "three"]

{ "one": 1, "two": 2, "three": 3 }

{"names": ["张三", "李四"] }

[ { "name": "张三"}, {"name": "李四"} ]
  1. JSON对象是js的原生对象,用来处理JSON格式数据
  2. 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/"}
  1. 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}
posted @ 2020-08-04 10:36  findmoon  阅读(441)  评论(0编辑  收藏  举报