Cauil's Blog

陌上花开,可缓缓归矣


  • 首页

  • 归档

  • 标签

ES6之Set与Map

发表于 2017-11-27 | 分类于 JS

为啥引入Set与Map

在ES6之前,当想使用非数值型索引,就会用非数组对象创建所需的数据结构;

ES5中set集合:

1
2
3
4
5
6
var set = Object.create(null);
set.foo = true;
// checking for existence
if (set.foo) {
// code to execute
}

Map集合:

1
2
3
4
5
var map = Object.create(null);
map.foo = "bar";
// retrieving a value
var value = map.foo;
console.log(value); // "bar"

用这种方法的确可以模拟Set集合与Map集合,但这种所有对象的属性名必须是字符串类型,必须确保每个键名都是字符串类型且在对象中是唯一的。如:

1
2
3
4
5
6
7
8
9
var map = Object.create(null);
map[5] = "foo";
console.log(map["5"]); // "foo"
var map = Object.create(null),
key1 = {},
key2 = {};
map[key1] = "foo";
console.log(map[key2]); // "foo"

对于Map集合中,如果它的属性值是假值,则要求使用布尔值的情况下,会被自动转换成false。当在判断Map结合中是否有某些属性场景会有问题:

1
2
3
4
5
6
var map = Object.create(null);
map.count = 0;
// checking for the existence of "count" or for a nonzero value?
if (map.count) {
// code to execute
}

ES6中Set

添加元素

1
2
3
4
let set = new Set();
set.add(5);
set.add("5");
console.log(set.size);

移除元素

1
2
3
4
5
6
7
8
9
10
let set = new Set();
set.add(5);
set.add("5");
console.log(set.has(5)); // true
set.delete(5);
console.log(set.has(5)); // false
console.log(set.size); // 1
set.clear();
console.log(set.has("5")); // false
console.log(set.size); // 0

forEach

需要注意的是回调函数中的前两个参数value与key是一样的;

1
2
3
4
5
let set = new Set([1, 2]);
set.forEach(function(value, key, ownerSet) {
console.log(key + " " + value);
console.log(ownerSet === set);
});

转换为数组

在前面的迭代器中有讲到可迭代对象可以展开,同样Set可以:

1
2
3
let set = new Set([1, 2, 3, 3, 3, 4, 5]),
array = [...set];
console.log(array); // [1, 2, 3, 4, 5]

Weak Set集合

为啥要引入Weak Set?先看一个例子:

1
2
3
4
5
6
7
8
9
let set = new Set(),
key = {};
set.add(key);
console.log(set.size); // 1
// eliminate original reference
key = null;
console.log(set.size); // 1
// get the original reference back
key = [...set][0];

上面例子将变量key设置null时消除了对初识对象的引用,但是Set集合却保留了这个引用;

有时我们希望当其他所有引用都不再存在时,让Set集合的这些引用随之消失;为了解决这个问题,ES6额外引入了一个类型:Weak Set集合(弱引用Set集合)。

Weak Set集合只存储对象的弱引用,并且不可以存储原始值;集合中的弱引用如果是对象唯一的引用,则会被回收并释放相应内存;

Weak Set支持add has和delete方法;

1
2
3
4
5
6
7
let set = new WeakSet(),
key = {};
// add the object to the set
set.add(key);
console.log(set.has(key));
set.delete(key);
console.log(set.has(key));

Weak Set与Set区别:

  • Weak Set实例中,只能含有对象参数;非对象参数会报错
  • Weak Set不可迭代;
  • Weak Set不暴露任何迭代器
  • Weak Set不支持forEach方法
  • Weak Set不支持size属性
  • 最大区别是Weak Set保存的是对象的弱引用

ES6中Map

支持的方法has delete clear

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let map = new Map();
map.set("name", "Nicholas");
map.set("age", 25);
console.log(map.size); // 2
console.log(map.has("name")); // true
console.log(map.get("name")); // 'Nicholas'
console.log(map.has("age")); // true
console.log(map.get("age")); // 25
map.delete("name");
console.log(map.has("name")); // false
console.log(map.get("name")); // undefined
console.log(map.size); // 1
map.clear();
console.log(map.has("name")); // false
console.log(map.get("name")); // undefined
console.log(map.has("age")); // false
console.log(map.get("age")); // undefined
console.log(map.size); // 0

Map集合初始化

1
2
3
4
5
6
let map = new Map([["name", "Nicholas"], ["age", 25]]);
console.log(map.has("name")); // true
console.log(map.get("name")); // 'Nicholas'
console.log(map.has("age")); // true
console.log(map.get("age")); // 25
console.log(map.size); // 2

Map集合的forEach

1
2
3
4
5
6
7
8
9
let map = new Map([["name", "Nicholas"], ["age", 25]]);
map.forEach(function(value, key, ownerMap) {
console.log(key + " " + value);
console.log(ownerMap === map);
});
// name Nicholas
// true
// age 25
// true

Weak Map

Weak Map与Map的不同之处和Weak Set与Set的不同类似

  • Weak Map实例中,只能含有对象参数;非对象参数会报错
  • Weak Map不可迭代;
  • Weak Map不暴露任何迭代器
  • Weak Map不支持forEach方法
  • Weak Map不支持size属性,不支持clear
  • 最大区别是Weak Map保存的是对象的弱引用

ES6之迭代器与生成器

发表于 2017-11-26 | 分类于 JS

迭代器

之前写过一篇文章关于可迭代对象-谈谈遍历与迭代协议, 并讲述了可迭代协议(iterable protocal);

还遗留了一个迭代器协议(iterator)没有讲述,这次我们就来说一说迭代器协议;

迭代器协议

MDN上是这样定义iterator protocal的:

The iterator protocol defines a standard way to produce a sequence of values (either finite or infinite).

An object is an iterator when it implements a next() method with the following semantics:

Property Value
next A zero arguments function that returns an object with two properties:
  • done (boolean)
    • Has the value true if the iterator is past the end of the iterated sequence. In this case value optionally specifies the return value of the iterator. The return values are explained here.
    • Has the value false if the iterator was able to produce the next value in the sequence. This is equivalent of not specifying the done property altogether.
  • value - any JavaScript value returned by the iterator. Can be omitted when done is true.

The next method always has to return an object with appropriate properties including done and value. If a non-object value gets returned (such as false or undefined), a TypeError (“iterator.next() returned a non-object value”) will be thrown. |

其实就是迭代器拥有一个next方法,每次调用返回含有value和done两个属性的对象,value表示下一个将要返回的值;done是一个布尔值,当没有更多返回数据时返回true;

1
2
3
4
5
6
7
8
var someArray = [1, 5];
var someArrayEntries = someArray.entries();
someArrayEntries.toString(); // "[object Array Iterator]"
someArrayEntries === someArrayEntries[Symbol.iterator](); // true
someArrayEntries.next(); // {value: Array(2), done: false}
someArrayEntries.next(); // {value: Array(2), done: false}
someArrayEntries.next(); // {value: undefined, done: true}

上个例子中someArray.entries()生成了一个迭代器,每次调用next返回一个对象;当迭代到最后,返回{value: undefined, done: true};并且特殊的是someArrayEntries也是一个符合iterable协议的可迭代对象,当调用someArrayEntries[Symbol.iterator])的时候返回一个迭代器对象就是其本身;

迭代器与可迭代对象的联系

1
2
3
4
5
6
7
iterableObj[Symbol.iterator]() ===> iteratorObj;
for (let v of iterableObj){
console.log(v);
}
iteratorObj.next(); //{value: v, done: false}
...
iteratorObj.next(); //{value: undefined, done: true}

以上介绍生成迭代器的方法还是针对具有Symbol.iterator属性的常见对象(如数组,map等)来的,怎么更通用的生成一个迭代器呢?

内建迭代器

1.集合对象迭代器

ES6中有三种类型集合对象: 数组、Map集合与Set集合。为了更好的访问对象中的内容,都内建了以下三种迭代器:

  • entries() 返回一个迭代器,其值为多个键值对
  • values() 返回一个迭代器,其值为集合中的值
  • keys() 返回一个迭代器,其值为集合中的所有键名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let colors = [ "red", "green", "blue" ];
for (let entry of colors.entries()) {
console.log(entry);
}
//[0, "red"]
//[1, "green"]
//[2, "blue"]
let tracking = new Set([1234, 5678, 9012]);
for (let value of tracking.values()) {
console.log(value);
}
// 1234
// 5678
// 9012
let data = new Map();
for (let key of data.keys()) {
console.log(key);
}
// "title"
// "format"

在for-of循环中,如果没有显式指定则使用默认的迭代器。数组和Set集合的默认迭代器是values()方法,Map默认迭代器是entries方法。

2.字符串迭代器和NodeList迭代器

1
2
3
4
var message = "A 𠮷 B";
for (let c of message) {
console.log(c);
}

NodeList迭代器:

1
2
3
4
5
var divs = document.getElementsByTagName("div");
for (let div of divs) {
console.log(div.id);
}
}

生成器

上面的例子中生成迭代器的方法还是针对具有Symbol.iterator属性的常见对象(如数组,map等)来的,怎么更通用的生成一个迭代器呢?

ES6中引入了生成器;生成器是一种返回迭代器的函数,通过function关键字后的*号来表示,函数中会用到新的关键字yield。星号可以紧挨着function关键字,也可以在中间添加一个空格,如:

1
2
3
4
5
6
7
8
9
10
11
function *createIterator() {
yield 1;
yield 2;
yield 3;
}
// generators are called like regular functions but return an iterator
let iterator = createIterator();
console.log(iterator); // [object Generator]
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3

上面的例子createIterator就是一个生成器,生成器执行返回的是一个生成器对象,生成器对象既是一个迭代器也是一个可迭代对象。

yield

生成器函数最有趣的部分是:每当执行完一条yield语句后函数就会自动停止执行。

**yield关键字只可以在生成器内部使用,在其他地方使用会导致程序抛出语法错误,即便在生成器内部的函数里使用也是如此:

1
2
3
4
5
6
function *createIterator(items) {
items.forEach(function(item) {
// syntax error
yield item + 1;
});
}

从字面量上看,yield关键字确实在createIterator函数内部,但是它与return关键字一样,二者都不能穿透函数边界。嵌套函数中的return语句不能用做外部函数的返回语句,而此处嵌套函数中的yield语句会导致程序抛出语法错误。

生成器函数表达式

1
2
3
4
5
6
7
8
9
10
let createIterator = function *(items) {
for (let i = 0; i < items.length; i++) {
yield items[i];
}
};
let iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"

不能用箭头函数来创建生成器

生成器对象的方法

由于生成器对象本身就是函数,因而可以将她们添加到对象中。例如

1
2
3
4
5
6
7
let o = {
createIterator: function *(items) {
for (let i = 0; i < items.length; i++) {
yield items[i];
}
} };
let iterator = o.createIterator([1, 2, 3]);

也可以用ES6对象方法的简写方式来创建生成器;

1
2
3
4
5
6
7
let o = {
*createIterator(items) {
for (let i = 0; i < items.length; i++) {
yield items[i];
}
} };
let iterator = o.createIterator([1, 2, 3]);

高级生成器

生成器根据需求计算它们的产出值,这使得它们能够有效地表示计算成本高的序列,或者甚至如上所述的无限序列。

给迭代器传递参数

The next() 方法也接受可用于修改生成器内部状态的值。传递给next()的值将被视为暂停生成器的最后一个yield表达式的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function* fibonacci() {
var fn1 = 0;
var fn2 = 1;
while (true) {
var current = fn1;
fn1 = fn2;
fn2 = current + fn1;
var reset = yield current;
if (reset) {
fn1 = 0;
fn2 = 1;
}
}
}
var sequence = fibonacci();
console.log(sequence.next().value); // 0
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 2
console.log(sequence.next().value); // 3
console.log(sequence.next().value); // 5
console.log(sequence.next().value); // 8
console.log(sequence.next(true).value); // 0
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 2

在迭代器中抛出错误

1
2
3
4
5
6
7
8
9
function *createIterator() {
let first = yield 1;
let second = yield first + 2;
yield second + 3;
}
let iterator = createIterator();
console.log(iterator.next()); // {value: 1, done: false}
console.log(iterator.next(4)); // {value: 6, done: false}
console.log(iterator.throw(new Error("Boom")));

生成器返回语句

1
2
3
4
5
6
7
8
function *createIterator() {
yield 1;
return 42;
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 42, done: true }"
console.log(iterator.next()); // "{ value: undefined, done: true }"

上面例子中return语句返回一个值,该值被赋值给返回对象的value属性,本来第二条是返回{ value: undefined, done: true }的最终返回{ value: 42, done: true }

委托生成器

语法:

1
2
3
yield* [[expression]];
expression
返回一个可迭代对象的表达式。

yield* 表达式迭代操作数,并产生它返回的每个值。yield* 表达式本身的值是当迭代器关闭时返回的值(即done为true时);

1.委托给其他生成器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function* g1() {
yield 2;
yield 3;
yield 4;
}
function* g2() {
yield 1;
yield* g1();
yield 5;
}
var iterator = g2();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: 4, done: false }
console.log(iterator.next()); // { value: 5, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

2.委托给其他可迭代对象

除了生成器对象这一种可迭代对象,yield* 还可以 yield 其它任意的可迭代对象,比如说数组、字符串、arguments 对象等等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function* g3() {
yield* [1, 2];
yield* "34";
yield* arguments;
}
var iterator = g3(5, 6);
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: "3", done: false }
console.log(iterator.next()); // { value: "4", done: false }
console.log(iterator.next()); // { value: 5, done: false }
console.log(iterator.next()); // { value: 6, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

3.yield* 表达式的值

yield* 是一个表达式,不是语句,所以它会有自己的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function* g4() {
yield* [1, 2, 3];
return "foo";
}
var result;
function* g5() {
result = yield* g4();
}
var iterator = g5();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }, 此时 g4() 返回了 { value: "foo", done: true }
console.log(result); // "foo"

koa1源码中就有用到委托生成器语法;

可迭代对象

ES6中,所有的集合对象(数组、Set集合、Map集合)和字符串都是可迭代对象,这些对象中都有默认的迭代器;

之前的文章谈过可迭代对象,这里就总结一下:

for-of循环

可迭代对象可用于for-of循环:

1
2
3
4
let values = [1, 2, 3];
for (let num of values) {
console.log(num);
}

spread syntax (…)

可迭代对象可用于…语法:

[...'abc']; // ["a", "b", "c"]

用于yield *

1
2
3
function* gen() {
yield* ['a', 'b', 'c'];
}

用于解构

1
let [a, b, c] = new Set(['a', 'b', 'c']);

ES6之class

发表于 2017-11-24 | 分类于 JS

类声明

cheat sheet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class PersonClass {
// equivalent of the PersonType constructor
constructor(name) {
this.name = name;
}
// equivalent of PersonType.prototype.sayName
sayName() {
console.log(this.name);
}
}
let person = new PersonClass("Nicholas");
person.sayName(); // outputs "Nicholas"
console.log(person instanceof PersonClass); // true
console.log(person instanceof Object); // true
console.log(typeof PersonClass); // function
console.log(typeof PersonClass.prototype.sayName); // function

类属性不可被赋予新值,在之前的实例中,PersonClass.prototype就是这样一个只可读的类属性;

类语法

类与ES5类型(new Person)之间有诸多相识之初,但也有差异:

  1. 函数声明可以被提升,而类声明与let声明类似,不能被提升;真正执行声明语句之前,它们会一直存在于临时死区
  2. 类声明中的所有代码将自动运行在严格模式下,而且无法强行让代码脱离严格模式执行;
  3. 在自定义类型中,需要通过Object.defineProperty方法手工指定某个方法不可枚举;而在类中,所有方法都是不可枚举的;
  4. 每个类都有一个名为[[Construct]]内部方法,通过关键字new调用那些不含有[[Construct]]的方法会导致程序抛出错误;
  5. 使用除关键字new以外的方法调用类的构造函数会导致程序抛出错误;
  6. 在类中修改类名会导致程序报错;

下面是ES5来实现上面例子类PersonClass的等价代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let PersonType2 = (function() { // 1 临时死区
'use strict'; // 2 严格模式
const PersonType2 = function(name) { // 6 内部类名不可修改
if(typeof new.target === 'undefined') { // 5 只能使用new
throw new Error('必须通过关键字new来调用构造函数');
}
this.name = name;
}
Object.defineProperty(PersonType2.prototype, sayName, {
value: function() {
if(typeof new.target !== 'undefined') { // 4 不能使用new
throw Error('不能使用关键字new来调用该方法');
}
console.log(this.name);
},
enumerable: false,// 3 不可枚举
writable: true,
configurable: true,
});
return Persontype2;
}())

上述1和6可以看下面这个例子:

1
2
3
4
5
6
class Foo {
constructor() {
Foo = "bar";// throws an error when executed...
} }
// but this is okay after the class declaration
Foo = "baz";

类表达式

声明表达式

1
2
3
let PersonClass = class {
...
}

命名表达式

1
2
3
let PersonClass = class PersonClass2 {
...
}

在JS引擎中,类表达式的实现与类声明稍有不同。对于类声明来说,通过let定义的外部绑定与通过const定义的内部绑定具有相同的名称;而命名表达式通过const定义名称,从而PersonClass2只能在类的内部使用;

一等公民的类

在程序中,一等公民是指一个可以传入函数,可以从函数返回,并且可以复制给变量的值。JS函数是一等公民,ES6中把类也设计为一等公民。

作为参数传入函数:

1
2
3
4
5
function createObject(classDef) {
return new classDef();
}
let obj = createObject(class {...});

立即调用类构造函数:

1
2
3
let person = new class {
...
}('Hi');

访问器属性

类也支持直接在原型上定义访问器属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class CustomHTMLElement {
constructor(element) {
this.element = element;
}
get html() {
return this.element.innerHTML;
}
set html(value) {
this.element.innerHTML = value;
} }
var descriptor = Object.getOwnPropertyDescriptor(CustomHTMLElement.prototype, "html");
console.log("get" in descriptor);
console.log("set" in descriptor);
console.log(descriptor.enumerable);
// true
// true
// false

可计算成员名称

类方法和访问器属性也支持使用可计算名称;

1
2
3
4
5
6
7
8
9
10
11
let methodName = "sayName";
class PersonClass {
constructor(name) {
this.name = name;
}
[methodName]() {
console.log(this.name);
}
};
let me = new PersonClass("Nicholas");
me.sayName(); // "Nicholas"

生成器方法

在类中可以在方法名称前附加一个星号来定义生成器;

1
2
3
4
5
6
7
8
9
class MyClass {
*createIterator() {
yield 1;
yield 2;
yield 3;
}
}
let instance = new MyClass();
let iterator = instance.createIterator();

静态成员

ES6中类简化了创建静态成员的工程,在方法或访问器属性前面使用正式的静态注释即可;

1
2
3
4
5
6
7
8
class PersonClass {
constructor() {
this.name = name;
}
static create() {
...
}
}

不可在实例中访问静态成员,必须要直接在类中访问静态成员

继承与派生

ES6中使用extends关键字可以创造继承类;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Rectangle {
constructor(length, width) {
this.length = length;
this.width = width;
}
getArea() {
return this.length * this.width;
}
}
class Square extends Rectangle {
constructor(length) {
// equivalent of Rectangle.call(this, length, length)
super(length, length);
}
}
var square = new Square(3);
console.log(square.getArea());// 9
console.log(square instanceof Square);// true
console.log(square instanceof Rectangle);// true

上述代码创造了一个Square继承Rectangle类;Square被称为派生类;注意:如果在派生类中指定了构造函数则必须要调用super,如果不这样做程序就会报错;如果不使用构造函数,则当创建新的类实例时会自动调用super并传入所有参数

1
2
3
4
5
6
7
8
9
class Square extends Rectangle {
// no constructor
}
// is equivalent to
class Square extends Rectangle {
constructor(...args) {
super(...args);
}
}

使用super要注意:

  • 只可在派生类的构造函数中使用super(),如果尝试在非派生类或者函数中使用会抛出错误
  • 在构造函数中访问this前一定要调用super,它负责初试化this,如果在调用super之前尝试访问this会报错
  • 如果不想调用super(),则唯一的方法是让类的构造函数返回一个对象

类方法遮蔽

派生类如果跟父类有同名函数,派生类的方法会覆盖父类的同名方法;如果想在派生类中调用父类的方法,可以super.funcName()这样来调用;

静态成员继承

如果基类有静态成员,那么这些静态成员在派生类中也可调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Rectangle {
constructor(length, width) {
this.length = length;
this.width = width;
}
getArea() {
return this.length * this.width;
}
static create(length, width) {
return new Rectangle(length, width);
} }
class Square extends Rectangle {
constructor(length) {
// equivalent of Rectangle.call(this, length, length)
super(length, length);
}
}
var rect = Square.create(3, 4);
console.log(rect instanceof Rectangle); // true
console.log(rect.getArea()); // 12
console.log(rect instanceof Square); // false

新的静态方法create被添加到Rectangle中,继承后的Square.create()与Rectangle.create()行为类似;

派生自表达式的类

ES6最强大的一面或许是从表达式导出类的功能了;只要表达式可以被解析为一个函数并且具有[[Construct]]属性和原型,那么就可以用extends进行派生;extends强大的功能使得类可以继承自任意类型的表达式,从而可以创造更多可能性,例如动态的确定类的继承目标:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let SerializableMixin = {
serialize() {
return JSON.stringify(this);
}
};
let AreaMixin = {
getArea() {
return this.length * this.width;
}
};
function mixin(...mixins) {
var base = function() {};
Object.assign(base.prototype, ...mixins);
return base;
}
class Square extends mixin(AreaMixin, SerializableMixin) {
constructor(length) {
super();
this.length = length;
this.width = length;
} }
var x = new Square(3);
console.log(x.getArea()); // 9
console.log(x.serialize()); // "{"length":3,"width":3}"

内建对象的继承

在ES6之前,几乎不可能通过继承方式创建属于自己的数组,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var colors = [];
colors[0] = "red";
console.log(colors.length); // 1
colors.length = 0;
console.log(colors[0]); // undefined
// trying to inherit from array in ES5
function MyArray() {
Array.apply(this, arguments);
}
MyArray.prototype = Object.create(Array.prototype, {
constructor: {
value: MyArray,
writable: true,
configurable: true,
enumerable: true
} });
var colors = new MyArray();
colors[0] = "red";
console.log(colors.length); // 0
colors.length = 0;
console.log(colors[0]) // red

通过上面的代码,可以看出MyArray的行为与内建数组行为不一致;

ES6中可以通过extends来创建自定义数组;

1
2
3
4
5
6
7
8
class MyArray extends Array {
// empty
}
var colors = new MyArray();
colors[0] = "red";
console.log(colors.length); // 1
colors.length = 0;
console.log(colors[0]); // undefined

在类的构造函数中使用new.target

类中new.targe一般就等于类本身:

1
2
3
4
5
6
7
8
class Rectangle {
constructor(length, width) {
console.log(new.target === Rectangle);
this.length = length;
this.width = width;
} }
// new.target is Rectangle
var obj = new Rectangle(3, 4); // outputs true

但有时其值会不同:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Rectangle {
constructor(length, width) {
console.log(new.target === Rectangle);
this.length = length;
this.width = width;
} }
class Square extends Rectangle {
constructor(length) {
super(length, length)
}
}
// new.target is Square
var obj = new Square(3); // outputs false

Square调用Rectangle的构造函数,所以当调用发生时new.target等于Square;每个构造函数都可以根据自身被调用的方式改变自己的行为;例如构造一个抽象基类;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Shape {
constructor() {
if (new.target === Shape) {
throw new Error("This class cannot be instantiated directly.")
}
} }
class Rectangle extends Shape {
constructor(length, width) {
super();
this.length = length;
this.width = width;
} }
var x = new Shape(); // throws an error
var y = new Rectangle(3, 4); // no error
console.log(y instanceof Shape); // true

上例子中抽象基类Shape不能直接被实例化,只能通过派生的类来继承;

ES6之Symbol

发表于 2017-11-22 | 分类于 JS

ES6引入了第6种原始类型:Symbol。起初,人们用它来创建对象的私有成员;在Symbol出现之前,人们一直通过属性名来访问所有属性,无论属性名由什么元素构成,全部通过一个字符串类型的名称来访问。

私有名称原本是为了让开发者创建非字符串属性名称而设计的,但是一般的技术无法检测这些属性的私有名称。

创建Symbol

可以通过全局的Symbol函数创建一个Symbol:

1
2
3
4
let firstName = Symbol();
let person = {};
person[firstName] = "Nicholas";
console.log(person[firstName]);

由于Symbol是原始值,因此调用new Symbol()会导致程序抛出错误。

Symbol接受一个可选参数,其可以添加一段文本描述即将创建的Symbol,这段描述不可以用作属性访问,只是为了便于阅读和调试Symbol程序;symbol的描述被存储在内部的[[Description]]属性中,只有当调用Symbol的toString()方法时才可以读取这个属性;

检测一个Symbol:

1
2
let name = Symbol('hi');
console.log(typeof name); // symbol

Symbol的使用方法

所有使用可计算属性名的地方,都可以使用Symbol。如Object.defineProperty等:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let firstName = Symbol("first name");
// use a computed object literal property
let person = {
[firstName]: "Nicholas"
};
// make the property read only
Object.defineProperty(person, firstName, { writable: false });
let lastName = Symbol("last name");
Object.defineProperties(person, {
[lastName]: {
value: "Zakas",
writable: false
}
});

Symbol共享体系

在不同的代码中共享同一个Symbol,ES6提供了一个可以随时访问的全局Symbol注册表;如果要创建一个共享的Symbol,要使用Symbol.for方法,它只接受一个参数,也就是即将创建的Symbol的字符串标示,这个参数同样也被用作Symbol的描述;

1
2
3
4
5
let uid = Symbol.for("uid");
let object = {};
object[uid] = "12345";
console.log(object[uid]); // '12345'
console.log(uid); // 'Symbol(uid)'

Symbol.for首先在全局Symbol注册表中搜索键为’uid’的Symbol,如果存在,直接返回已存在的,否则创建一个新的Symbol,并使用这个键在Symbol全局注册表中注册,随机返回新创建的Symbol。

1
2
3
4
let uid = Symbol.for("uid");
let uid2 = Symbol.for("uid");
console.log(uid === uid2); // true
console.log(uid2); // 'Symbol(uid)'

还有一个与Symbol共享有关的特性,Symbol.keyFor方法在Symbol全局注册表中检索与Symbol有关的键;

1
2
3
4
5
6
let uid = Symbol.for("uid");
console.log(Symbol.keyFor(uid)); // uid
let uid2 = Symbol.for("uid");
console.log(Symbol.keyFor(uid2));// uid
let uid3 = Symbol("uid");
console.log(Symbol.keyFor(uid3));// undefined

Symbol全局注册表中不存在uid3这个Symbol(没有通过Symbol.for注册),返回undefined;

Symbol与类型强制转换

不能将Symbol强制转换为字符串和数字类型;Symbol在需要逻辑判断表达式中为真;

1
2
3
4
var uid = Symbol.for("uid"),
desc = uid + ""; // Cannot convert a Symbol value to a string
var sum = uid / 1; // Cannot convert a Symbol value to a number
!!uid; // true

Symbol属性检索

Object.keys和Object.getOwnpropertyNames不能检索出Symbol属性,ES6中Object.getOwnpropertySymbols来支持这个功能,返回对象中的所有Symbol属性的数组;

1
2
3
4
5
6
7
8
let uid = Symbol.for("uid");
let object = {
[uid]: "12345"
};
let symbols = Object.getOwnPropertySymbols(object);
console.log(symbols.length); // 1
console.log(symbols[0]); // 'Symbol(uid)'
console.log(object[symbols[0]]);// '12345'

通过well-known Symbol暴露内部操作

ES6通过在原型链上定义与Symbol相关的属性来暴露更多的语言内部逻辑;开放了以前JS中常见的内部操作,并通过预定义一些well-known Symbol来表示。每一个这类Symbol都是symbol对象的一个属性,例如Symbol.match

Symbol.hasInstance

一个在执行instanceof时调用的内部方法,用于检测对象的继承信息;

Function.prototype中有一个Symbol.hasInstance方法,每一个函数都继承了这个方法,用于确定对象是否是函数的实例。该方法被定义为不可写,不可配置并且不可枚举。

1
Object.getOwnPropertyDescriptor(Function.prototype, Symbol.hasInstance); // {writable: false, enumerable: false, configurable: false, value: ƒ}

看一段代码:

1
2
3
Array.hasOwnProperty(Symbol.hasInstance); // false
Function.prototype.hasOwnProperty(Symbol.hasInstance); // true
Array instanceof Function; // true

从上面可以看出Array是Function的实例;Function.prototype具有自有属性Symbol.hasInstance,Array没有Symbol.hasInstance;

obj instanceof Array实际等价于Array[Symbol.hasInstance](obj),由于Array没有Symbol.hasInstance,所以会从原型链中寻找,最终在Function.prototype中找到,最终实际执行的就是Function.prototype[Symbol.hasInstance](obj)。

明白了instanceof的实际调用过程,就可以按我们的意愿来定制一些内容,例如定义一个无实例的函数:

1
2
3
4
5
6
7
8
9
10
function MyObject() {
// empty
}
Object.defineProperty(MyObject, Symbol.hasInstance, {
value: function(v) {
return false;
}
});
let obj = new MyObject();
console.log(obj instanceof MyObject); // false

Symbol.isConcatSpreadable

concat元素

1
2
3
4
let colors1 = [ "red", "green" ],
colors2 = colors1.concat([ "blue", "black" ]);
console.log(colors2.length); // 4
console.log(colors2); ["red","green","blue","black"]

concat数组

1
2
3
4
let colors1 = [ "red", "green" ],
colors2 = colors1.concat([ "blue", "black" ], "brown");
console.log(colors2.length); // 5
console.log(colors2); // ["red","green","blue","black","brown"]

JS规范声明,concat凡是传入了数组参数,就会自动将它们分解成独立元素,在ES6之前,我们根本无法调整这个特性;

Symbol.isConcatSpreadable属性是一个布尔值,如果该属性为true,则表示对象由length属性和数字键,故它的数值型属性应该被独立添加到cancat()调用结果中;与其他well-known Symbol属性不同的是,这个Symbol属性默认情况下不会出现标准对象,它只是一个可选对象,用于增强作用于特定对象类型的concat()方法的功能,有效简化其默认特性;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let collection = {
0: "Hello",
1: "world",
length: 2,
[Symbol.isConcatSpreadable]: true
};
let messages = [ "Hi" ].concat(collection);
console.log(messages.length); // 3
console.log(messages); // ["Hi","Hello","world"]
let collection1 = {
0: "Hello",
1: "world",
length: 2,
[Symbol.isConcatSpreadable]: true
};
let messages = [ "Hi" ].concat(collection1);
console.log(messages.length); // 2
console.log(messages); // ["Hi",{...}]

Symbol.match Symbol.replace Symbol.search Symbol.split

这四个Symbol用在String对应的四个方法String.match/replace/search/split中且当参数为正则表达式时。

可以定义一个对象的这四个Symbol属性方法,这对象就可以代替正则表达式来应用在上面的对应场景中;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// effectively equivalent to /^.{10}$/
let hasLengthOf10 = {
[Symbol.match]: function(value) {
return value.length === 10 ? [value.substring(0, 10)] : null;
},
[Symbol.replace]: function(value, replacement) {
return value.length === 10 ? replacement + value.substring(10) : value;
},
[Symbol.search]: function(value) {
return value.length === 10 ? 0 : -1;
},
[Symbol.split]: function(value) {
return value.length === 10 ? ["", ""] : [value];
}
};
let message1 = "Hello world", // 11 characters
message2 = "Hello John"; // 10 characters
let match1 = message1.match(hasLengthOf10),
match2 = message2.match(hasLengthOf10);
console.log(match1); // null
console.log(match2); // ["Hello John"]
let replace1 = message1.replace(hasLengthOf10),
replace2 = message2.replace(hasLengthOf10);
console.log(replace1); // "Hello world"
console.log(replace2); // "Hello John"
let search1 = message1.search(hasLengthOf10),
search2 = message2.search(hasLengthOf10);
console.log(search1); // -1
console.log(search2); // 0
let split1 = message1.split(hasLengthOf10),
split2 = message2.split(hasLengthOf10);
console.log(split1); // ["Hello world"]
console.log(split2); // ["", ""]

Symbol.toPrimitive

JS引擎中,经常会尝试将对象转换到相应的原始值,例如比较一个对象与字符串;对象被转换为原始值,会有三种选择:数字,字符串或无类型偏好的值。对于大多数标准对象,当对象被转换为数字时:

  1. 调用valueOf方法,如果结果为原始值,则返回
  2. 否则,调用toString方法,如果结果值为原始值,则返回
  3. 如果再无可选值,则抛出异常

当对象被转换为字符串时:

  1. 调用toSring方法,如果结果为原始值,则返回
  2. 否则,调用valueOf方法,如果结果值为原始值,则返回
  3. 如果再无可选值,则抛出异常

大多数情况下,标准对象会将默认模式按数字模式处理(除了Date对象);默认模式只用于==运算,+运算及给Date构造函数传递一个参数时。

在ES6标准中,通过Symbol.toPrimitive方法可以更改那个暴露出来的原始值;Symbol.toPrimitive被定义在每一个标准类型的原型上,并且规定了当对象被转换为原始值时应该执行的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Temperature(degrees) {
this.degrees = degrees;
}
Temperature.prototype[Symbol.toPrimitive] = function(hint) {
switch (hint) {
case "string":
return this.degrees + "\u00b0"; // degrees symbol
case "number":
return this.degrees;
case "default":
return this.degrees + " degrees";
} };
var freezing = new Temperature(32);
console.log(freezing + "!"); // "32 degrees!"
console.log(freezing / 2); // 16
console.log(String(freezing)); // "32°"

Symbol.toStringTag

当检测一个数据的类型时,可能会用instanceof,这样在使用iframe标签情况下会有一定问题,不同的iframe代表不同的领域,不同领域的构造函数是不同的,当数据传递到不同的领域时,使用instanceof会得不到预期的结果;

这时我们可能会用Object.prototype.toString.call来处理;

如区分原生JSON对象和自建对象

1
2
3
4
function supportsNativeJSON() {
return typeof JSON !== "undefined" &&
Object.prototype.toString.call(JSON) === "[object JSON]";
}

在ES6中重新定义了原生对象过去的状态,通过Symbol.toStringTag改变了调用Object.prototype.toString方法时返回的身份标示;这个Symbol所代表的属性在每一个对象中都存在,其定义了调用对象的Object.prototype.toString.call方法时返回的值。

1
2
3
4
5
6
7
function Person(name) {
this.name = name;
}
Person.prototype[Symbol.toStringTag] = "Person";
var me = new Person("Nicholas");
console.log(me.toString()); // [object Person]
console.log(Object.prototype.toString.call(me)); // [object Person]

Symbol.unscopables

The Symbol.unscopables well-known symbol is used to specify an object value of whose own and inherited property names are excluded from the with environment bindings of the associated object.

1
2
3
4
5
6
7
8
var keys = [];
with (Array.prototype) {
keys.push('something');
}
Object.keys(Array.prototype[Symbol.unscopables]);
// ["copyWithin", "entries", "fill", "find", "findIndex", "includes", "keys", "values"]

也可以改变对象的Symbol.unscopables属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var obj = {
foo: 1,
bar: 2
};
obj[Symbol.unscopables] = {
foo: false,
bar: true
};
with (obj) {
console.log(foo); // 1
console.log(bar); // ReferenceError: bar is not defined
}

ES6之对象

发表于 2017-11-21 | 分类于 JS

对象字面量语法扩展

属性初始值的简写

1
2
3
4
5
6
function createPerson(name, age) {
return {
name: name,
age: age
};
}

属性与为属性赋值的变量是重复的,可以简写为:

1
2
3
4
5
6
function createPerson(name, age) {
return {
name,
age
};
}

这样可以避免命名错误以及不小心的拼写错误。

对象方法的简写

1
2
3
4
5
6
var person = {
name: "Nicholas",
sayName: function() {
console.log(this.name);
}
};

可以简写为:

1
2
3
4
5
6
var person = {
name: "Nicholas",
sayName() {
console.log(this.name);
}
};

简写方法sayName里面可以使用super关键字;

可计算属性名

ES5及早期版本的对象实例中,如果想要通过计算得到属性名,就需要用方括号代替点记法。并且有空格的字符串不能直接作为属性名,也需要方括号代替;

1
2
3
4
5
6
var person = {},
lastName = "last name";
person["first name"] = "Nicholas";
person[lastName] = "Zakas";
console.log(person["first name"]);
console.log(person[lastName]);

ES6中,可在对象字面量中使用可计算属性名称,其语法与引用对象实例的可计算属性名称相同,也是使用方括号。

1
2
3
4
5
6
7
let lastName = "last name";
let person = {
"first name": "Nicholas",
[lastName]: "Zakas"
};
console.log(person["first name"]);
console.log(person[lastName]);

新增方法

Object.is

在JS中比较两个值,使用===跟==比较多,但是===在有些情况也不完全准确;+0与-0在JS引擎中是两个完全不同的实体,NaN === NaN 返回值为false,需要用isNaN()才能检测出NaN;

1
2
3
4
5
6
7
8
9
10
11
12
console.log(+0 == -0); // true
console.log(+0 === -0); // true
console.log(Object.is(+0, -0)); // false
console.log(NaN == NaN); // false
console.log(NaN === NaN); // false
console.log(Object.is(NaN, NaN)); // true
console.log(5 == 5); // true
console.log(5 == "5"); // true
console.log(5 === 5); // true
console.log(5 === "5"); // false
console.log(Object.is(5, 5)); // true
console.log(Object.is(5, "5")); // false

Object.assign

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var receiver = {};
var test = {name: 'zakas'};
Object.assign(receiver,
{
type: "js",
name: "file.js"
},
{
type: "css",
data: test,
}
);
console.log(receiver.type); // css
console.log(receiver.name); // file.js
console.log(receiver.data); // {name: 'zakas'}
test.name = 'hi';
console.log(receiver.data); // {name: 'hi'}

Object.assign参数后面的对象会覆盖前面的对象,并且为浅复制;

Object.assign会把访问器属性复制为数据属性;

1
2
3
4
5
6
7
8
9
10
var receiver = {},
supplier = {
get name() {
return "file.js"
}
};
Object.assign(receiver, supplier);
var descriptor = Object.getOwnPropertyDescriptor(receiver, "name");
console.log(descriptor.value); // "file.js"
console.log(descriptor.get); // undefined

重复的对象字面量属性

ES5严格模式重复字面量属性会报错;ES6无论严格或者非严格模式下,重复属性都会选取最后一个取值;

1
2
3
4
5
6
"use strict";
var person = {
name: "Nicholas",
name: "Greg" // no error in ES6 strict mode
};
console.log(person.name); // Greg

自有属性枚举顺序

  1. 所有数字键按升序排列
  2. 所有字符串键按照它们被加入对象的顺序排序
  3. 所有symbol按照它们被加入的顺序排序
1
2
3
4
5
6
7
8
9
10
11
var obj = {
a: 1,
0: 1,
c: 1,
2: 1,
b: 1,
1: 1
};
obj.d = 1;
console.log(Object.getOwnPropertyNames(obj).join(""));
// 012acbd

增强对象原型

改变对象的原型setPrototypeOf

对象原型的真实值被储存在内部的专用属性[[Prototype]]中,调用Object.getPrototypeOf返回储存在其中的值,调用Object.setPrototypeOf方法改变其中的值;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let person = {
getGreeting() {
return "Hello";
}
};
let dog = {
getGreeting() {
return "Woof";
}
};
// prototype is person
let friend = Object.create(person);
console.log(friend.getGreeting()); // Hello
console.log(Object.getPrototypeOf(friend) === person); // true
// set prototype to dog
Object.setPrototypeOf(friend, dog);
console.log(friend.getGreeting()); // "Woof"
console.log(Object.getPrototypeOf(friend) === dog); // true

简化原型访问的Super引用

ES6引入了super关键字,super引用相当于指向对象原型的指针,实际上也就是Object.getPrototypeOf()的效果;

1
2
3
4
5
6
let friend = {
getGreeting() {
// in the previous example, this is the same as:
// Object.getPrototypeOf(this).getGreeting.call(this)
return super.getGreeting() + ", hi!";
} };

super关键字必须要在使用简写方法的对象中使用super引用,如果在其他方法声明中使用会导致语法错误;

1
2
3
4
5
let friend = {
getGreeting: function() {// syntax error
return super.getGreeting() + ", hi!"; // 'super' keyword unexpected here
}
};

正式的方法定义

ES6正式将方法定义为一个函数,它会由一个内部的[[HomeObject]]属性来容纳这个方法从属的对象;

1
2
3
4
5
6
7
8
9
10
let person = {
// method
getGreeting() {
return "Hello";
}
};
// not a method
function shareGreeting() {
return "Hi!";
}

上段代码的getGreeting方法的[[HomeObject]]属性值为person,而创建shareGreeting函数时,由于未将其赋值给一个对象,因为该方法没有明确定义[[HomeObject]]属性。

super的所有引用都通过[[HomeObject]]属性调用Object.getPrototypeOf方法来检索原型的引用;然后搜寻原型找到同名函数;最后,设置this绑定并且调用相应的方法;

1
2
3
4
5
6
7
8
9
10
11
12
let person = {
getGreeting() {
return "Hello";
}
};
// prototype is person
let friend = {
getGreeting() {
return super.getGreeting() + ", hi!";
} };
Object.setPrototypeOf(friend, person);
console.log(friend.getGreeting());

friend.getGreeting()方法的[[HomeObject]]的属性值时friend,friend的原型是person,所以super.getGreeting()等价于person.getGreeting.call(this);

ES6之解构

发表于 2017-11-21 | 分类于 JS

为何使用解构功能

在编码过程中,我们经常定义许多对象和数组,然后有组织地从中提取相关的信息片段;如果对象跟数组结构复杂,我们就需要深入挖掘整个数据结构才能找到所需数据。

所以ES6位对象与数组都添加了结构功能,将数据结构打散的过程变得更加简单;许多语言都通过极少量的语法实现了结构功能,以简化获取信息的过程;而ES6实际上利用了你早已熟悉的语法:对象和数组字面量的语法。

对象解构

对象解构的语法形式是在一个赋值操作符左边放置一个对象字面量。

1
2
3
4
5
6
7
let node = {
type: "Identifier",
name: "foo"
};
let { type, name } = node;
console.log(type); // "Identifier"
console.log(name); // "foo"

使用var let或const解构声明变量,必须要提供初始化程序。下面几行代码全部都会导致抛出语法错误,它们都缺少了初始化程序:

1
2
3
4
5
6
// syntax error!
var { type, name };
// syntax error!
let { type, name };
// syntax error!
const { type, name };

解构赋值

如果在定义变量之后想要修改它们的值,需要用小括号包裹解构赋值语句,因为JS引擎将一对开放的花括号视为一个代码块,而语法规定,代码块语句不允许出现在赋值语句左侧,添加小括号后可以将块语句转化为一个表达式,从而实现解构赋值的过程。如下面的({ type, name } = node)语句:

1
2
3
4
5
6
7
8
9
10
let node = {
type: "Identifier",
name: "foo"
},
type = "Literal",
name = 5;
// assign different values using destructuring
({ type, name } = node);
console.log(type); // "Identifier"
console.log(name);

解构赋值表达式(=号右侧)如果为null或者undefined会导致程序抛出错误;let {hello} = null会抛出错误Cannot destructure propertyhelloof 'undefined' or 'null'.

默认值

当指定的局部变量名称在对象中不存在的时候,会被赋值为undefined,我们可以设置一个默认值,来处理这种情况:

1
2
3
4
5
6
7
8
let node = {
type: "Identifier",
name: "foo"
};
let { type, name, value = true } = node;
console.log(type); // "Identifier"
console.log(name); // "foo"
console.log(value);// true

为非同名局部变量赋值

也可以使用非同名的局部变量来解构对象,并且可以指定默认值:

1
2
3
4
5
6
7
8
let node = {
type: "Identifier",
name: "foo"
};
let { type: localType, name: localName, first_name: firstName='hi' } = node;
console.log(localType); // "Identifier"
console.log(localName); // "foo"
console.log(firstName); // "hi"

嵌套解构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let node = {
type: "Identifier",
name: "foo",
loc: {
start: {
line: 1,
column: 1 },
end: {
line: 1,
column: 4 }
} };
let { loc: { start }} = node;
console.log(start.line); // 1
console.log(start.column); // 1

数组解构

数组解构语法比对象解构更简单,使用的是数组字面量,且解构操作全部在数组内完成,而不是像对象字面量语法一样使用对象的命名属性:

1
2
3
4
let colors = [ "red", "green", "blue" ];
let [ firstColor, ,thirdColor ] = colors;
console.log(firstColor); // "red"
console.log(thirdColor); // "blue"

当通过var let或者const声明数组解构的绑定时,必须要提供一个初始化程序;

解构赋值

数组解构也可用于赋值上下文,不需要用小括号包裹, 因为[]跟{}不一样,后者有块级的功能;

1
2
3
4
5
6
let colors = [ "red", "green", "blue" ],
firstColor = "black",
secondColor = "purple";
[ firstColor, secondColor ] = colors;
console.log(firstColor); // "red"
console.log(secondColor); // "green"

数组也可用来交换两个变量的值:

1
2
3
4
5
let a = 1,
b = 2;
[ a, b ] = [ b, a ];
console.log(a); // 2
console.log(b); // 1

与对象解构一样,如果右侧为null或者undefined,抛出错误;

默认值

1
2
3
4
let colors = [ "red" ];
let [ firstColor, secondColor = "green" ] = colors;
console.log(firstColor); // "red"
console.log(secondColor); // "green"

嵌套解构

1
2
3
4
5
let colors = [ "red", [ "green", "lightgreen" ], "blue" ];
// later
let [ firstColor, [ secondColor ] ] = colors;
console.log(firstColor); // "red"
console.log(secondColor); // "green"

不定元素

1
2
3
4
5
6
let colors = [ "red", "green", "blue" ];
let [ firstColor, ...restColors ] = colors;
console.log(firstColor); // "red"
console.log(restColors.length); // 2
console.log(restColors[0]); // "green"
console.log(restColors[1]); // "blue"

通过上面这个语法,可以用来实现生成数组的副本;

1
2
3
4
// cloning an array in ECMAScript 6
let colors = [ "red", "green", "blue" ];
let [ ...clonedColors ] = colors;
console.log(clonedColors); // "[red,green,blue]"

被解构的数组中,不定元素必须为最后一个条目,在后面继续添加逗号会导致程序抛出语法错误

混合解构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let node = {
type: "Identifier",
name: "foo",
loc: {
start: {
line: 1,
column: 1 },
end: {
line: 1,
column: 4 }
},
range: [0, 3]
};
let {
loc: { start },
range: [ startIndex ]
} = node;
console.log(start.line); // 1
console.log(start.column); // 1
console.log(startIndex); // 0

解构参数

ES5解析参数可能会这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// properties on options represent additional parameters
function setCookie(name, value, options) {
options = options || {};
let secure = options.secure,
path = options.path,
domain = options.domain,
expires = options.expires;
// code to set the cookie
}
// third argument maps to options
setCookie("type", "js", {
secure: true,
expires: 60000
});

利用解构我们可以:

1
2
3
function setCookie(name, value, { secure, path, domain, expires }) {
// code to set the cookie
}

必须传值的解构参数

解构参数在默认情况下,如果调用函数时不提供被解构的参数会导致程序抛出错误;如果这样调用上面的setCookie函数setCookie('type', 'js')会抛出错误;

实际上JS是这样解析的:

1
2
3
4
function setCookie(name, value, options) {
let { secure, path, domain, expires } = options;
// code to set the cookie
}

当第三个参数不传值时,实际options被赋值为undefined,根据前面讨论过的解构赋值表达式=号右边的值不能为undefined或者null,所以第三个参数是必须传值的;

可以设置默认值来避免必须传值:

1
2
3
function setCookie(name, value, { secure, path, domain, expires } = {}) {
// empty
}

解构参数默认值

1
2
3
4
5
6
7
8
9
function setCookie(name, value,
{
secure = false,
path = "/",
domain = "example.com",
expires = new Date(Date.now() + 360000000)
} = {} ){
// empty
}

也可以为第三个参数设置一个默认对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const cookieDefault = {
secure: false,
path: '/',
domain: 'example.com',
expires: new Date(Date.now() + 360000000)
};
function setCookie(name, value,
{
secure = cookieDefault.secure,
path = cookieDefault.path,
domain = cookieDefault.domain,
expires = cookieDefault.expires
} = cookieDefault ){
// empty
}

ES6之函数

发表于 2017-11-19 | 分类于 JS

函数形参默认值

ES5默认形参

ES5中模拟默认形参

1
2
3
4
function makeRequest(url, timeout, callback) {
timeout = timeout || 2000;
callback = callback || function() {};
}

但是这种方法也有缺陷,如果第二个值我们传0,会被视为一个假值,函数timeout最后取值却为2000。

更好的做法是:

1
2
3
4
function makeRequest(url, timeout, callback) {
timeout = typeof timeout !== undefined ? timeout : 2000;
callback = typeof callback !== undefined ? callback : function() {};
}

ES6默认形参

ES6做法:

1
2
3
function makeRequest(url, timeout=2000, callback=function(){}) {
// do something
}

注意: 当timeout传值为undefined时,最终取值为默认值2000;

1
2
3
4
function makeRequest(url, timeout = 2000, callback) {
console.log(timeout)
}
makeRequest('/foo', undefined); // 2000

对arguments影响

ES5非严格模式下,形参值被改变,arguments对应的也跟着改变:

1
2
3
4
5
6
7
8
9
10
11
12
13
function mixArgs(first, second) {
console.log(first === arguments[0]);
console.log(second === arguments[1]);
first = "c";
second = "d";
console.log(first === arguments[0]);
console.log(second === arguments[1]);
}
mixArgs("a", "b");
// true
// true
// true
// true

严格模式下,无论形参如何被赋值,arguments不再跟着改变;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function mixArgs(first, second) {
'use strict';
console.log(first === arguments[0]);
console.log(second === arguments[1]);
first = "c";
second = "d";
console.log(first === arguments[0]);
console.log(second === arguments[1]);
}
mixArgs("a", "b");
// true
// true
// false
// false

ES6中,如果使用了默认参数,则无论是否显式定义了严格模式,arguments行为与ES5严格模式下保持一致;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function mixArgs(first, second = "b") {
console.log(arguments.length);
console.log(first === arguments[0]);
console.log(second === arguments[1]);
first = "c";
second = "d"
console.log(first === arguments[0]);
console.log(second === arguments[1]);
}
mixArgs("a");
// 1
// true
// false
// false
// false

默认参数表达式

默认参数也可以使用表达式;

默认参数表达式求值条件:当调用函数不传入默认参数对应位置的值时才会调用;

1
2
3
4
5
6
7
8
9
10
let value = 5;
function getValue() {
return value++;
}
function add(first, second = getValue()) {
return first + second;
}
console.log(add(1, 1)); // 2
console.log(add(1)); // 6
console.log(add(1)); // 7

默认参数临时死区(TDZ)

默认参数也类似let有默认死区,后面的参数不能用在之前参数的默认值上:

1
2
3
4
5
function add(first = second, second) {
return first + second;
}
console.log(add(1, 1)); // 2
console.log(add(undefined, 1)); // Uncaught ReferenceError: second is not defined

无命名参数

由于之前当有多个无命名参数时,我们需要利用arguments参数来处理,有一个问题是需要记录从哪个索引开始,比较麻烦;

1
2
3
4
5
6
7
8
function pick(object) {
let result = Object.create(null);
// start at the second parameter
for (let i = 2, len = arguments.length; i < len; i++) {
result[arguments[i]] = object[arguments[i]];
}
return result;
}

ES6做法:

1
2
3
4
5
6
7
8
function pick(object, ...keys) {
let result = Object.create(null);
// start at the second parameter
for (let i = 0, len = keys.length; i < len; i++) {
result[keys[i]] = object[keys[i]];
}
return result;
}

不定参数使用限制:最多只能一个不定参数,而且一定要放在所有参数的最后;

1
2
3
4
5
6
7
8
function pick(object, ...keys, last) {
let result = Object.create(null);
for (let i = 0, len = keys.length; i < len; i++) {
result[keys[i]] = object[keys[i]];
}
return result;
}
pick() // error

增强的Function构造函数

Function构造函数也可以使用默认参数和无命名参数:

1
var add = new Function("first", "second = first", "...rest", "return first + second");

展开运算符

1
2
let values = [-25, -50, -75, -100];
console.log(Math.max(...values, 0));

name属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var doSomething = function doSomethingElse() {
// empty
};
var person = {
get firstName() {
return "Nicholas"
},
sayName: function() {
console.log(this.name);
} }
console.log(doSomething.name); // doSomethingElse
console.log(person.sayName.name); // sayName
console.log(person.firstName.name); // get firstName
console.log(doSomething.bind().name); // bound doSomethingElse
console.log((new Function()).name); // anonymous

注意:

  1. 命名函数(doSomethingElse)并且赋值了一个变量(doSomething),取命名的名字doSomethingElse;
  2. get属性的函数前面会有一个get; // chrome是返回undefined,需要确认是不是实现不一致;
  3. 利用bind生成的新函数前面有一个bound
  4. Function构造函数生成的函数为anonymous

明确函数的多重用途

JS函数有两个不同的内部方法: [[Call]]和[[Construct]]。当通过new关键字调用函数时,执行的是[[Construct]],它负责创建一个通常被称为实例的新对象,然后再执行函数体,将this绑定到新实例上;如果不通过new关键字调用函数,则执行[[Call]]函数,从而直接执行代码中的函数体。具有[[Construct]]方法的函数被统称为构造函数。

注意:不是所有的函数都有[[Construct]]方法,例如箭头函数就没有[[Construct]]方法,所以箭头函数不能使用new关键字调用;

之前ES5判断一个函数是不是通过’new’调用会判断this的原型, 但是这也有问题,函数执行没有用new但使用call来改变this同样可以不报错:

1
2
3
4
5
6
7
8
9
10
function Person(name) {
if (this instanceof Person) {
this.name = name; // using new
} else {
throw new Error("You must use new with Person.")
}
}
var person = new Person("Nicholas");
var notAPerson = Person("Nicholas");
var notAPerson = Person.call(person, "Michael"); // works

元属性new.target

为了解决判断函数是否通过new关键字调用的问题,ES6引入了new.target这个元属性;

元属性是指非对象的属性,其可以提供非对象目标的补充信息;当调用函数的[[Construct]]方法时,new.target被赋值为new操作符的目标,通常是新创建对象实例,也就是函数体内this的构造函数,如果调用[[Call]]方法,则new.target的值为undefined。则判断一个函数是否是通过new关键字调用的,可以这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person(name) {
if (new.target === Person) {
this.name = name;
} else {
throw new Error("You must use new with Person.")
}
}
function AnotherPerson(name) {
Person.call(this, name);
}
var person = new Person("Nicholas");
var person = Person("Zakas"); //Uncaught Error: You must use new with Person.
var anotherPerson = new AnotherPerson("Nicholas");// Uncaught Error: You must use new with Person.

在函数体外使用new.target是一个语法错误

块级函数

ES5严格模式下声明块级函数会抛出错误;ES6中,会将块级函数视作一个块级声明,从而在代码块内可以访问和调用;但是块级函数会被提升至顶部;

1
2
3
4
5
6
7
8
if (true) {
console.log(typeof doSomething);
function doSomething() {
// empty
}
doSomething();
}
console.log(typeof doSomething); // function

如果要真正的定义块级函数,可以使用let表达式,这样函数表达式就不会提升了;并且有临时死区的情况,如:

1
2
3
4
5
6
7
if (true) {
console.log(typeof doSomething); // error
let doSomething = function () {
// empty
}
doSomething();
}

箭头函数

  1. 无this super arguments和new.target绑定

这些值由外围最近一层非箭头函数决定;

1
2
3
4
5
6
7
8
9
10
let PageHandler = {
id: "123456",
init: function() {
document.addEventListener("click",
event => this.doSomething(event.type), false);
},
doSomething: function(type) {
console.log("Handling " + type + " for " + this.id);
}
};

  1. 不能通过new关键字调用

正如之前提到过的箭头函数没有[[Constructor]]方法,不能通过new关键字调用,;

1
2
var MyType = () => {},
object = new MyType(); // MyType is not a constructor
  1. 没有原型
1
2
3
4
var MyType = () => {};
var test = function() {};
console.log(MyType.prototype); // undefined
console.log(test.prototype.toString()); // [object Object]
  1. 不可以改变this的绑定
1
2
3
4
var MyType = () => {console.log(this)};
MyType.bind({name: 'hi'})(); // Window
var test = function() {console.log(this)};
test.bind({name: 'hi'})(); // {name: 'hi'}
  1. 不支持arguments对象
    绑定的arguments是外围非箭头函数的arguments
1
2
3
4
5
function createArrowFunctionReturningFirstArg() {
return () => arguments[0];
}
var arrowFunction = createArrowFunctionReturningFirstArg(5);
console.log(arrowFunction());
  1. 不支持重复的命名参数

传统函数,只有严格模式不能使用重名的参数;

1
2
3
function test(name, name){};
'use strict';
function test(name, name){}; //Duplicate parameter name not allowed in this context

箭头函数中,不支持重复的命名参数

1
var test = (name, name) => {console.log(this)} // Duplicate parameter name not allowed in this context

尾调用优化

ES6缩减了严格模式下尾调用栈的大小(非严格模式下不影响),如果满足以下条件,尾调用不再创建新的栈帧,而是清楚并重用当前栈帧:

  • 尾调用不访问当前栈帧的变量;(函数不是一个闭包)
  • 在函数内部,尾调用是最后一条语句;
  • 尾调用的结果作为函数值返回;

满足条件的尾调用:

1
2
3
4
5
"use strict";
function doSomething() {
// optimized
return doSomethingElse();
}

不满足第一个条件

1
2
3
4
5
6
7
"use strict";
function doSomething() {
var num = 1,
func = () => num;
// not optimized - function is a closure
return func();
}

不满足第二个条件

1
2
3
4
5
6
"use strict";
function doSomething() {
// not optimized - call isn't in tail position
var result = doSomethingElse();
return result;
}

不满足第三个条件

1
2
3
4
5
"use strict";
function doSomething() {
// not optimized - must add after returning
return 1 + doSomethingElse();
}

ES6之let与const 字符串和正则

发表于 2017-11-15 | 分类于 JS

let const

let与const其实没什么特别的,主要就是提供的块级作用域,存在于函数内部和块中(字符{和}之间的区域),没有var的变量提升;

1
2
var num = 1 + num1; // throw error
let num1 = 5;

需要注意的点:

禁止重声明

1
2
var msg = 'hi';
let msg = 'test'; // Uncaught SyntaxError: Identifier 'msg' has already been declared

临时死区TDZ(Temporal Dead Zone)

使用let和const定义的变量在定义处到块级作用域开始之间是不能使用定义的变量的,使用会报错;

1
2
3
4
5
var msg = 'hi';
if(true) {
typeof msg; // Uncaught ReferenceError: msg is not defined
let msg = 'hello';
}

循环

由于词法作用域的缘故,执行console.log(i)指向的都市同一个i,最后值是10

1
2
3
4
5
6
7
8
9
var funcs = [];
for (var i = 0; i < 10; i++) {
funcs.push(function() {
console.log(i);
});
}
funcs.forEach(function(func) {
func(); // outputs the number "10" ten times
});

let声明每次迭代循环会创建一个新变量:

1
2
3
4
5
6
7
8
9
10
var funcs = [];
for (var i = 0; i < 10; i++) {
funcs.push((function(value) {
return function() {
console.log(value);
} }(i)));
}
funcs.forEach(function(func) {
func(); // outputs 0, then 1, then 2, up to 9
});

全局作用域行为

var在全局作用域定义的是window的一个属性

1
2
3
var msg = "Hello!";
console.log(window.msg); // 'Hello!'
console.log(window.msg === msg); // true

而let和const创建了一个绑定并遮蔽了全局的msg变量;let和const比较安全;

1
2
3
let msg1 = "Hello!";
console.log(window.msg1); // undefined
console.log(window.msg1 === msg1); // false

const绑定的是引用

const绑定是引用,只要引用不变,不报错;

1
2
3
4
const obj = {msg: 1};
obj.msg = 2;
console.log(obj); // {msg: 2}
obj = {msg: 2} // Uncaught TypeError: Assignment to constant variable.

块级绑定最佳实践是尽量用const,只有确实需要改变变量的值时用let。

字符串变动

Unicode支持

ES6出现之前,JS字符一直基于16位字符编码(UTF-16)进行构建。每16位的序列是一个编码单元,代表一个字符。length\chatAt等属性与方法都是基于这种编码单元构成的。

UTF-16编码中:

  • 前2**16个码位均以16位的编码单元表示,这个范围被称作BMP
  • 但是如果超出16位编码范围之外的码位,就无法仅用16位来表示,UTF-16引入了代理对,用两个16位编码单元来表示;

“𠮷”这个字符就是超出16位表示范围的,使用了代理对来表示的,在ES5中:

1
2
3
4
5
6
7
let text = "𠮷";
console.log(text.length); // 2
console.log(/^.$/.test(text)); // false
console.log(text.charAt(0)); // ""
console.log(text.charAt(1)); // ""
console.log(text.charCodeAt(0)); // 55362
console.log(text.charCodeAt(1)); // 57271

codePointAt / fromCodePoint

ES6引入了codePointAt与fromcodePoint来支持超出16位码元,这两个方法接受编码单元的位置而非字符位置作为参数;如:

1
2
3
4
5
6
7
8
let text = "𠮷a";
console.log(text.charCodeAt(0)); // 55362
console.log(text.charCodeAt(1)); // 57271
console.log(text.charCodeAt(2)); // 97
console.log(text.codePointAt(0)); // 134071 超出0xffff
console.log(text.codePointAt(1)); // 57271
console.log(text.codePointAt(2)); // 97
console.log(String.fromCodePoint(134071)); // '𠮷'

注意charCodeAt与codePointAt的区别,如上面的text.codePointAt(0)计算了’𠮷’的完整码位-两个编码单元,超出0xffff;

新增字符串方法includes / startWith / endWith / repeat

1
2
3
4
5
6
7
8
9
10
let msg = "Hello world!";
console.log(msg.startsWith("Hello")); // true
console.log(msg.endsWith("!")); // true
console.log(msg.includes("o")); // true
console.log(msg.startsWith("o")); // false
console.log(msg.endsWith("world!")); // true
console.log(msg.includes("x")); // false
console.log(msg.startsWith("o", 4)); // true
console.log(msg.endsWith("o", 8)); // true
console.log(msg.includes("o", 8)); // false

这里需要注意的是endsWith是从用第二个参数减去搜索字符的长度得到的数字位置开始搜索的;如msg.endsWith("o", 8)是从8-len('o')=7开始搜索的;以此类推如果是msg.endsWith("orl", 8)则是从8-len('orl')=5开始搜索返回false;

1
2
3
console.log('hello'.repeat(2)); // hellohello
console.log('a'.repeat(2)); // aa
console.log('bc'.repeat(3)); // bcbcbc

模版字面量

ES6引入了模版字面量语法支持更丰富的字符串功能:

  • 多行字符串
  • 基本的字符串格式化,支持嵌套
  • HTLM转义

看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let message = `hello world!`
console.log(message) // 'hello world'
console.log(typeof message) // 12
// 多行字符串
var msg = `Multiline
string`;
console.log(msg); // 'Multiline
// string'
console.log(message.length); // 16
// 字符串占位
let name = 'world',
msg1 = `Hello, ${name}`;
console.log(message); // "Hello, world!"

除了上面的功能之外,还有一个标签模版的功能;标签可以是一个函数,第一个参数是一个数组,包含JS解释过后的字面量字符串,之后的所有参数都是每一个占位符的解释值;如:

1
2
3
4
5
6
7
8
9
10
11
12
13
function tag(literals, ...substitutions) {
console.log(literals);
console.log(literals.raw)
console.log(substitutions);
return 'hello world!';
}
var name1 = 'lili';
var name2 = 'lucy';
var msg = tag`abc${name1}def${name2}\n`;
// ["abc", "def", "↵", raw: Array(3)]
// ["abc", "def", "\n"]
// ["lili", "lucy"]
console.log(msg); // 'hello world!'

特别要说明的是literals.raw是对应literals的原生字符串信息;如\n为\\n;

正则变动

u修饰符

上面我们看到”𠮷”匹配不到/^.$/正则表达式;这是因为正则是通过编码单元来匹配字符串的,而”𠮷”是由两个编码单元组成的,所以/^.$/是匹配不成功的;

为了解决这种问题,ES6引入了一个支持Unicode的u修饰符;当使用了修饰符u,正则表达式就从编码单元模式切换为字符模式,如此一来正则表达式就不会视代理对为两个字符,从而安全按照我们预期正常运行;

1
2
3
4
let text = "𠮷";
console.log(text.length); // 2
console.log(/^.$/.test(text)); // false
console.log(/^.$/u.test(text)); // true

检测是否支持修饰符u:

1
2
3
4
5
6
7
8
function hasRegExpU() {
try {
var pattern = new RegExp(".", "u");
return true;
} catch (ex) {
return false;
}
}

修饰符y

ES6新增了一个正则表达式扩展:修饰符y,主要是影响正则表达式搜索过程中的sticky属性;当在字符串开始匹配时,会通知搜索从正则表达式的lastIndex属性开始进行,如果在指定位置没能匹配成功,则停止继续匹配;看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let text = "hello1 hello2 hello3",
pattern = /hello\d\s?/,
result = pattern.exec(text),
globalPattern = /hello\d\s?/g,
globalResult = globalPattern.exec(text),
stickyPattern = /hello\d\s?/y,
stickyResult = stickyPattern.exec(text);
console.log(result[0]);// "hello1 "
console.log(globalResult[0]);// "hello1 "
console.log(stickyResult[0]);// "hello1 "
pattern.lastIndex = 1;
globalPattern.lastIndex = 1;
stickyPattern.lastIndex = 1;
result = pattern.exec(text);
globalResult = globalPattern.exec(text);
stickyResult = stickyPattern.exec(text);
console.log(result[0]); // "hello1 "
console.log(globalResult[0]);// "hello2 "
console.log(stickyResult[0]);// throws an error!

当指定lastIndex设置为1的时候,stickyPattern没有匹配成功,stickyResult返回null,console.log(stickyResult[0])就报错;

需要注意以下几点:

  1. y和g修饰符如果匹配失败,lastIndex会被重置为0;
  2. y是严格从lastIndex开始匹配,而g从lastIndex匹配失败可以继续往后匹配;
  3. 只有调用exec和test这些正则表达式对象的方法时才会涉及lastIndex属性;调用字符串的方法是不会有效果的;

检测是否支持y修饰符:

1
2
3
4
5
6
7
8
function hasRegExpY() {
try {
var pattern = new RegExp(".", "y");
return true;
} catch (ex) {
return false;
}
}

正则表达式复制

ES5可以通过给RegExp构造函数传递正则表达式作为参数来复制这个表达式, 但是如果第二个参数传递修饰符参数则会报错;

1
2
3
var re1 = /ab/i
var re2 = new RegExp(re1) //
var re3 = new RegExp(re1, 'g') //报错

ES6修改了这个行为,可以传递修饰符var re4 = new RegExp(re1, 'g');但是会把之前的修饰符替换掉,re4变成了/ab/g.

正则表达式flags属性

ES6在正则表达式中新增了flags属性,会输出正则的修饰符;如

1
2
3
let reg = /ab/igu;
console.log(reg.flags); // 'igu'
console.log(reg.source); // 'ab'

递归

发表于 2017-11-11 | 分类于 算法

// 留个坑

减而治之 通过不断蚕食不断削减问题有效规模的策略

为求解一个大规模的问题,可以将其划分为若干(通常两个)子问题,规模大体相当,分别求解子问题,由子问题的解,得到原问题的解。

MSE

发表于 2017-11-06 | 分类于 html5

什么是MSE

MSE是一个允许在支持html5-video的浏览器中发送字节流的w3c标准, 全称是Media Souce Extensions.

MSE来由

要先知道MSE的来由,先要知道没有MSE之前浏览器对video的支持情况。

Html5发布后浏览器支持video标签, 但是只是基本支持了播放一个完整单轨道,无法支持视频arraybuffer的分隔与组合;由于现在社会多媒体技术的发展,
人们对视频观看方式的需求日益剧增,如视频点播, 代表为国内的youku/souhu/iqiyi等视频网站,国外的youtube netflix hulu等;到现在的各种直播网站,
国内的斗鱼国外的twitch等;之前浏览器的video功能已经不能支持,所以MSE标准应运而生。

MSE特点

MSE首先提供了MediaSource对象,作为一个容器,内部包含了一系列视频信息,如要被播放的media资源的状态, 指向组成播放流的多个轨道的SouceBuffer对象,
除此之外,MSE还提供了对视频内容获取的多少与速度的把控,以及一些内存的管理;

但是,MSE也有一些不足之处,因为浏览器处理视频解码转码方面性能消耗很大,这些功能只能放在浏览器之外处理,所以浏览器只接受主流的一些媒体格式,如
H.264 video codec, AAC audio codec, and MP4 container format; 还有一点就是各浏览器对MSE的支持程度不太一致,实现也有一些差别,导致现在html5播放器
在浏览器使用上没有普及开来,需要各方的协作.

主要对象

1.MediaSouce

构造函数

MediaSource() // 构造并且返回一个新的MediaSource的空对象

属性

MediaSource.sourceBuffers / MediaSource.activeSourceBuffers / MediaSource.readyState /MediaSource.duration

方法

MediaSource.addSourceBuffer() / MediaSource.removeSourceBuffer() / MediaSource.endOfStream() / MediaSource.isTypeSupported()

2.SouceBuffer

构造

可以通过MediaSource.addSourceBuffer()来构造

属性

mode / updating / buffered / timestampOffset / audioTracks / videoTracks / textTracks / appendWindowStart / trackDefaults

方法

appendBuffer() / appendStream() / abort() / remove()

事件

onabort / onerror / onupdate / onupdateend / onupdatestart

3.URL Object Extensions

构造方法

1
2
3
4
var video = document.querySelector('#video')
var m = new MediaSource
var l = URL.createObjectURL(m)
video.src = l

4.HTMLMediaElement Extensions

HTMLMediaElement.seekable -> normalized TimeRanges object

HTMLMediaElement.buffered -> normalized TimeRanges objec

一个简单例子

下面是从MDN上摘录下来的一个栗子, 这个栗子可以在这里下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
var video = document.querySelector('video');
var assetURL = 'frag_bunny.mp4';
// Need to be specific for Blink regarding codecs
// ./mp4info frag_bunny.mp4 | grep Codec
var mimeCodec = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';
if ('MediaSource' in window && MediaSource.isTypeSupported(mimeCodec)) {
var mediaSource = new MediaSource();
//console.log(mediaSource.readyState); // closed
video.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen);
} else {
console.error('Unsupported MIME type or codec: ', mimeCodec);
}
function sourceOpen (_) {
//console.log(this.readyState); // open
var mediaSource = this;
var sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
fetchAB(assetURL, function (buf) {
sourceBuffer.addEventListener('updateend', function (_) {
mediaSource.endOfStream();
video.play();
//console.log(mediaSource.readyState); // ended
});
sourceBuffer.appendBuffer(buf);
});
};
function fetchAB (url, cb) {
console.log(url);
var xhr = new XMLHttpRequest;
xhr.open('get', url);
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
cb(xhr.response);
};
xhr.send();
};

相关库

  • dash.js

  • hls.js

1234
cauil

cauil

37 日志
12 分类
23 标签
GitHub Weibo DouBan
© 2018 cauil
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.3