类型详解(可不读)

ReadMe

建议直接阅读: w3c

MDN

《JavaScript高级程序设计》

image

以上官方更为权威。
本章是用于记录个人在使用JS时候踩过的坑,或者其他章节需要扩展的内容。例如上一章的基本类型就可以跳转到对应这章的内容。

类型null

定义

另一种只有一个值的类型是 Null,它只有一个专用值 null,即它的字面量。值 undefined 实际上是从值 null 派生来的,因此 ECMAScript 把它们定义为相等的。 从逻辑角度来看,null 值表 示一个空对象指针。 实际上,undefined 值是派生自 null 值的,因此 ECMA-262规定对它们的相等性测试要返回 true

alert(null == undefined);  //输出 "true"

历史

历史原因

《Speaking JavaScript》提及 原来,这与JavaScript的历史有关。1995年JavaScript诞生时,最初像Java一样,只设置了null作为表示"无"的值。根据C语言的传统,null被设计成可以自动转为0。

Number(null)
// 0
5 + null
// 5

但是,JavaScript的设计者Brendan Eich,觉得这样做还不够,有两个原因。 首先,null像在Java里一样,被当成一个对象。

typeof null  
// "object"

理由看null

但是,JavaScript的数据类型分成原始类型(primitive)和合成类型(complex)两大类,Brendan Eich觉得表示"无"的值最好不是对象。
其次,JavaScript的最初版本没有包括错误处理机制,发生数据类型不匹配时,往往是自动转换类型或者默默地失败。Brendan Eich觉得,如果null自动转为0,很不容易发现错误。 因此,Brendan Eich又设计了一个undefined。

最初设计

JavaScript的最初版本是这样区分的:null是一个表示"无"的对象,转为数值时为0;undefined是一个表示"无"的原始值,转为数值时为NaN。

Number(undefined)
// NaN
5 + undefined
// NaN

后来用法

null ref: link
undefined 看下面

用法

  • 作为函数的参数,表示该函数的参数不是对象。
  • 作为对象原型链的终点。
Object.getPrototypeOf(Object.prototype)
// null

ref: 原型链

关联

typeOf

typeof null // 'object'

原因:
为什么会出现这种情况呢?因为在 JS 的最初版本中,使用的是 32 位系统,为了性能考虑使用低位存储了变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来。

如果我们想获得一个变量的正确类型,可以通过 Object.prototype.toString.call(xx)。

Object.prototype.toString.call(null) // "[object Null]"

boolen

console.log(!!null) // false

类型undefined

定义

undefined 表示一个未声明的变量,或已声明但没有赋值的变量,或一个并不存在的对象属性,函数没有返回值时,默认返回undefined。

用法

  • 在变量提升(预解析)阶段,只声明未定义,默认值就是undefined。 ref: 变量提升
  • 在JS的严格模式下(”use strict”),没有明确的主体,this指的就是undefined。
  • 函数定义没有返回值(return或者return后面什么也不带),默认的返回值就是undefined
  • 函数定义形参不传值,默认就是undefined
  • 对象没有这个属性名,属性值默认就是undefined
  • 在数组的find方法中,没有找到的情况下是undefined

关联

  • typeof undefined
typeof(reValue) === "undefined" // true

ref: typeof

  • boolen 的情况
console.log(!!undefined) // false

ref: boolen

类型number

定义

JavaScript 内部,所有数字都是以64位浮点数形式储存,即使整数也是如此。所以,1与1.0是相同的,是同一个数。这就是说,JavaScript 语言的底层根本没有整数,所有数字都是小数(64位浮点数)

浮点数

位数

根据国际标准 IEEE 754,JavaScript 浮点数的64个二进制位,从最左边开始,是这样组成的

1 第2位到第12位(共11位) 第13位到第64位(共52位)
符号位 0表示正数,1表示负数 指数部分 小数部分(即有效数字)

范围

最大值的11次方减1(2047)

分出一半表示负数,则 JavaScript 能够表示的数值范围为21024到2-1023(开区间)

如果一个数大于等于2的1024次方,那么就会发生“正向溢出”,即 JavaScript 无法表示这么大的数,这时就会返回Infinity

Math.pow(2, 1024) // Infinity

如果一个数小于等于2的-1075次方(指数部分最小值-1023,再加上小数部分的52位),那么就会发生为“负向溢出”,即 JavaScript 无法表示这么小的数,这时会直接返回0。

Math.pow(2, -1075) // 0

进制

不扩展,直接看组成原理

NaN

NaN是 JavaScript 的特殊值,表示“非数字”(Not a Number),主要出现在将字符串解析成数字出错的场合。

5 - 'x' // NaN
Math.acos(2) // NaN
Math.log(-1) // NaN
Math.sqrt(-1) // NaN
0 / 0 // NaN

需要注意的是,NaN不是独立的数据类型,而是一个特殊数值,它的数据类型依然属于Number,使用typeof运算符可以看得很清楚。

运算规则

NaN不等于任何值,包括它本身。

NaN === NaN // false
typeof NaN // 'number'

Infinity

不常用,如有需要看 苑一峰

Number 对象

ref Number api

为什么 0.1 + 0.2 != 0.3

因为 JS 采用 IEEE 754 双精度版本(64位),并且只要采用 IEEE 754 的语言都有该问题。

我们都知道计算机表示十进制是采用二进制表示的,所以 0.1 在二进制表示为

// (0011) 表示循环
0.1 = 2^-4 * 1.10011(0011)

image 小数算二进制和整数不同。乘法计算时,只计算小数位,整数位用作每一位的二进制,并且得到的第一位为最高位。所以我们得出 0.1 = 2^-4 * 1.10011(0011),那么 0.2 的演算也基本如上所示,只需要去掉第一步乘法,所以得出 0.2 = 2^-3 * 1.10011(0011)。

回来继续说 IEEE 754 双精度。六十四位中符号位占一位,整数位占十一位,其余五十二位都为小数位。因为 0.1 和 0.2 都是无限循环的二进制了,所以在小数位末尾处需要判断是否进位(就和十进制的四舍五入一样)。

所以 2^-4 * 1.10011...001 进位后就变成了 2^-4 * 1.10011(0011 * 12次)010 。那么把这两个二进制加起来会得出 2^-2 * 1.0011(0011 * 11次)0100 , 这个值算成十进制就是 0.30000000000000004

下面说一下原生解决办法,如下代码所示

parseFloat((0.1 + 0.2).toFixed(10))

类型string

定义

String 类型的独特之处在于,它是唯一没有固定大小的原始类型。可以用字符串存储 0 或更多的 Unicode 字符,有 16 位整数表示 字符串中每个字符都有特定的位置,首字符从位置 0 开始,第二个字符在位置 1,依此类推。这意味着字符串中的最后一个字符的位置一定是字符串的长度减 1:

转义序列

字面量 含义
\n 换行
\t 制表符
\b 空格
\r 回车
\ \ 反斜杠
\ ' 单引号
" 双引号

String的api

ref: 对象 String

字符串与数组

字符串可以被视为字符数组,因此可以使用数组的方括号运算符,用来返回某个位置的字符(位置编号从0开始)。

var s = 'hello';
s[0] // "h"
s[1] // "e"
s[4] // "o"

// 直接对字符串使用方括号运算符
'hello'[1] // "e"

如果方括号中的数字超过字符串的长度,或者方括号中根本不是数字,则返回undefined。

'abc'[3] // undefined
'abc'[-1] // undefined
'abc'['x'] // undefined

但是,字符串与数组的相似性仅此而已。实际上,无法改变字符串之中的单个字符

var s = 'hello';

delete s[0];
s // "hello"

s[1] = 'a';
s // "hello"

s[5] = '!';
s // "hello"

关联

逻辑运算符

&& || ref 逻辑运算符

es6 模板串

类型boolean

定义

Boolean 类型是 ECMAScript 中最常用的类型之一。它有两个值 true 和 false (即两个 Boolean 字面量)

历史

名称来源

该类型的名称是为了纪念英国数学家George Boole

出版了《逻辑的数学分析》 ,这是它对符号逻辑诸多贡献中的第一次。1854年,他出版了《思维规律的研究》 ,这是他最著名的著作。在这本书中布尔介绍了现在以他的名字命名的布尔代数。

转换

为false的

(anki)

  • undefined
  • null
  • false
  • NaN
  • ''
  • 0
  • -0

为true

除了上面都是true 包括

  • []
  • {}

关联

typeof

typeof true // 'boolean'

逻辑运算

ref 逻辑运算符

类型symbol

定义

ES6引入了一种新的原始数据类型Symbol(标志),表示独一无二的值,他是js的第七种数据类型,前六种是: undefined ,null ,布尔值(Boolean),字符串(String)数值(Number),对象(Object)。 symbol值通过Symbol函数生成,也就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种是新增的symbol类型,凡是属性名属于symbol类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。

let s  = Symbol();
typeof s
//'symbol'

变量s就是一个独一无二的值。typeof运算符的结果,表明变量s是 Symbol 数据类型,而不是字符串之类的其他类型。

Symbol函数前不能使用new命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象。也就是说,由于 Symbol 值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。

Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。

let s1 = Symbol('foo');
let s2 = Symbol('bar');

s1 // Symbol(foo)
s2 // Symbol(bar)

s1.toString() // "Symbol(foo)"
s2.toString() // "Symbol(bar)"

s1和s2是两个 Symbol 值。如果不加参数,它们在控制台的输出都是Symbol(),不利于区分。有了参数以后,就等于为它们加上了描述,输出的时候就能够分清,到底是哪一个值。

// 没有参数的情况

let s1 = Symbol();
let s2 = Symbol();

s1 === s2 // false

// 有参数的情况
let s1 = Symbol('foo');
let s2 = Symbol('foo');

s1 === s2 // false

Symbol 值不能与其他类型的值进行运算,会报错。Symbol 值可以显式转为字符串。另外,Symbol 值也可以转为布尔值,但是不能转为数值。

let sym = Symbol('My symbol');
String(sym) // 'Symbol(My symbol)'

Number(sym) // TypeError
sym + 2 // TypeError

let sym = Symbol();
Boolean(sym) // true
!sym  // false

作为属性名的symbol 由于每一个 Symbol 值都是不相等的,这意味着 Symbol 值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。

let mySymbol = Symbol();

// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';

// 第二种写法
let a = {
  [mySymbol]: 'Hello!'
};

// 第三种写法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });

// 以上写法都得到同样结果
a[mySymbol] // "Hello!"

Symbol 值作为对象属性名时,不能用点运算符。 在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中。 属性名的遍历 symbol作为属性名,该属性不会出现在for ...in,for...of 循环中,也不会被Object.keys(),Object.getOwnPrototypeNames(),JSON.stringify()返回,但是他也不是私有属性,有一个Object.getOwnPropertySymbols方法,可以获取指定对象的所有 Symbol 属性名。

使用情况

ref

作为属性名的使用

var mySymbol = Symbol();
// 第一种写法
var a = {};
a[mySymbol] = 'Hello!';
// 第二种写法
var a = { [mySymbol]: 'Hello!'};
// 第三种写法
var a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });
// 以上写法都得到同样结果
a[mySymbol] // "Hello!"

为什么要使用Symbol?

那么问题来了,究竟为什么要使用Symbol呢?有这样一种场景,我们想区分两个属性,其实我们并不在意,这两个属性值究竟是什么,我们在意的是,这两个属性绝对要区分开来!例如:

var shapeType = { triangle: 'Triangle'};
function getArea(shape, options) { 
    var area = 0; 
    switch (shape) { 
      case shapeType.triangle:
      area = .5 * options.width * options.height; 
      break; 
    } 
    return area;
}

这个时候,我们仅仅是想区分各种形状,因为不同的形状用不同的计算面积的公式。这里使用的是triangle的名字叫做‘Triangle’,而是事实上我们不想对triangle去特地取个名,我们只想要区分triangle这个形状不同于任何其他形状,那么这个时候Symbol就派上用场啦!

const shapeType = {
   triangle: Symbol()
};

发现了吗? 也就是说,我们不用非要去给变量赋一个字符串的值,去区分它和别的变量的值不同,因为去给每个变量取个语义化而又不同的值是一件伤脑子的事,当我们只需要知道每个变量的值都是百分百不同的即可,这时候我们就可以用Symbol。

对象Object

概述

什么是对象?简单说,对象就是一组“键值对”(key-value)的集合,是一种无序的复合数据集合。 所有Array,Date,Function的原型是Object。(ref:原型链 toDo)

生成方式

var obj = {
  foo: 'Hello',
  bar: 'World'
};

键名

所有键名都是字符串,如果不是会转成字符串。比较复杂的可以使用es6的Map

对象的每一个键名又称为“属性”(property)

键值

“键值”可以是任何数据类型。如果一个属性的值为函数,通常把这个属性称为“方法”,它可以像函数那样调用

var o1 = {};
var o2 = { bar: 'hello' };

o1.foo = o2;
o1.foo.bar // "hello"

上面代码中,对象o1的属性foo指向对象o2,就可以链式引用o2的属性。

对象的引用

如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址。修改其中一个变量,会影响到其他所有变量。

var o1 = {};
var o2 = o1;

o1.a = 1;
o2.a // 1

o2.b = 2;
o1.b // 2

属性操作

属性的读取

读取对象的属性,有两种方法,一种是使用点运算符,还有一种是使用方括号运算符。

var obj = {
  p: 'Hello World'
};

obj.p // "Hello World"
obj['p'] // "Hello World"

==请注意,如果使用方括号运算符,键名必须放在引号里面,否则会被当作变量处理==

var foo = 'bar';

var obj = {
  foo: 1,
  bar: 2
};

obj.foo  // 1
obj[foo]  // 2

上面代码中,引用对象obj的foo属性时,如果使用点运算符,foo就是字符串;如果使用方括号运算符,但是不使用引号,那么foo就是一个变量,指向字符串bar。

方括号运算符内部还可以使用表达式。

obj['hello' + ' world']
obj[3 + 3]

数值键名不能使用点运算符(因为会被当成小数点),只能使用方括号运算符。

var obj = {
  123: 'hello world'
};

obj.123 // 报错
obj[123] // "hello world"

属性的赋值

点运算符和方括号运算符,不仅可以用来读取值,还可以用来赋值。

var obj = {};
obj.foo = 'Hello';
obj['bar'] = 'World';

属性键名查看

var obj = {
  key1: 1,
  key2: 2
};

Object.keys(obj);
// ['key1', 'key2']

属性删除

delete命令用于删除对象的属性,删除成功后返回true。

var obj = { p: 1 };
Object.keys(obj) // ["p"]

delete obj.p // true
obj.p // undefined
Object.keys(obj) // []

属性是否存在 in运算符

in运算符用于检查对象是否包含某个属性(注意,检查的是键名,不是键值),如果包含就返回true,否则返回false。它的左边是一个字符串,表示属性名,右边是一个对象。

var obj = { p: 1 };
'p' in obj // true
'toString' in obj // true

in运算符的一个问题是,它不能识别哪些属性是对象自身的,哪些属性是继承的。就像上面代码中,对象obj本身并没有toString属性,但是in运算符会返回true,因为这个属性是继承的。

这时,可以使用对象的hasOwnProperty方法判断一下,是否为对象自身的属性。

var obj = {};
if ('toString' in obj) {
  console.log(obj.hasOwnProperty('toString')) // false
}

属性遍历 for。。。in

for...in循环用来遍历一个对象的全部属性。

var obj = {a: 1, b: 2, c: 3};

for (var i in obj) {
  console.log('键名:', i);
  console.log('键值:', obj[i]);
}
// 键名: a
// 键值: 1
// 键名: b
// 键值: 2
// 键名: c
// 键值: 3

Object方法

Object对象的原生方法分成两类:Object本身的方法与Object的实例方法。 image

静态方法

所谓”本身的方法“就是直接定义在Object对象的方法。 静态方法就是本身方法

Object.print = function (o) { console.log(o) };

Object.keys

Object.keys方法的参数是一个对象,返回一个数组。该数组的成员都是该对象自身的(而不是继承的)所有属性名。

var obj = {
  p1: 123,
  p2: 456
};

Object.keys(obj) // ["p1", "p2"]

getOwnPropertyNames

Object.getOwnPropertyNames方法与Object.keys类似,也是接受一个对象作为参数,返回一个数组,包含了该对象自身的所有属性名。

var obj = {
  p1: 123,
  p2: 456
};

Object.getOwnPropertyNames(obj) // ["p1", "p2"]

对于一般的对象来说,Object.keys()和Object.getOwnPropertyNames()返回的结果是一样的。只有涉及不可枚举属性时,才会有不一样的结果。Object.keys方法只返回可枚举的属性ref: 对象属性描述,Object.getOwnPropertyNames方法还返回不可枚举的属性名。

var a = ['Hello', 'World'];

Object.keys(a) // ["0", "1"]
Object.getOwnPropertyNames(a) // ["0", "1", "length"]

其他静态方法

参考 ref 对象属性描述 对象属性模型的相关方法 -Object.getOwnPropertyDescriptor():获取某个属性的描述对象。

  • Object.defineProperty():通过描述对象,定义某个属性。
  • Object.defineProperties():通过描述对象,定义多个属性。

控制对象状态的方法

  • Object.preventExtensions():防止对象扩展
  • Object.isExtensible():判断对象是否可扩展。
  • Object.seal():禁止对象配置。
  • Object.isSealed():判断一个对象是否可配置。
  • Object.freeze():冻结一个对象。
  • Object.isFrozen():判断一个对象是否被冻结。

实例方法

所谓实例方法就是定义在Object原型对象Object.prototype上的方法。它可以被Object实例直接使用。

Object.prototype.print = function () {
  console.log(this);
};

var obj = new Object();
obj.print() // Object

valueOf()

valueOf方法的作用是返回一个对象的“值”,默认情况下返回对象本身。

var obj = new Object();
obj.valueOf() === obj // true

valueOf方法的主要用途是,JavaScript 自动类型转换时会默认调用这个方法 ref 类型转换

toString

方法的作用是返回一个对象的字符串形式,默认情况下返回类型字符串

var o1 = new Object();
o1.toString() // "[object Object]"

var o2 = {a:1};
o2.toString() // "[object Object]"

上面代码调用空对象的toString方法,结果返回一个字符串object Object,其中第二个Object表示该值的构造函数。这是一个十分有用的判断数据类型的方法。

ref 类型判断

toLocaleString()

Object.prototype.toLocaleString方法与toString的返回结果相同,也是返回一个值的字符串形式。

  • Array.prototype.toLocaleString() // 测试暂时一样
  • Number.prototype.toLocaleString() // 测试暂时一样
  • Date.prototype.toLocaleString() // 不一样
var date = new Date();
date.toString() // "Tue Jan 01 2018 12:01:33 GMT+0800 (CST)"
date.toLocaleString() // "1/01/2018, 12:01:33 PM"

hasOwnProperty()

Object.prototype.hasOwnProperty方法接受一个字符串作为参数,返回一个布尔值,表示该实例对象自身是否具有该属性。

var obj = {
  p: 123
};

obj.hasOwnProperty('p') // true
obj.hasOwnProperty('toString') // false

上面代码中,对象obj自身具有p属性,所以返回true。toString属性是继承的,所以返回false。

Object描述

image

概述

JavaScript 提供了一个内部数据结构,用来描述对象的属性,控制它的行为,比如该属性是否可写、可遍历等等。这个内部数据结构称为“属性描述对象”(attributes object)。每个属性都有自己对应的属性描述对象,保存该属性的一些元信息

{
  value: 123,
  writable: false,
  enumerable: true,
  configurable: false,
  get: undefined,
  set: undefined
}

元属性

value

value属性是目标属性的值。

var obj = {};
obj.p = 123;

Object.getOwnPropertyDescriptor(obj, 'p').value
// 123

Object.defineProperty(obj, 'p', { value: 246 });
obj.p // 246
writable

writable属性是一个布尔值,决定了目标属性的值(value)是否可以被改变

var obj = {};

Object.defineProperty(obj, 'a', {
  value: 37,
  writable: false
});

obj.a // 37
obj.a = 25;
obj.a // 37

上面代码中,obj.a的writable属性是false。然后,改变obj.a的值,不会有任何效果

enumerable

enumerable(可遍历性)返回一个布尔值,表示目标属性是否可遍历

  • for..in循环
  • Object.keys
  • JSON.stringify
configurable

configurable(可配置性)返回一个布尔值,决定了是否可以修改属性描述对象。也就是说,configurable为false时,value、writable、enumerable和configurable都不能被修改了。

存取器

一旦对目标属性定义了存取器,那么存取的时候,都将执行对应的函数。利用这个功能,可以实现许多高级特性,比如某个属性禁止赋值。

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: 新的值必须大于当前值

实际使用地方 vue响应的核心 ref

方法

getOwnPropertyDescriptor

Object.getOwnPropertyDescriptor()方法可以获取属性描述对象。它的第一个参数是目标对象,第二个参数是一个字符串,对应目标对象的某个属性名。

var obj = { p: 'a' };

Object.getOwnPropertyDescriptor(obj, 'p')
// Object { value: "a",
//   writable: true,
//   enumerable: true,
//   configurable: true
// }

上面代码中,Object.getOwnPropertyDescriptor()方法获取obj.p的属性描述对象

注意,Object.getOwnPropertyDescriptor()方法只能用于==对象自身的属性,不能用于继承的属性==

getOwnPropertyNames()

Object.getOwnPropertyNames方法返回一个数组,成员是参数对象自身的全部属性的属性名,不管该属性是否可遍历。

var obj = Object.defineProperties({}, {
  p1: { value: 1, enumerable: true },
  p2: { value: 2, enumerable: false }
});

Object.getOwnPropertyNames(obj)
// ["p1", "p2"]
defineProperty() defineProperties()

Object.defineProperty()方法允许通过属性描述对象,定义或修改一个属性,然后返回修改后的对象,它的用法如下。

Object.defineProperty(object, propertyName, attributesObject)

Object.defineProperty方法接受三个参数

参数 描述
object 属性所在的对象
propertyName 字符串,表示属性名
attributesObject 属性描述对象

如果一次性定义或修改多个属性,可以使用Object.defineProperties()方法。

var obj = Object.defineProperties({}, {
  p1: { value: 123, enumerable: true },
  p2: { value: 'abc', enumerable: true },
  p3: { get: function () { return this.p1 + this.p2 },
    enumerable:true,
    configurable:true
  }
});

obj.p1 // 123
obj.p2 // "abc"
obj.p3 // "123abc"
propertyIsEnumerable()

例对象的propertyIsEnumerable()方法返回一个布尔值,用来判断某个属性是否可遍历。注意,这个方法只能用于判断对象自身的属性,对于继承的属性一律返回false。

var obj = {};
obj.p = 123;

obj.propertyIsEnumerable('p') // true
obj.propertyIsEnumerable('toString') // false

控制对象状态

preventExtensions()

Object.preventExtensions方法可以使得一个对象无法再添加新的属性。

var obj = new Object();
Object.preventExtensions(obj);

Object.defineProperty(obj, 'p', {
  value: 'hello'
});
// TypeError: Cannot define property:p, object is not extensible.

obj.p = 1;
obj.p // undefined
isExtensible()

Object.isExtensible方法用于检查一个对象是否使用了Object.preventExtensions方法。也就是说,检查是否可以为一个对象添加属性。

var obj = new Object();

Object.isExtensible(obj) // true
Object.preventExtensions(obj);
Object.isExtensible(obj) // false

上面代码中,对obj对象使用Object.preventExtensions方法以后,再使用Object.isExtensible方法,返回false,表示已经不能添加新属性了。

seal()

Object.seal方法使得一个对象既无法添加新属性,也无法删除旧属性。

var obj = { p: 'hello' };
Object.seal(obj);

delete obj.p;
obj.p // "hello"

obj.x = 'world';
obj.x // undefined

上面代码中,obj对象执行Object.seal方法以后,就无法添加新属性和删除旧属性了。

Object.seal实质是把属性描述对象的configurable属性设为false,因此属性描述对象不再能改变了。

isSealed()

Object.isSealed方法用于检查一个对象是否使用了Object.seal方法。

var obj = { p: 'a' };

Object.seal(obj);
Object.isSealed(obj) // true

Object.seal只是禁止新增或删除属性,并不影响修改某个属性的值。

var obj = { p: 'a' };
Object.seal(obj);
obj.p = 'b';
obj.p // 'b'
isSealed()

Object.isSealed方法用于检查一个对象是否使用了Object.seal方法

var obj = { p: 'a' };

Object.seal(obj);
Object.isSealed(obj) // true

这时,Object.isExtensible方法也返回false

var obj = { p: 'a' };

Object.seal(obj);
Object.isExtensible(obj) // false
freeze()

Object.freeze方法可以使得一个对象无法添加新属性、无法删除旧属性、也无法改变属性的值,使得这个对象实际上变成了常量。

var obj = {
  p: 'hello'
};

Object.freeze(obj);

obj.p = 'world';
obj.p // "hello"

obj.t = 'hello';
obj.t // undefined

delete obj.p // false
obj.p // "hello"

上面代码中,对obj对象进行Object.freeze()以后,修改属性、新增属性、删除属性都无效了。这些操作并不报错,只是默默地失败。如果在严格模式下,则会报错。

isFrozen()

Object.isFrozen方法用于检查一个对象是否使用了Object.freeze方法。

var obj = {
  p: 'hello'
};

Object.freeze(obj);
Object.isFrozen(obj) // true

对象String

操作

image

拼接

concat()

定义和用法: concat() 方法用于连接两个或多个字符串。

语法:
stringObject.concat(stringX,stringX,...,stringX)

参数 描述
stringX 必需。将被连接为一个字符串的一个或多个字符串对象。

demo

var str1="Hello "
var str2="world!"
console.log(str1.concat(str2)) // Hello world!

截取

slice()

定义和用法: slice() 方法可提取字符串的某个部分,并以新的字符串返回被提取的部分。(可以负数)

语法:
stringObject.slice(start,end)

参数 描述
start 开始位置,可以负数,负数从后面开始
end 结束位置,可负数,省略到结尾

demo

var str="Hello happy world!"
console.log(str.slice(6,11)) // happy

substring()

定义和用法: substring() 方法用于提取字符串中介于两个指定下标之间的字符 语法:
stringObject.substring(start,stop)

参数 描述
start 开始位置,不能负数
end 结束位置,不能负数,省略到结尾

demo

var str="Hello world!"
console.log(str.substring(3,7))

substr

定义和用法 substr() 方法可在字符串中抽取从 start 下标开始的指定数目的字符。

语法
stringObject.substr(start,length)

参数 描述
start 开始位置,可以负数
end 结束位置,不能负数,省略到结尾

注意 重要事项:ECMAscript 没有对该方法进行标准化,因此反对使用它。

demo

var str="Hello world!"
console.log(str.substr(3)) // lo world!

空格处理

trim

清除前后空格

trimLeft

清除前面空格

trimRight

清除后面空格

其他

split()

定义和用法 split() 方法用于把一个字符串分割成字符串数组。

语法 stringObject.split(分隔符,最大数组length)

参数 描述
分隔符 必需。字符串或正则表达式
最大数组length 最大长度,设置后分割不会多于此长度,没有的话就不考虑长度

提示和注释

  • 如果把空字符串 ("") 用作 separator,那么 stringObject 中的每个字符之间都会被分割。
  • String.split() 执行的操作与 Array.join 执行的操作是相反的。

repeat

定义和用法 repeat方法返回一个新字符串,表示将原字符串重复n次。 语法 stringObject.repeat(重复次数)

参数 描述
重复次数 小数取整(向下)

补全

padStart() padEnd()

定义 padStart()用于头部补全,padEnd()用于尾部补全。 语法

stringObject.padStart(长度,字符串)
stringObject.padEnd(长度,字符串)
参数 描述
长度 字符串最短长度
字符串 补全字符串,超出会截取

demo 超出

'abc'.padStart(10, '0123456789') // '0123456abc'

padStart的常见用途是为数值补全指定位数。下面代码生成 10 位的数值字符串。

'1'.padStart(10, '0') // "0000000001"
'12'.padStart(10, '0') // "0000000012"
'123456'.padStart(10, '0') // "0000123456"

一个用途是提示字符串格式。

'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"
'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"

查找

image

位置查找

image

indexOf()

定义和用法 indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。

语法:
stringObject.indexOf(查询字段,开始位置)

参数 描述
查询字段 必需。规定需检索的字符串值。
开始位置 可选,开始位置,位置(0~len-1)省略从头开始

提示和注释

  • indexOf() 方法对大小写敏感!
  • 如果要检索的字符串值没有出现,则该方法返回 -1。
  • 不能正则

demo

var str="Hello world!"
document.write(str.indexOf("Hello")) // 0
document.write(str.indexOf("World")) // -1
document.write(str.indexOf("world")) // 6

lastIndexOf()

定义和用法: indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。

语法:
stringObject.lastIndexOf(searchvalue,fromindex)

参数 描述
查询字段 必需。规定需检索的字符串值。
开始位置 可选,开始位置,位置(0~len-1)省略从最后一个字符开始

提示和注释

  • indexOf() 方法对大小写敏感!
  • 如果要检索的字符串值没有出现,则该方法返回 -1。、
  • 不能正则

demo

var str="Hello world!"
document.write(str.lastIndexOf("Hello")) // 0
document.write(str.lastIndexOf("World")) // -1
document.write(str.lastIndexOf("world")) // 6

匹配

image

match()

定义和用法: match() 方法可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。它返回指定的值(是个数组),没有匹配将返回 null

语法:

stringObject.match(字符串)
stringObject.match(regexp)
参数 描述
字符串 必需。规定要检索的字符串值。
regexp 规定要匹配的模式的 RegExp 对象

提示和注释 找到一个或多个与 regexp 匹配的文本,赖于 regexp 是否具有标志 g

  • 没有标志 g 执行一次匹配。返回数组。返回内容看demo
  • 具有标志 g 执行全局检索,找到 stringObject 中的所有匹配子字符串。若没有找到任何匹配的子串,则返回 null。

demo

var str="1 plus 2 equal 3"
console.log(str.match(/\d+/g))
// ["1", "2", "3"]
console.log(str.match(/\d+/))
// ["1", index: 0, input: "1 plus 2 equal 3", groups: undefined]

定义和用法 search() 方法用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串.返回值,stringObject 中第一个与 regexp 相匹配的子串的起始位置。如果没有找到任何匹配的子串,则返回 -1

语法
stringObject.search(regexp)

参数 描述
regexp 规定要匹配的模式的 RegExp 对象

提示和注释 search() 方法不执行全局匹配,它将忽略标志 g。它同时忽略 regexp 的 lastIndex 属性,并且总是从字符串的开始进行检索,这意味着它总是返回 stringObject 的第一个匹配的位置。

demo

var str="Visit W3School!"
console.log(str.search(/W3School/) // 6
console.log(str.search(/w3school/) // -1

replace()

定义和用法 replace() 方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。返回一个新的字符串,是用 replacement 替换了 regexp 的第一次匹配或所有匹配之后得到的。

语法:

stringObject.replace(regexp/substr,replacement)
参数 描述
regexp/substr 必需。规定子字符串或要替换的模式的 RegExp 对象
replacement 必需。一个字符串值。规定了替换文本或生成替换文本的函数。

提示和注释 字符串 stringObject 的 replace() 方法执行的是查找并替换的操作。它将在 stringObject 中查找与 regexp 相匹配的子字符串,然后用 replacement 来替换这些子串。如果 regexp 具有全局标志 g,那么 replace() 方法将替换所有匹配的子串。否则,它只替换第一个匹配子串。 replacement

字符 替换文本
$1、$2、...、$99 与 regexp 中的第 1 到第 99 个子表达式相匹配的文本。
$& 与 regexp 相匹配的子串。
$` 位于匹配子串左侧的文本。
$' 位于匹配子串右侧的文本。
$$ 直接量符号。

demo

var str="Welcome to Microsoft! "
str=str + "We are proud to announce that Microsoft has "
str=str + "one of the largest Web Developers sites in the world."

document.write(str.replace(/Microsoft/g, "W3School"))

// Welcome to W3School! We are proud to announce that W3School has one of the largest Web Developers sites in the world.

在本例中,我们将把 "Doe, John" 转换为 "John Doe" 的形式:

name = "Doe, John";
name.replace(/(\w+)\s*, \s*(\w+)/, "$2 $1");

包含

includes()

返回布尔值,表示是否找到了参数字符串

startsWith()

返回布尔值,表示参数字符串是否在原字符串的头部

endsWith()

返回布尔值,表示参数字符串是否在原字符串的尾部

对象Array

思维图

image

添加

push

定义和用法 push() 方法可向数组的末尾添加一个或多个元素,并返回新的长度。push() 方法可向数组的末尾添加一个或多个元素,并返回新的长度。

语法

arrayObject.push(元素1,元素x,....)

demo

const array = []
array.push(1)
console.log(array) //=> [1]

array.push(2, 3)
console.log(array) //=> [1, 2, 3]
console.log(array.length) //=> 3

unshift

定义和用法 unshift() 方法可向数组的开头添加一个或更多元素,并返回新的长度。

语法 arrayObject.push(元素1,元素x,....)

注意 unshift() 方法将把它的参数插入 arrayObject 的头部,并将已经存在的元素顺次地移到较高的下标处,以便留出空间。该方法的第一个参数将成为数组的新元素 0,如果还有第二个参数,它将成为新的元素 1,以此类推。 demo

const array = [ 4, 5 ]
array.unshift(3)
console.log(array) //=> [3, 4, 5]

array.unshift(1, 2)
console.log(array) //=> [1, 2, 3, 4, 5] 

concat

定义和用法 concat() 方法用于连接两个或多个数组。该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。可以用在数组拷贝

语法

arrayObject.concat(arrayX,arrayX,......,arrayX)

注意 该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。可以用在数组拷贝。

demo

var arr = new Array(3)
arr[0] = "George"
arr[1] = "John"
arr[2] = "Thomas"

var arr2 = new Array(3)
arr2[0] = "James"
arr2[1] = "Adrew"
arr2[2] = "Martin"

var arr3 = new Array(2)
arr3[0] = "William"
arr3[1] = "Franklin"

document.write(arr.concat(arr2,arr3))
// George,John,Thomas,James,Adrew,Martin,William,Franklin

删除

pop

定义和用法 pop() 方法用于删除并返回数组的最后一个元素。返回arrayObject 的最后一个元素。

语法 arrayObject.pop()

注意 pop() 方法将删除 arrayObject 的最后一个元素,把数组长度减 1,并且返回它删除的元素的值。如果数组已经为空,则 pop() 不改变数组,并返回 undefined 值。

demo

var fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.pop();// Banana,Orange,Apple

shift

定义和用法 shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。

语法 arrayObject.shift()

注意 如果数组是空的,那么 shift() 方法将不进行任何操作,返回 undefined 值。请注意,该方法不创建新数组,而是直接修改原有的 arrayObject。该方法会改变数组的长度。

demo

var fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.shift()

子数组

splice

定义和用法 splice() 方法向/从数组中添加/删除项目,然后返回被删除的项目。该方法会改变原始数组。

语法 arrayObject.splice(index,del,item1,.....,itemX)

参数 描述
index 规定添加/删除项目的位置
del 必需。要删除的项目数量。如果设置为 0,则不会删除项目
item1, ..., itemX 可选。向数组添加的新项目。

注意 splice() 方法可删除从 index 处开始的零个或多个元素,并且用参数列表中声明的一个或多个值来替换那些被删除的元素。

如果从 arrayObject 中删除了元素,则返回的是含有被删除的元素的数组。

demo

var fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.splice(2,0,"Lemon","Kiwi");
//Banana,Orange,Lemon,Kiwi,Apple,Mango

slice

定义和用法 slice() 方法可从已有的数组中返回选定的元素。返回一个新的数组,包含从 start 到 end (不包括该元素)的 arrayObject 中的元素。

语法 arrayObject.slice(start,end)

参数 描述
start 必需。规定从何处开始选取。可负
end 何时结束,可负。

注意 请注意,该方法并不会修改数组,而是返回一个子数组。如果想删除数组中的一段元素。用于浅拷贝

demo

var fruits = ["Banana", "Orange", "Lemon", "Apple", "Mango"];
var citrus = fruits.slice(1,3);
//Orange,Lemon

join

定义和用法 join() 方法用于把数组中的所有元素放入一个字符串。元素是通过指定的分隔符进行分隔的。

语法 arrayObject.join(分隔符)

参数 描述
分隔符 可选。指定要使用的分隔符。如果省略该参数,则使用逗号作为分隔符。

注意 请注意,该方法并不会修改数组,而是返回一个子数组。如果想删除数组中的一段元素。用于浅拷贝

demo

var fruits = ["Banana", "Orange", "Apple", "Mango"];
var energy = fruits.join();
//Banana,Orange,Apple,Mango

排序

reverse

定义和用法 reverse() 方法用于颠倒数组中元素的顺序。

语法 arrayObject.reverse()

demo

var fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.reverse();
// Mango,Apple,Orange,Banana

sort

定义和用法 sort() 方法用于对数组的元素进行排序。

语法 arrayObject.sort(sortby)

参数 描述
sortby 可选。规定排序顺序。必须是函数。

注意 对数组的引用。请注意,数组在原数组上进行排序,不生成副本。

  • 大于0 调换。
  • <0 不处理

demo

var fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.sort();
//Apple,Banana,Mango,Orange

查询

indexOf

定义和用法 indexOf() 方法可返回数组中某个指定的元素位置。

该方法将从头到尾地检索数组,看它是否含有对应的元素。开始检索的位置在数组 start 处或数组的开头(没有指定 start 参数时)。如果找到一个 item,则返回 item 的第一次出现的位置。开始位置的索引为 0。

如果在数组中没找到指定元素则返回 -1。 语法 array.indexOf(item,start)

参数 描述
item 必须。查找的元素。
start 可选的整数参数。规定在字符串中开始检索的位置。

注意 没有返回值为-1,和find返回undefined不一样

demo

var fruits=["Banana","Orange","Apple","Mango","Banana","Orange","Apple"];
var a = fruits.indexOf("Apple",4); //6

lastIndexOf

定义和用法 lastIndexOf() 方法可返回一个指定的元素在数组中最后出现的位置,在一个数组中的指定位置从后向前搜索。

如果要检索的元素没有出现,则该方法返回 -1。

该方法将从尾到头地检索数组中指定元素 item。开始检索的位置在数组的 start 处或数组的结尾(没有指定 start 参数时)。如果找到一个 item,则返回 item 从尾向前检索第一个次出现在数组的位置。数组的索引开始位置是从 0 开始的。

如果在数组中没找到指定元素则返回 -1。 语法 array.lastIndexOf(item,start)

参数 描述
item 必须。查找的元素。
start 可选的整数参数。规定在字符串中开始检索的位置。

注意 没有返回值为-1,和find返回undefined不一样

demo

var fruits = ["Banana", "Orange", "Apple", "Mango"];
var a = fruits.lastIndexOf("Apple"); //2

find

定义和用法 find() 方法返回通过测试(函数内判断)的数组的第一个元素的值。

find() 方法为数组中的每个元素都调用一次函数执行 start

  • 当数组中的元素在测试条件时返回 true 时, find() 返回符合条件的元素,之后的值不会再调用执行函数。
  • 如果没有符合条件的元素返回 undefined

语法 array.find(function(currentValue, index, arr))

参数 描述
currentValue 必需。当前元素
index 可选。当前元素的索引值
arr 可选。当前元素所属的数组对象

注意

  • find() 对于空数组,函数是不会执行的。
  • find() 并没有改变数组的原始值。

demo

var ages = [3, 10, 18, 20];
 
function checkAdult(age) {
    return age >= 18;
}
 
console.log(ages.find(checkAdult)) //18

findIndex

定义和用法 findIndex() 方法返回传入一个测试条件(函数)符合条件的数组第一个元素位置。

findIndex() 方法为数组中的每个元素都调用一次函数执行:

  • 当数组中的元素在测试条件时返回 true 时, findIndex() 返回符合条件的元素的索引位置,之后的值不会再调用执行函数
  • 如果没有符合条件的元素返回 -1

语法 array.findIndex(function(currentValue, index, arr))

参数 描述
currentValue 必需。当前元素
index 可选。当前元素的索引值
arr 可选。当前元素所属的数组对象

注意

  • findIndex() 对于空数组,函数是不会执行的。
  • findIndex() 并没有改变数组的原始值。

demo

var ages = [3, 10, 18, 20];
 
function checkAdult(age) {
    return age >= 18;
}
 
console.log(ages.findIndex(checkAdult)) //2

includes

定义和用法 includes() 方法用来判断一个数组是否包含一个指定的值,如果是返回 true,否则false。

语法 arr.includes(searchElement, fromIndex)

参数 描述
searchElement 必须。需要查找的元素值。
fromIndex 可选。从该索引处开始查找 searchElement。

注意

  • findIndex() 对于空数组,函数是不会执行的。
  • findIndex() 并没有改变数组的原始值。

demo

[1, 2, 3].includes(2);     // true
[1, 2, 3].includes(4);     // false
[1, 2, 3].includes(3, 3);  // false
[1, 2, 3].includes(3, -1); // true
[1, 2, NaN].includes(NaN); // true

Iterator

Iterator

遍历

image

forEach()

定义和用法 forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数。

语法 array.forEach(function(currentValue, index, arr))

参数 描述
currentValue 必需。当前元素
index 可选。当前元素的索引值。
arr 可选。当前元素所属的数组对象。

image 注意 forEach() 对于空数组是不会执行回调函数的。

demo

var numbers = [65, 44, 12, 4];
numbers.forEach(item => console.log(item))
filter

定义和用法 array.filter(function(currentValue,index,arr))

语法 array.forEach(function(currentValue, index, arr))

参数 描述
currentValue 必需。当前元素
index 可选。当前元素的索引值。
arr 可选。当前元素所属的数组对象。

image 注意

  • filter() 不会对空数组进行检测。
  • filter() 不会改变原始数组。

demo

var numbers = [65, 44, 12, 4];
var newNum =numbers.filter(item => return { item > 13})
console.log(newNum) // [65, 44]

map

定义和用法 array.map(function(currentValue,index,arr))

参数 描述
currentValue 必需。当前元素
index 可选。当前元素的索引值。
arr 可选。当前元素所属的数组对象。

image

注意

  • map() 不会对空数组进行检测。
  • map() 不会改变原始数组。

demo

var numbers = [65, 44, 12, 4];
var newNum =numbers.filter(item => return { item * 2})
console.log(newNum) 
// [130, 88, 24, 8]

reduce

定义和用法 reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。 reduce() 可以作为一个高阶函数,用于函数的 compose。

语法 array.reduce(function(total, currentValue, currentIndex, arr))

参数 描述
total 必需。初始值, 或者计算结束后的返回值
currentValue 必需。当前元素
currentIndex 可选。当前元素的索引
arr 可选。当前元素所属的数组对象。

image 注意

  • reduce() 不会对空数组进行检测。
  • reduce() 不会改变原始数组。

demo

var numbers = [1, 2, 3, 4];
var sum =numbers.reduce((total, item) => return { total+ item})
console.log(newNum) // 10

every

定义和用法 every() 方法用于检测数组所有元素是否都符合指定条件(通过函数提供) every() 方法使用指定函数检测数组中的所有元素:

  • 如果数组中检测到有一个元素不满足,则整个表达式返回 false ,且剩余的元素不会再进行检测。
  • 如果所有元素都满足条件,则返回 true。 语法 array.reduce(function(total, currentValue, currentIndex, arr))
参数 描述
currentValue 必需。当前元素
index 可选。当前元素的索引值。
arr 可选。当前元素所属的数组对象。

image 注意

  • every() 不会对空数组进行检测。
  • every() 不会改变原始数组。

demo

var numbers = [ 12, 32, 33, 40];
console.log(numbers.every( item => return {item >32})) // false

some

定义和用法 every() 方法用于检测数组所有元素是否都符合指定条件(通过函数提供) every() 方法使用指定函数检测数组中的所有元素:

  • 如果有一个元素满足条件,则表达式返回true , 剩余的元素不会再执行检测。。
  • 如果没有满足条件的元素,则返回false。 image

语法 array.some(function(currentValue,index,arr))

参数 描述
currentValue 必需。当前元素
index 可选。当前元素的索引值。
arr 可选。当前元素所属的数组对象。

注意

  • some() 不会对空数组进行检测。
  • some() 不会改变原始数组。

demo

var numbers = [ 12, 32, 33, 40];
console.log(numbers.every( item => return {item >32})) // true

遍历

keys()

keys()是对键名的遍历

for (let index of ['a', 'b'].keys()) {
  console.log(index);
}
// 0
// 1

values()

values()是对键值的遍历

for (let elem of ['a', 'b'].values()) {
  console.log(elem);
}
// 'a'
// 'b'

entries()

entries()是对键值对的遍历

for (let [index, elem] of ['a', 'b'].entries()) {
  console.log(index, elem);
}
// 0 "a"
// 1 "b"

原型链使用

上面这些数组方法之中,有不少返回的还是数组,所以可以链式使用。

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);
});

高阶函数

toDo

扩展运算符

含义 扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列

console.log(...[1, 2, 3])
// 1 2 3

[...document.querySelectorAll('div')]
// [<div>, <div>, <div>]

用途 Iterator 接口的对象

let nodeList = document.querySelectorAll('div');
let array = [...nodeList];

替代函数的 apply 方法

// ES5 的写法
function f(x, y, z) {
  // ...
}
var args = [0, 1, 2];
f.apply(null, args);

// ES6的写法
function f(x, y, z) {
  // ...
}
let args = [0, 1, 2];
f(...args);

如下

// ES5 的写法
Math.max.apply(null, [14, 3, 77])

// ES6 的写法
Math.max(...[14, 3, 77])

// 等同于
Math.max(14, 3, 77);

复制数组 ES5 只能用变通方法来复制数组。

const a1 = [1, 2];
const a2 = a1.concat();

a2[0] = 2;
a1 // [1, 2]

ES6

const a1 = [1, 2];
// 写法一
const a2 = [...a1];

合并数组

const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];

// ES5 的合并数组
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]

// ES6 的合并数组
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]

Array.from

含义 将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)

类数组对象

let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};

// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']

// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

可遍历

// NodeList对象
let ps = document.querySelectorAll('p');
Array.from(ps).filter(p => {
  return p.textContent.length > 100;
});

复杂数据处理 · 结构转换

这章出自掘金小册子《基于 JavaScript 开发灵活的数据应用》 作者:小问iwillwen 小册子链接 之后找个时间自己重写这部分

Any ↔ 字符串

在开发数据应用的时候,有大部分的数据都不会是由 JavaScript 或用户的操作实时生成的,更多的是直接从服务端的数据存储设施中提取出来,然后通过网络协议传输到客户端以用于展示。

这样的话我们可以首先引入一个题外话,既然我们知道前端使用的数据大部分都需要通过网络协议从服务端传往前端,那这样一个传输过程就是抽象内容的编码和解编码的过程。而且在计算机科学中,通信协议基本上都是以字符串(或二进制)为基础承载数据结构,也就是说在一个服务端与客户端的通信架构中,会需要将各种数据结构首先转换为字符串,经过了网络的传输过程而达到另一端之后,再以相同的方式转换为原本的数据结构。 image

JSON

JSON,全称为 JavaScript Object Notation,是目前最流行的网络数据传输格式之一。相比于 CSV(Comma-Separated Values,逗号分隔值)、XML(Extensible Markup Language,可扩展标记语言)等历史更为悠久的格式化数据传输格式,JSON 同时拥有着易读性强(完全符合 JavaScript 标准)、格式不敏感和轻量化的特点。

{
  "name": "Chaoyang Gan",
  "nickname": "iwillwen"
}

JSON 是一个 JavaScript 语言标准的子集,它完全可以直接运行在 JavaScript 引擎中。当然因为 JavaScript 语言本身是具有可被攻击的可能性的,所以在解析 JSON 数据内容的时候,并不能直接作为一段 JavaScript 代码运行。

JavaScript 引擎中提供了一个 eval 函数以用于运行一段 JavaScript 代码,所以假如一段 JSON 数据内容是绝对安全的,确实可以使用 eval 函数当做是 JSON 解析器。

const jsonStr = `{
  "name": "Chaoyang Gan",
  "nickname": "iwillwen"
}`
eval('var me = ' + jsonStr)


console.log(me.name) //=> Chaoyang Gan

但如果需要解析的 JSON 数据并不能保证安全甚至可以被恶意篡改(通过中间人劫持、XSS 攻击等方式),就会出现非常不安全的情况,严重者会导致用户私密信息被盗取。

const somethingImportant = 'some secret'

const jsonStr = `{
  "attack": (function(){
    alert(somethingImportant)
  })()
}`

eval('var me = ' + jsonStr) //=> some secret

为了避免这种情况的出现,我们必须使用现代 JavaScript 引擎中提供的或其他可信任的 JSON.parse 函数进行解码和 JSON.stringify 函数进行编码。

JSON.parse(`{
  "attack": (function(){
    alert(somethingImportant)
  })()
}`) //=> SyntaxError: Unexpected token ( in JSON

言归正传,通常来说,我们可以把将非字符串类型的数据通过某种算法转换为字符串的过程称为序列化(字符串也是一种有序序列),而利用 JSON 格式便是目前最流行的序列化方法之一。

const jsonStr = JSON.stringify({
  name: 'Chaoyang Gan',
  nickname: 'iwillwen'
})

console.log(jsonStr) //=> {"name":"Chaoyang Gan","nickname":"iwillwen"}
直接转换

JSON 格式的好处是将结构不确定的数据转换为字符串格式,但同时也会强行带来可能不必要的内容,比如 JSON 的边界字符(如 "、{} 等)。在需要转换的目标数据类型是确定的,而且将序列化后的字符串数据进行解析的接收方也是可控的的情况下,可以选择直接对数据进行类型转换。

数值类型 在 JavaScript 中所有的对象都会默认带有一个 toString 方法,而对于数值类型来说,可以直接使用这个方法来进行向字符串类型的转换。

const n1 = 1
const n2 = 1.2
const s1 = n1.toString()
const s2 = n2.toString()
console.log(s1, typeof s1) //=> 1 string
console.log(s2, typeof s2) //=> 1.2 string

除了将数值直接转换为字符串之外,我们常常需要实现一个将数据类型的小数点后的值固定在一个长度范围内,比如 5 -> 5.00 和 3.1415 -> 3.14,这个主要用于表格和图表的展示上。3.1415 可以通过数值计算得到需要的 3.14,但是 5 没办法直接通过计算得到 5.00。因为 JavaScript 并不像其他语言那样区分开整型和非整型的数值,所以它提供了一个用于实现这个需求的方法 Number.toFixed。这个方法接受一个数值参数,即小数点后的保留位数,一般来说这个参数需要是非负整型数值,当然如果传入一个非整型数值,该方法也会自动取整进行计算。

const int = 5
const pi = Math.PI //=> 3.141592653589793 (约等于)

console.log(int.toFixed(2)) //=> '5.00'
console.log(pi.toFixed(2)) //=> '3.14'
console.log(int.toFixed(pi)) //=> '5.000'

转换成字符串之后还可以通过 parseInt 和 parseFloat 将以字符串形式存储的数值转换为整型或浮点型。

console.log(parseInt('5.00')) //=> 5
console.log(parseFloat('3.14')) //=> 3.14

布尔型(逻辑型) 布尔型也就是真与假(幸亏 JavaScript 并不存在中间态),在 JavaScript 中表现为 true 与 false。显而易见,这两个值各自都有一个以英文单词来表示的意义,那么我们自然可以非常简单地对其进行转换了。

console.log(true.toString()) //=> 'true'
console.log(false.toString()) //=> 'false'

但是要将其再转换成布尔型就没那么简单了,因为 JavaScript 中并没有直接提供 parseBoolean 这样的函数,而且作为弱类型语言,JavaScript 在进行一些判断时也有不少让人非常费解的“操作”。

true == 'true' //=> false
false == 'false' //=> false
true == 1 //=> true
false == 0 //=> true

所以一般来说我们可以使用强类型判断 === 分别判断一个字符串是否是 "true",不是则为 false。

function parseBoolean(string) {
  return string === 'true'
}

console.log(parseBoolean('true')) //=> true
console.log(parseBoolean('false')) //=> false

数组 事实上,我们在第 2 节中就已经接触过字符串中的 split 方法,它用于将一个字符串以指定字符串为分隔符分割成一个数组。

const str = '1,2,3,4,5'
const arr = str.split(',')
console.log(arr) //=> [ 1, 2, 3, 4, 5 ]

对应地,数组也可以进行组合变成一个字符串,使用的是 Array.join 方法。

const arr = [ 1, 2, 3, 4, 5 ]

console.log(arr.join()) //=> 1,2,3,4,5
console.log(arr.join('#')) //=> 1#2#3#4#5

对象 ↔ 数组

在 JavaScript 中的数组实际上是一个特殊的对象字面量,那么在从属关系上看数组应该是对象字面量的一个子集,但为什么我们这里还是要提到对象和数组之间的互相转换呢?假设我们需要将一个对象字面量中的属性以列表的形式展示出来: image

虽然各种框架都有相关的函数或者工具来完成这个需求,但是为了更好地理解数据结构之间的差异及对其的应用,我们还是需要了解其中如何进行数据格式的转换。

JavaScript 中提供了一个Object.keys()函数,可以提取出对象的所有属性键,并以数组的形式表示。

const object = {
  "name": "Chaoyang Gan",
  "title": "Engineer",
  "subject": "Maths"
}
const keys = Object.keys(object)
console.log(keys) //=> ["name", "title", "subject"]

得到了目标对象的属性键数组后,配合数组的 .map 方法便可以将每一个属性键对应的值提取出来。

const list = keys.map(key => {
  return {
    key, value: object[key]
  }
})

console.log(list)
//=> [
// {key: "name", value: "Chaoyang Gan"},
// {key: "title", value: "Engineer"},
// {key: "subject", value: "Maths"}
// ]

当然我们可以将第二层中的对象也使用数组表示。

const pairs = keys.map(key => {
  return [ key, object[key] ]
})

console.log(pairs)
// => [
// ["name", "Chaoyang Gan"],
// ["title", "Engineer"],
// ["subject", "Maths"]
// ]

同样,我们也可以使用 Lodash 中提供的 _.toPairs 方法将对象转换为以双元素为键值对表达方式的数组。

const pairs = _.toPairs(object)

完成了从对象到数组的转换后自然需要一个将其进行逆转换的方法,可以直接使用 Lodash 中提供的 _.fromPairs。

const object = _.fromPairs(pairs)
console.log(object)
// => {
// name: "Chaoyang Gan",
// title: "Engineer",
// subject: "Maths"
// }

事实上,我们在第 5 节中用过的 _.groupBy 函数也是一种将数组转换为对象的方法,但它更多的是为了将数组根据其中的某一个字段或某一种变换结果来进行字典化,而不是单纯地将其进行转换。

我们需要明确的原则是,数据转换的出发点和目的都是为了服务需求,而不是单纯地将其进行数据结构上的转换,在思考如何对数据进行处理之前,首先要明确目标需求究竟需要怎样的数据形式。 究竟是需要一个以数值作为元素的数组(如人工神经网络的输入和输出值),还是以对象作为元素类型的数组以用于表格的展示(每一个对象元素代表表格中的一行),或是以列为单位存储的数据框对象(如 ECharts 框架中常用)。

// Input data for ANN
const xorArray = [ 1, 0, 0, 1, 1, 0, 1 ]
// Row-base dataset
const rDataset = [
{ name: "iwillwen", gender: "male" },
{ name: "rrrruu", gender: "female" }
]


// Column-base dataset
const cDataset = {
name: [ "iwillwen", "rrrruu" ],
gender: [ "male", "female" ]
}

对象Date

构建

var d = new Date();
var d = new Date(milliseconds);
var d = new Date(dateString);
var d = new Date(year, month, day, hours, minutes, seconds, milliseconds);

历史

unix时间戳 unix时间戳是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数,不考虑闰秒

2038年问题

  • 早期计算机32位,时间也是32位
  • 最长时间68年,就会出现2038年问题
  • 1969年UNIX的原型,被称为Unics(当时尚未有UNIX)诞生
  • 1973年,Unix正式诞生

方法

get

image

getSeconds()

根据本地时返回 Date 对象的秒数 (0 ~ 59)

getMinutes()

根据本地时返回 Date 对象的分钟 (0 ~ 59)

getHours()

根据本地时返回 Date 对象的小时 (0 ~ 23)

getDate()

根据本地时从Date对象返回一个月中的某一天(1 ~ 31)

getDay()

根据本地时从Date对象返回一周中的某一天(1 ~ 6)

getFullYear()

根据本地时从 Date 对象以四位数字返回年份

getTime()

返回 1970 年 1 月 1 日至今的毫秒数。

set

把上面get变成set

Moment.js

官方文档 ref

常用函数

format

Date.prototype.format = function (format) {
  var values = {
    "(Y+)": this.getFullYear(),                     // year
    "(M+)": this.getMonth() + 1,                    // month
    "(D+)": this.getDate(),                         // day
    "(h+)": this.getHours(),                        // hour
    "(m+)": this.getMinutes(),                      // minute
    "(s+)": this.getSeconds(),                      // second
    "(q+)": Math.floor((this.getMonth() + 3) / 3),  // quarter
    "(ms)": this.getMilliseconds()                  // millisecond
  };
  var result = format || "YYYY-MM-DD";
  for (var key in values) {
    var value  = values[key].toString();
    var isUnit = 1 === value.length;
    value = isUnit ? "0" + value : value;
    if (new RegExp(key).test(result)) {
      var length = isUnit ? RegExp.$1.length : value.length;
      result = result.replace(RegExp.$1, value.substr(-length, length));
    }
  }
  return result;
};

对象Math

image

浮点转整数

ceil()

ceil() 方法可对一个数进行上舍入

Math.ceil(1.4) // 2

floor()

对数进行下舍入

Math.floor(1.6); // 1

round()

把数四舍五入为最接近的整数

Math.round(2.5); // 3

最值

max()

max() 方法可返回两个指定的数中带有较大的值的那个数。

Math.max(n1,n2,n3,...,nX)

min()

min() 方法可返回指定的数字中带有最小值的数字。

Math.min(5,10);

数学计算

random()

random() 方法可返回介于 0(包含) ~ 1(不包含) 之间的一个随机数。

Math.random(); // 0.5272828698272205

abs()

abs() 方法可返回一个数的绝对值。

Math.abs(-7.25); // 7.25

pow()

pow() 方法返回 x 的 y 次幂。

Math.pow(4,3); // 64

sqrt()

sqrt() 方法可返回一个数的平方根。

Math.sqrt(9); // 3

对象RegExp

这部分需要重写,用结合一些日常案例

构建

var re = new RegExp("\\w+");
var re = /\w+/;

修饰符

修饰符 描述
i 执行对大小写不敏感的匹配。
g 执行全局匹配
m 执行多行匹配

方括号

修饰符 描述
[0-9] 查找任何从 0 至 9 的数字
[a-z] 查找任何从小写 a 到小写 z 的字符
[A-Z] 查找任何从大写 A 到大写 Z 的字符
[A-z] 查找任何从大写 A 到小写 z 的字符
[^abc] 表达式用于查找任何不在方括号之间的字符

元字符

修饰符 描述
. 查找单个字符,除了换行和行结束符
\w 查找单词字符
\W 查找非单词字符
\d 查找数字
\D 查找非数字字符
\s 查找空白字符
\S 查找非空白字符

量词

(anki)

修饰符 描述
n+ 匹配任何包含至少一个 n 的字符串
n* 匹配任何包含零个或多个 n 的字符串
n? 匹配任何包含零个或一个 n 的字符串
n{X} 匹配包含 X 个 n 的序列的字符串
n{X,Y} 匹配包含 X 至 Y 个 n 的序列的字符串。
n{X,} 匹配包含至少 X 个 n 的序列的字符串
?=n 匹配任何其后紧接指定字符串 n 的字符串
?!n ?!n 量词匹配其后没有紧接指定字符串 n 的任何字符串

位置

(anki)

修饰符 描述
n$ 匹配任何结尾为 n 的字符串
^n 匹配任何开头为 n 的字符串
\b 两个单词间隔
\B 不是单词间隔的

常用正则

// 整数或者小数:
^[0-9]+\.{0,1}[0-9]{0,2}$

// 只能输入数字:
"^[0-9]*$"

// 只能输入n位的数字:
"^\d{n}$"

// 只能输入至少n位的数字:
"^\d{n,}$"

// 只能输入m~n位的数字:
"^\d{m,n}$"

// 只能输入零和非零开头的数字:
"^(0|[1-9][0-9]*)$"

// 只能输入有两位小数的正实数:
"^[0-9]+(.[0-9]{2})?$"

// 只能输入有1~3位小数的正实数:
"^[0-9]+(.[0-9]{1,3})?$"

// 只能输入非零的正整数:
"^\+?[1-9][0-9]*$"

// 只能输入非零的负整数:
"^\-[1-9][]0-9"*$

// 只能输入长度为3的字符:
"^.{3}$"

// 只能输入由26个英文字母组成的字符串:
"^[A-Za-z]+$"

// 只能输入由26个大写英文字母组成的字符串:
"^[A-Z]+$"

// 只能输入由26个小写英文字母组成的字符串:
"^[a-z]+$"

// 只能输入由数字和26个英文字母组成的字符串:
"^[A-Za-z0-9]+$"

// 只能输入由数字、26个英文字母或者下划线组成的字符串:
"^\w+$"

// 验证用户密码:
"^[a-zA-Z]\w{5,17}$"
// 正确格式为:以字母开头,长度在6~18之间,只能包含字符、数字和下划线。


// 只能输入汉字:
"^[\u4e00-\u9fa5]{0,}$"

// 验证Email地址:
"^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$"// 验证InternetURL:
"^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$"// 验证电话号码:
"^(\(\d{3,4}-)|\d{3.4}-)?\d{7,8}$"正确格式为:"XXX-XXXXXXX""XXXX- XXXXXXXX""XXX-XXXXXXX""XXX-XXXXXXXX""XXXXXXX""XXXXXXXX"// 验证身份证号(15位或18位数字):
"^\d{15}|\d{18}$"// 验证一年的12个月:
"^(0?[1-9]|1[0-2])$"

// 正确格式为:
"01""09""1""12"

// 验证一个月的31天:
"^((0?[1-9])|((1|2)[0-9])|30|31)$"
 // 正确格式为;"01"~"09"和"1"~"31"。整数或者小数:^[0-9]+\.{0,1}[0-9]{0,2}$

// 只能输入数字:
"^[0-9]*$"

// 只能输入n位的数字:
"^\d{n}$"

// 只能输入至少n位的数字:
"^\d{n,}$"

// 只能输入m~n位的数字:
"^\d{m,n}$"

// 只能输入零和非零开头的数字:
"^(0|[1-9][0-9]*)$"

// 只能输入有两位小数的正实数:
"^[0-9]+(.[0-9]{2})?$"

// 只能输入有1~3位小数的正实数:
"^[0-9]+(.[0-9]{1,3})?$"

// 只能输入非零的正整数:
"^\+?[1-9][0-9]*$"

// 只能输入非零的负整数:
"^\-[1-9][]0-9*$" 

// 只能输入长度为3的字符:
"^.{3}$"

// 只能输入由26个英文字母组成的字符串:
"^[A-Za-z]+$"

// 只能输入由26个大写英文字母组成的字符串:
"^[A-Z]+$"

// 只能输入由26个小写英文字母组成的字符串:
"^[a-z]+$"

// 只能输入由数字和26个英文字母组成的字符串:
"^[A-Za-z0-9]+$"

// 只能输入由数字、26个英文字母或者下划线组成的字符串:
"^\w+$"

// 验证用户密码:
"^[a-zA-Z]\w{5,17}$"

对象JSON

JSON 格式(JavaScript Object Notation 的缩写)是一种用于数据交换的文本格式,2001年由 Douglas Crockford 提出,目的是取代繁琐笨重的 XML 格式。

相比 XML 格式,JSON 格式有两个显著的优点:书写简单,一目了然;符合 JavaScript 原生语法,可以由解释引擎直接处理,不用另外添加解析代码。所以,JSON 迅速被接受,已经成为各大网站交换数据的标准格式,并被写入标准。

每个 JSON 对象就是一个值,可能是一个数组或对象,也可能是一个原始类型的值。总之,只能是一个值,不能是两个或更多的值。

JSON 对值的类型和格式有严格的规定。

  • 复合类型的值只能是数组或对象,不能是函数、正则表达式对象、日期对象。

  • 原始类型的值只有四种:字符串、数值(必须以十进制表示)、布尔值和null(不能使用NaN, Infinity,和undefined)。

  • 字符串必须使用双引号表示,不能使用单引号。

  • 对象的键名必须放在双引号里面。

  • 数组或对象最后一个成员的后面,不能加逗号。

JSON.stringify()

JSON.stringify方法用于将一个值转为 JSON 字符串。该字符串符合 JSON 格式,并且可以被JSON.parse方法还原。

JSON.stringify('abc') // ""abc""
JSON.stringify(1) // "1"
JSON.stringify(false) // "false"
JSON.stringify([]) // "[]"
JSON.stringify({}) // "{}"

JSON.stringify([1, "false", false])
// '[1,"false",false]'

JSON.stringify({ name: "张三" })
// '{"name":"张三"}'

上面代码将各种类型的值,转成 JSON 字符串。

注意,对于原始类型的字符串,转换结果会带双引号。 JSON.stringify方法还可以接受一个数组,作为第二个参数,指定需要转成字符串的属性

var obj = {
  'prop1': 'value1',
  'prop2': 'value2',
  'prop3': 'value3'
};

var selectedProperties = ['prop1', 'prop2'];

JSON.stringify(obj, selectedProperties)
// "{"prop1":"value1","prop2":"value2"}"

上面代码中,JSON.stringify方法的第二个参数指定,只转prop1和prop2两个属性。

这个类似白名单的数组,只对对象的属性有效,对数组无效。

JSON.stringify(['a', 'b'], ['0'])
// "["a","b"]"

JSON.stringify({0: 'a', 1: 'b'}, ['0'])
// "{"0":"a"}"

第二个参数还可以是一个函数,用来更改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}'

上面代码中的f函数,接受两个参数,分别是被转换的对象的键名和键值。如果键值是数值,就将它乘以2,否则就原样返回。

JSON.parse()

JSON.parse方法用于将 JSON 字符串转换成对应的值。

JSON.parse('{}') // {}
JSON.parse('true') // true
JSON.parse('"foo"') // "foo"
JSON.parse('[1, 5, "false"]') // [1, 5, "false"]
JSON.parse('null') // null

var o = JSON.parse('{"name": "张三"}');
o.name // 张三

如果传入的字符串不是有效的 JSON 格式,JSON.parse方法将报错。

对象Set

它类似于数组,但是成员的值都是唯一的,没有重复的值。和array类似 image

方法

操作

  • size 返回Set实例的成员总数
  • add(value):添加某个值,返回 Set 结构本身。
  • delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
  • has(value):返回一个布尔值,表示该值是否为Set的成员。
  • clear():清除所有成员,没有返回值。

遍历

遍历方法

  • keys():返回键名的遍历器
  • values():返回键值的遍历器
  • entries():返回键值对的遍历器
  • forEach():使用回调函数遍历每个成员
let set = new Set(['red', 'green', 'blue']);

for (let item of set.keys()) {
  console.log(item);
}
// red
// green
// blue

for (let item of set.values()) {
  console.log(item);
}
// red
// green
// blue

for (let item of set.entries()) {
  console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]

forEach()

Set 结构的实例与数组一样,也拥有forEach方法,用于对每个成员执行某种操作,没有返回值

set = new Set([1, 4, 9]);
set.forEach((value, key) => console.log(key + ' : ' + value))
// 1 : 1
// 4 : 4
// 9 : 9

上面代码说明,forEach方法的参数就是一个处理函数。该函数的参数与数组的forEach一致,依次为键值、键名、集合本身(上例省略了该参数)。这里需要注意,Set 结构的键名就是键值(两者是同一个值),因此第一个参数与第二个参数的值永远都是一样的。

map filter

let set = new Set([1, 2, 3]);
set = new Set([...set].map(x => x * 2));
// 返回Set结构:{2, 4, 6}

let set = new Set([1, 2, 3, 4, 5]);
set = new Set([...set].filter(x => (x % 2) == 0));
// 返回Set结构:{2, 4}

扩展运算符

扩展运算符和 Set 结构相结合,就可以去除数组的重复成员。

let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)];
// [3, 5, 2]