es6+疑难解析

2020-05-18  本文已影响0人  zxhnext

实际上 JavaScript 是 ECMAScript 的扩展语言
ECMAScript 只提供了最基本的语法


浏览器端 服务器端

es6发布的特性可归纳为以下4类:

  1. 解决原有语法上的一些问题或者不足(如:let、const)
  2. 对原有语法进行增强(如:解构赋值)
  3. 全新的对象、全新的方法、全新的功能(如:promise)
  4. 全新的数据类型和数据结构(如:symbol、set、get)

到目前为止,es6+中共有8种数据类型,分别是:
StringNumberBooleannullundefinedSymbolBigIntObject
下面我们分别来对es6中的新特性来做下介绍。

1. let、const

首先来看var与let的区别

  1. var会变量提升,let不会
  2. let会形成块级作用域,仅在块级作用域内有效
  3. let会形成暂时性死区,在作用域内,声明前不可使用
  4. 同一个作用域内,let不允许重复声明

const和let作用类似,但是const在let基础上多了只读属性(变量声明过后不允许再被修改),另外const不允许修改是指不允许修改内存地址,而不是指不允许修改常量中的属性。
来看下面代码,我们可以发现,i的输出每次都为foo,而不是for循环中i的值,

for (let i = 0; i < 3; i++) {
  let i = "foo";
  console.log(i); // foo
}

来对for循环做一下拆解,结合上面let的的功能,我们就明白了。

let i = 0;
if (i < 3) {
  let i = "foo";
  console.log(i);
}
i++;
if (i < 3) {
  let i = "foo";
  console.log(i);
}
i++;
if (i < 3) {
  let i = "foo";
  console.log(i);
}
i++;

我们可以看到,let形成了块级作用域,所以每次的i都为'foo',只有当我们没有在块作用域中声明i时,i的值才会取外部for循环中的i

总结:建议主用const,辅助let,不用var

2. 赋值解构

2.1 数组赋值解构

来看下面这个例子,当我们需要获取路径时,es5中我们需要这么做

var path = '/foo/bar/baz'
var tmp = path.split('/')
var rootDir = tmp[1]

而在es6中使用赋值解构,我们只需要按如下写法:

const [, rootDir] = path.split('/')
console.log(rootDir)

2.2 对象赋值解构

我们来对对象做赋值解构,并为之设置别名和默认值:

const obj = { name: "zxh", age: 18 };
const { name: objName = 'jack' } = obj;
console.log(objName);

应用:简化console.log()方法

const { log } = console
log(1)

3. 模版字符串

3.1 换行

在es5中,如果我们需要对字符串换行,那么我们需要在每行末尾使用\n,那么在es6中,就简单多了,来看下面例子:

const str = `
  this is first 
  this is second
`

3.2 转义

如果字符串中含有** ` **,那么我们需要使用\来转义

const str = `this is a \`string\``

3.3 带标签的模版字符串

const name = "tom";
const gender = true;
function myTagFunc(strings, name, gender) {
  console.log(strings); // ['hey,', 'is a', '.']
  console.log(name); // tom
  console.log(gender); // true
  return 1;
}
const result = myTagFunc`hey, ${name} is a ${gender}.`;
console.log(result); // 1

4. 字符串扩展

startsWithendsWithincludes

5. 函数扩展

5.1 函数参数默认值

如果函数某个参数有默认值,那么有默认值的参数必须写在尾部。如果非尾部的参数设置默认值,那么该参数的实参是没法省略的。

function f(x = 1, y) {
  return [x, y];
}

f() // [1, undefined]
f(2) // [2, undefined]
f(, 1) // 报错
f(undefined, 1) // [1, 1]

如果我们给函数传入undefined,将触发该参数等于默认值,null则没有这个效果。

function f(x, y = 5, z) {
  return [x, y, z];
}

f() // [undefined, 5, undefined]
f(1) // [1, 5, undefined]
f(1, ,2) // 报错
f(1, undefined, 2) // [1, 5, 2]

// -----------------------------------
function foo(x = 5, y = 6) {
  console.log(x, y);
}

foo(undefined, null)
// 5 null

5.2 剩余参数

剩余参数只能出现在尾部,并且只能使用一次

function getname(name, ...args) {}

5.3 函数length属性

指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。
length属性的含义是,该函数预期传入的参数个数。某个参数指定默认值以后,预期传入的参数个数就不包括这个参数了。同理,rest 参数也不会计入length属性

(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
(function(...args) {}).length // 0
(function (a = 0, b, c) {}).length // 0
(function (a, b = 1, c) {}).length // 1

5.4 箭头函数

我们通过筛选数组中的基数这个例子来看普通函数和箭头函数区别

// 普通函数
const arr = [1, 2, 3, 4, 5, 6, 7];
arr.filter(function (item) {
  return item % 2;
});

// 箭头函数
arr.filter(i => i % 2);

必包:

// 这也算一个必包,setTimeout是在sayHiAsync函数外执行的,但是拿到了sayHiAsync函数的this
const person = { 
    name: 'tom',
    sayHiAsync() {
        const _this = this
        setTimeout(function () {
            console.log(_this.name)
        }, 1000)
    }
}

6. 对象

6.1 计算()动态属性名

const obj = {
  [Math.random]: 123
}

6.2 Object.is()

由于==会做隐式转换,而===又无法判断NaN,因此我们可以使用Object.is()

console.log(0==false) // true
console.log(0===false) // false
console.log(+0===-0) // true
console.log(NaN===NaN) // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

7. proxy

7.1 Object.defineProperty与Proxy区别

  1. defineProperty只能监视属性的读写,而Proxy监听的更多对象操作。
    具体可参考下图表格:


    image.png
  2. 支持数组对象监视
    ES5通过重写数组方法来实现劫持,Proxy如下:
const list = []
const listProxy = new Proxy(list, {
    set(target, property, value) {
        console.log('set', property, value) // 0 100
        target[property] = value
        return true
    }
})
listProxy.push(100)
  1. 非侵入式监听,不需要对原对象做任何操作

8. Reflect

8.1 Reflect成员方法就是Proxy处理对象的默认实现

const obj = {
  foo: "123",
  bar: "456",
};

const proxy = new Proxy(obj, {
  // 如果我们没有定义get方法,那么Proxy会默认一个get方法如下,返回Reflect
  get(target, property) {
    console.log(" watch logic~");
    return Reflect.get(target, property);
  },
});
console.log(proxy.foo);

8.2 Reflect最大作用是提供了一套完整的对象调用方法

它的内部封装了一系列对对象的底层操作,便于我们更方便的操作对象

const obj = {
  name: "zce",
  age: 18,
};
// es5
console.log("name" in obj);
console.log(delete obj["age"]);
console.log(Object.keys(obj));
// Reflect
console.log(Reflect.has(obj, "name"));
console.log(Reflect.deleteProperty(obj, "age"));
console.log(Reflect.ownKeys(obj));

Reflect共有13个api,具体查阅文档

8. Promise

9. 类

9.1 静态方法

es5中通过给函数挂载方法来实现静态方法(函数也是一个对象,可以像对象一样直接挂载),在中es6,我们通过static关键词来定义静态方法
注意:静态方法的this不指向实例对象

class Person {
  constructor(name) {
    this.name = name;
  }
  say() {
    console.log(`hi, my name is ${this.name}`);
  }
  static create(name) {
    return new Person(name);
  }
}
const tom = Person.create("tom");
tom.say();

2. 继承

class Student extends Person {
  constructor(name, number) {
    super(name, number);
    this.number = number;
  }
  hello() {
    super.say();
    console.log(`my school number is ${this.number}`);
  }
}

const s = new Student("jack", " 100");
s.hello();

10. Set Map

10.1 Set

我们通过Array.from()或者展开运算符可以将Set转化为数组

const arr = [1, 2, 3, 4, 5]
const result = Array.from(new Set(arr))
// or
const result = [...new Set(arr)]

10.2 Map

与对象类似,是一个键值对集合,目的是为了解决对象只能用字符串作键值
在es5中,普通对象如果键不是字符串,那么键就等于输入键的toString()结果作为键,es6开始对象支持用字符串或Symbol来作为键值而在Map中,任意类型数据都可作为键

// 普通对象
const obj = {}
obj[true] = 'value'
obj[123] = 'value'
obj[{ a: 1 }] = 'value'
// 如果键不是字符串,那么键就等于输入键的toString()结果作为键
console.log(Object.keys(obj)) // ['true', '123', '[object Object]']

// Map 任意类型数据都可作为键
const m = new Map();
const tom = { name: "tom" };
m.set(tom, 90);
console.log(m);
console.log(m.get(tom));

11. Symbol

es6开始对象支持用字符串或Symbol来作为键值,symbol最主要的作用就是为对象添加独一无二的属性名

Symbol()===Symbol // false
// Symbol 描述符
Symbol('foo')
Symbol('bar')

11.1 利用Symbol来实现私有成员

// a.js ======================================
const name = Symbol();
const person = {
  [name]: "zce",
  say() {},
};

// b.js =======================================
// Symbol是唯一的,外面的Symbol与定义时的不一样
// person[Symbol()]
person.say();

11.2 如何获取Symbol

  1. 通过Symbol.for()传入标识符获取
const s1 = Symbol.for('foo') // 传入标识符
const s2 = Symbol.for('foo')
console.log(s1 === s2) // true
Symbol('foo') === Symbol('foo') // false

注意,传入的标识符应该是字符串,如果不是字符串,会默认转为字符串,所以以下代码是相等的

Symbol.for(true) === Symbol.for('true')

11.3 扩展方法

const obj0 = {};
console.log(obj0.toString()); // [object Object]

const obj1 = {
  [Symbol.toStringTag]: "Xobject",
};
console.log(obj1.toString()); // [object Xobject]

11.4 获取Symbol键值

普通的for inObject.keys()JSON.stringify()是获取不到Symbol的键值的,我们需要通过Object.getOwnPropertySymbols获取,所以Symbol特别适合设置对象私有成员

const obj = {
  [Symbol()]: "symbol value",
  foo: "normal value",
};

for (var key in obj) {
  console.log(key); // foo
}
console.log(Object.keys(obj)); //  'foo' ]
console.log(JSON.stringify(obj)); // {"foo":"normal value"}
console.log(Object.getOwnPropertySymbols(obj)); // [ Symbol() ]

12. for of

for of可以通过break跳出循环,在代码中建议使用for of。但是我们可以发现,for of不支持对象,因为对象没有实现Iterable接口,我们在Iterable会详细讲解。
实现Iterable接口就是for..of的前提,也就是说,只要该种数据结构实现了(可迭代接口)Iterable,那么就可以使用for of

const arr = [1, 2, 3, 4, 5];
for (const item of arr) {
  console.log(item); // 1,2,3,4,5
  if (item > 100) {
    break;
  }
}

const m = new Map();
m.set("foo", "123");
m.set("bar", " 345");
for (const [key, value] of m) {
  console.log(key, value);
}

13. Iterable(迭代器)

13.1 每一个可迭代数据原型上都有一个Symbol.iterator方法

const set = new Set(["foo", "bar", "baz"]);
const iterator = set[Symbol.iterator]();
console.log(iterator.next()); // { value: 'foo', done: false }
console.log(iterator.next()); // { value: 'bar', done: false }
console.log(iterator.next()); // { value: 'baz ', done: false }
console.log(iterator.next()); // { value: undefined, done: true }
console.log(iterator.next()); // { value: undefined, done: true }

13.2 给对象添加迭代器

给对象添加迭代器以后,就可以使用for of循环了

const obj = {
    store: ['foo', 'bar', 'baz'],
    [Symbol.iterator]: function () {
        let index = 0
        const _self = this
        return {
            next: function () {
                const result = {
                    value: _self.store[index],
                    done: index >= _self.store.length
                }
                index++
                return result
            }
        }
    }
}

13.3 迭代器应用

遍历结构

const todos = {
    life: ['吃皈', '睡觉', '打豆豆'],
    learn: ['语文', '数学', '外語'],
    work: ['喝茶'],
    // 普通模式,通过each模式
    each: function (callback) {
        const all = [].concat(this.life, this.learn, this.work)
        for (const item of all) {
            callback(item)
        }
    },
    // 迭代器模式
    [Symbol.iterator]: function () {
        const all = [...this.life, ...this.learn, ...this.work]
        let index = 0
        return {
            next: function () {
                return {
                    value: all[index],
                    done: index++ >= all.length
                }
            }
        }
    }
}

// 使用each
todos.each(function (item) {
    console.log(item)
})

// 使用迭代器
for(const item of todos) {
  console.log(item)
}

14. Generator(生成器)

解决异步嵌套

14.1 应用

  1. 使用Generator实现迭代器
// 以上一节例子为例
[Symbol.iterator]: function* () {
        const all = [...this.life, ...this.learn, ...this.work]
        // let index = 0
        // return {
        //     next: function () {
        //         return {
        //             value: all[index],
        //             done: index++ >= all.length
        //         }
        //     }
        // }
        for (const item of all) {
            yield item
        }
    }
  1. 发号器
function* createIdMaker() {
    let id = 1
    while (true) {
        yield id++
    }

}

const idMaker = createIdMaker()
console.log(idMaker.next().value)
console.log(idMaker.next().value)
console.log(idMaker.next().value)
console.log(idMaker.next().value)

15. es2016

15.1 includes

indexof不能查找数组中的NaN(掘金遍历数组的文章)

15.2 指数运算

计算2的10次方

// es5: 
Math.pow(2, 10)
// es7: 
2 ** 10

16. es2017

16.1 对象扩展

Object.values() // 对象值的数组
Objece.entries // 对象键值对数组

  1. 通过Objece.entries 可以使对象用for of 循环
const obj = {
    foo: 'foo',
    bar: 'bar'
}
for (const [item, index] of Object.entries(obj)) { 
    console.log(index, item)
}
  1. 将对象转为Map形式对象
new Map(Object.entries(obj))
  1. Object.getOwnPropertyDescriptors
    以下问题,当拷贝一个对象时,对象中的getter属性被当作了普通属性,所以修改p2中firstName的值,fullName没有变化,这时我们可以通过Object.getOwnPropertyDescriptors解决
const p1 = {
    firstName: 'Lei',
    lastName: 'Wang',
    get fullName() {
        return this.firstName + ' ' + this.lastName
    }
}
console.log(p1.fullName) // Lei Wang

const p2 = Object.assign({}, p1)
p2.firstName = 'Zhao'
console.log(p2.fullName) // Lei wang

const descriptors = Object.getOwnPropertyDescriptors(p1)
const p2 = Object.defineProperties({}, descriptors)
p2.firstName = 'Zhao'
console.log(p2.fullName) // zhao wang

16.2 padStart、padEnd

上一篇下一篇

猜你喜欢

热点阅读