首页>前端教程>JavaScript教程

JavaScript(ES6)的常用特性!(下)

现在几乎都是使用ES6了,浏览器要兼容的时代随着移动端的统一归于平静,想起以前轰轰烈烈的浏览器大战,为了兼容性痛苦不堪的年代,这一切都过去了。况且随着脚手架等自动化构建工具的使用,直接写最新最酷的代码,再babel一下,放心大胆的用。

所以,我也建议直接用最新版本的语法,不管是语法糖,还是效率,用起来都更舒心。

1、Symbol

ES6为JavaScript引入了一个新的原生类型:Symbol,但是,和其他原生数据类型不一样,symbol没有字面量形式。作为构造函数来说它并不完整,因为它不支持语法:"new Symbol()"。

下面是创建Symbol的过程:

var sym = Symbol('something');
console.log(typeof sym); //symbol
console.dir(Symbol)

注意事项:

  • 不能也不应该对Symbol()使用new。它不是一个构造器,也不会创建一个对象。

  • 传给Symbol(...)的参数是可选的,如果传入了的话,应该是一个为这个symbol的用途给出用户友好描述的字符串。

  • typeof的输出是一个新的值"symbol",这是识别symbol的首选方法。

两个Symbol永远不会相等

console.log(Symbol('name') === Symbol('name')); // false

1.1 为对象添加私有成员

利用Symbol不重复的特性,可以为对象添加不重复的键名。

每个从 Symbol() 返回的 symbol 值都是唯一的。一个 symbol 值能作为对象属性的标识符;这是该数据类型的目的。

//使用 Symbol 为对象添加不重复的键

const obj = {}
obj[Symbol()] = '123'
obj[Symbol()] = '456'
console.log(obj)

//也可以在计算属性名中使用

const obj = {
  [Symbol()]: 123
}
console.log(obj)

利用两个Symbol不相等的特性,可以为对象创建私有成员,外部不能访问。

const axisName = Symbol('obj的别名');
const obj = {
    // 利用这种方式可以创建私有成员,外部不能访问
    [Symbol('obj的id')]: 1,
    [Symbol('obj的name')]: 'obj',
    [axisName]: 'myObj',
    url: 'http://www.xxx.com'

}
// 外部不能访问,因为没有两个Symbol是一样的。
console.log(obj[Symbol('obj的id')]);
console.log(obj[axisName]);
console.log(obj.url);

常规的方式不能获取Symbol属性名

// for...in遍历不出Symbol,仅包含了以字符串为键的属性
for(const key in obj){
    console.log(key)
}
// 也不能返回Symbol,仅包含了对象自身的、可枚举的、以字符串为键的属性
console.log(Object.keys(obj))
// 当使用 JSON.stringify() 时,以 symbol 值作为键的属性会被完全忽略:
console.log(JSON.stringify(obj))

ES6的Object专门提供了一个方法用于获取对象的Symbol属性。

//其包含的是以 Symbol 为键的属性
console.log(Object.getOwnPropertySymbols(obj)); //[Symbol(obj的id), Symbol(obj的name), Symbol(obj的别名)]

1.2 内置的Symbol属性

除了自己创建的 symbol,JavaScript 还内建了一些在 ECMAScript 5 之前没有暴露给开发者的 symbol,它们代表了内部语言行为。它们可以使用以下属性访问:

1、迭代 symbols Symbol.iterator

一个返回一个对象默认迭代器的方法。被 for...of 使用。

object默认没有这个迭代属性,所以不能使用for...of,但是可以自己定义

//通过数组可以观察这个迭代器
const arr = [1, 2, 3];
console.log(arr[Symbol.iterator]); // f()
const it = arr[Symbol.iterator](); //{next: f(){}}
console.log(it.next()); //{value: 1, done: false}
console.log(it.next())
console.log(it.next())
console.log(it.next()); //{value: undefined, done: true}
const myObj = {
    name: '诸葛',
    age: 18,
    address: '卧龙岗',
    [Symbol.iterator]: function () {
        const _this = this;
        let num = 0;
        const keys = Object.keys(_this);
        return {
            next: function () {
                return {
                    value: _this[keys[num++]],
                    done: num > keys.length
                }
            }
        }
    }
}
//现在可以使用for...of迭代obj了
for(const value of myObj){
    console.log(value);
}

2、Symbol.replace

一个替换匹配字符串的子串的方法。被 String.prototype.replace() 使用。

3、Symbol.split

一个在匹配正则表达式的索引处拆分一个字符串的方法.。被 String.prototype.split() 使用。

更多了解:

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol

2、Set数据结构

Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。

Set对象是值的集合,Set 中的元素只会出现一次,即 Set 中的元素是唯一的。

NaN 和 undefined 都可以被存储在 Set 中,NaN 被视为相同的值(NaN 被认为是相同的,尽管 NaN !== NaN)。

2.1 创建一个set数据结构的实例

//Set 构造函数能创建 Set 对象实例
const mySet = new Set(); // Set(0) {size: 0}

参数iterable 可选,如果传递一个可迭代对象,它的所有元素将不重复地被添加到新的 Set 中。如果不指定此参数或其值为 null,则新的 Set 为空,返回一个新的 Set 对象。  

const mySet1 = new Set([1,2,2,3,3,4,5]);
console.log(mySet1);//Set(5) {1, 2, 3, 4, 5}

2.2 实例的方法

2.2.1 添加add(value)

如果 Set 对象中没有具有相同值的元素,则 add() 方法将插入一个具有指定值的新元素到 Set 对象中。 并返回Set 对象本身,因此可以链式调用。

const mySet = new Set();
mySet.add(1).add(true).add('Tom').add(18)
console.log(mySet); //Set(4) {1, true, 'Tom', 18}

2.2.2 移除某个值delete(value)

delete() 方法从 Set 对象中删除指定的值(如果该值在 Set 中)。

成功删除返回 true,否则返回 false

console.log(mySet.delete('Tom')); // true
console.log(mySet.delete('daisy'));//false

从Set中删除对象。

因为对象是通过引用比较的,所以如果没有对原始对象的引用,就必须通过检查单个属性来删除它们。

const setObj = new Set();
setObj.add({ x: 5, y: 20 }).add({ x: 20, y: 30 });
// 删除任何x > 10 的对象 
setObj.forEach(point => {
    if (point.x > 10) {
        setObj.delete(point);
    }
})
console.log(setObj)

2.2.3 移除所有元素 clear()

clear() 方法移除 Set 对象中所有元素。 返回值为undefined.

mySet.clear()

2.2.4 has(value)

has() 方法返回一个布尔值来指示对应的值是否存在于 Set 对象中。

const setObj = new Set();
setObj.add({ x: 5, y: 20 }).add({ x: 20, y: 30 }).add('circle');
console.log(setObj.has({ x: 20, y: 30})); // false,不是一个对象的引用
console.log(setObj.has('circle')); //true

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Set

2.3 实例的属性

size 属性将会返回 Set 对象中(唯一的)元素的个数。

size 的值是一个整数,表示 Set 对象有多少条目。size 的 set 访问函数是 undefined;你不能改变这个属性。 也就是只能读不能写。

2.4 遍历

2.4.1 forEach()

forEach() 方法对 Set 对象中的每个值按插入顺序执行一次提供的函数。

 setObj.forEach(item => console.log(item))

2.4.2 for ... of

for(const item of setObj){
    console.log(item);
}

2.5 应用场景

2.5.1 和数组的转换

// 数组去重
const arr = [1,1,2,2,3,3,4,5];
//const newArr = Array.from(new Set(arr));
const newArr = [...new Set(arr)];
console.log(newArr); // [1, 2, 3, 4, 5]

2.5.2 和字符串相关

let str1 = 'javascript';
let str2 = 'JAvaScript';
let s1 = new Set(str1);
let s2 = new Set(str2);
//大小写敏感
console.log(s1); //Set(9) {'j', 'a', 'v', 's', 'c', …}
console.log(s2); //Set(10) {'J', 'A', 'v', 'a', 'S', …}

3、Map数据结构

Map 对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者基本类型)都可以作为一个键或一个值。

3.1 对键值的操作

// 创建一个map实例
const myMap = new Map();
// 这种方式赋值不能改变map的数据结构,所以不推荐
// myMap.name = 'mrszhao';
// 正确的方式,使用set方法
myMap.set('name', 'mrszhao');
myMap.set('age', 18);
myMap.set('city', '成都');
myMap.set('city', '重庆');
// 使用get方法获取键的值
console.log(myMap.get('city'));
// size属性获取键值对的数量
console.log(myMap.size);
// 删除键值对,返回布尔值
myMap.delete('age');
// 判断知否存在某个键,返回布尔值
console.log(myMap.has('name'))
console.log(myMap);

注意:键名具有唯一性,重复设置,后面的值会覆盖前面的值,而且,键值对是根据设置的顺序保存的。

3.2 键名可以是任意的数据类型

object对象的键名只能是string和symbol,而map数据的键名可以是任意数据类型,包括复杂数据类型。

//对象的键名会被转成字符串,对象转成字符串是[object Object]
const obj = {
    [Symbol()]: 'mrszhaoObj',
    name: '诸葛',
    1: 1,
    true: true,
    [{a: 1}]: 'a'
}
console.log(obj);
obj[{b: 1}] = 'b';
console.log(Object.keys(obj))
console.log(obj['[object Object]'])

map的优势在于可以使用对象作为键名。

const o = {b: 2};
myMap.set({a: 1}, 'a');
myMap.set(o, 'b');
myMap.set(function a(){} , 1);
myMap.set(true, 1);
//对象的key只能出现一次  要用对象来设键名的时候,一定要在外面设变量
// 因为{} === {} false
console.log(map.get({a: 1})); //undefined
console.log(map.get(o)); // 'b'

3.3 map的遍历

// 第一个参数是value,第二个参数是key
 myMap.forEach((value, key) => console.log(key, value));

因为拥有迭代器属性,可以使用for...of

// 遍历出来的item是一个包含key和value的数组
// 使用数组的解构方式
for(const [key, value] of myMap){
    console.log(key, value)
}
//只遍历键名。myMap.keys()返回一个有迭代器的对象,所以可以用for...of
for(const key of myMap.keys()){
    console.log(key)
}
// 只遍历值
for(const value of myMap.values()){
    console.log(value)
}

3.4 map和数组的转换

const arrMap = [['key1', 'value1'], ['key2', 'value2']];
//使用常规的 Map 构造函数可以将一个二维键值对数组转换成一个 Map 对象
const myMap1 = new Map(arrMap);
// const newArr = Array.from(myMap1);
//最简单的是使用...扩展运算符展开对象,再放入数组。
   const newArr = [...myMap];

更多Map和Object对象的差别,可以查看文档:

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Map

4、Object相关

4.1 Object.assign()

Object.assign(target, ...sources)

Object.assign() 方法将所有可枚举(Object.propertyIsEnumerable() 返回 true)的自有(Object.hasOwnProperty() 返回 true)属性从一个或多个源对象复制到目标对象,返回修改后的对象。

如果目标对象与源对象具有相同的 key,则目标对象中的属性将被源对象中的属性覆盖,后面的源对象的属性将类似地覆盖前面的源对象的属性。

const target = {
    a: 1,
    b: 2
}
const source1 = {
    a: 2,
    c: 3
}
const source2 = {
    a: 3,
    d: 4
}
const result = Object.assign(target, source1, source2);
console.log(result, result === target);

点击空白页面产生小球,点击小球删除自己。试试。看到空白页面使劲点。

    function Circle(option) {
      option = option || {};
      this.shape = {
        r: 30,
        bgColor: '#f60',
        x: 0,
        y: 0
      }
      Object.assign(this.shape, option);
    }
    Circle.prototype.removeCircle = function() {
      console.log('我被干掉了');
    }

    const oHTML = document.documentElement;
    oHTML.addEventListener('click', function (e) {
       var circle = new Circle({
        r: getRandom(20, 50),
        bgColor: `rgba(${getRandom(0, 255)},${getRandom(0, 255)},${getRandom(0, 255)}, ${Math.random()})`,
        x: e.x,
        y: e.y,
      })
      console.log(circle);
      const oDiv = document.createElement('div');
      oDiv.style.cssText = `width:${circle.shape.r * 2}px;height:${circle.shape.r * 2}px;background-color:${circle.shape.bgColor};border-radius:50%;position:absolute;left:${circle.shape.x - circle.shape.r}px;top:${circle.shape.y - circle.shape.r}px;transition:0.2s`;
      document.body.appendChild(oDiv);
      oDiv.addEventListener('click', function(e) {
        this.remove();
        circle.removeCircle();
        e.stopPropagation();
      })
    })
    function getRandom(min, max) {
      return Math.floor(Math.random() * (max - min + 1) + min);
    }

4.2 Object.is()

Object.is() 方法判断两个值是否为同一个值。

Object.is(value1, value2);

Object.is的判断规则和==和===都不同。

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

4.3 Object.create()

Object.create() 方法用于创建一个新对象,使用现有的对象来作为新创建对象的原型(prototype)。

比如下面这个案例,点击页面产生一个圆,点击圆本身,会弹出它的面积。试试

核心代码:

// 创建一个大类 圆,有半径,x坐标, y坐标三个属性
    function Circle(r = 20, x = 0, y = 0) {
      this.r = r;
      this.x = x;
      this.y = y;
    }
    // 有一个原型上的获取面积的方法
    Circle.prototype.getArea = function () {
      return Math.floor(Math.PI * (this.r ** 2));
    }
    // 创建一个子类,产生有背景颜色的圆
    function BgcolorCircle(bgcolor = '#000') {
      this.bgcolor = bgcolor;
      // 执行大类的构造函数,让子类也有这三个属性,并且有默认值
      Circle.call(this);
    }
    // 把大类的原型对象当作小类的原型对象的原型,小类的实例也可以访问大类原型上的方法
    BgcolorCircle.prototype = Object.create(Circle.prototype);
    // 如果不指定构造函数,会默认指向Circle上级对象
    BgcolorCircle.prototype.constructor = BgcolorCircle;
    
    const oHTML = document.documentElement;
    oHTML.addEventListener('click', function (e) {
      const cc = new BgcolorCircle();
      cc.r = getRandom(10, 100);
      cc.x = e.x;
      cc.y = e.y;
      cc.bgcolor = `rgba(${getRandom(70, 255)},${getRandom(70, 255)},${getRandom(70, 255)})`;
      const oDiv = document.createElement('div');
      oDiv.style.cssText = `width: ${cc.r * 2}px;height:${cc.r * 2}px;position:absolute;left:${cc.x - cc.r}px;top:${cc.y - cc.r}px;background-color: ${cc.bgcolor};border-radius:50%`;
      document.body.appendChild(oDiv);
      oDiv.addEventListener('click', function (e) {
        alert(`我的面积是:${cc.getArea()}`);
        e.stopPropagation();
      })
    })
    function getRandom(min, max) {
      return Math.floor(Math.random() * (max - min + 1) + min);
    }

5、class类

看看Class类这个语法糖有多甜。

5.1 声明类

// 类声明,没有提升,先声明再实例化
class Circle {
    //用于创建和初始化一个由class创建的对象
    constructor(r = 20, x = 0, y = 0) {
        this.r = r;
        this.x = x;
        this.y = y;
    }
    // getter 只读属性
    get area() {
        return this.getArea();
    }
    // 原型方法
    getArea() {
        return (Math.PI * (this.r ** 2)).toFixed(2);
    }
}

const c = new Circle(50);
console.log(c);
console.log(c.area);
console.log(c.getArea());

5.2 静态属性和方法

不能在类的实例上调用静态方法,而应该通过类本身调用。

// 类声明,没有提升,先声明再实例化
class Circle {
    //用于创建和初始化一个由class创建的对象
    constructor(r = 20, x = 0, y = 0) {
        this.r = r;
        this.x = x;
        this.y = y;
    }
    // getter 只读属性
    get area() {
        return this.getArea();
    }
    // 静态属性,只有类能访问,实例不能访问
    static proname = 'Circle';
    // 原型方法
    getArea() {
        return (Math.PI * (this.r ** 2)).toFixed(2);
    }
    // 静态方法
    static createDefaultCircle() {
        return new Circle();
    }
}
// 只能类访问静态属性和静态方法
console.log(Circle.proname);
console.log(Circle.createDefaultCircle());

5.3 extends扩展子类

extends 关键字用于类声明中,以创建一个类,该类是另一个类的子类。

class ChildClass extends ParentClass { ... }
// 使用extends扩展内置对象,产生一个内置对象的子类
class myDate extends Date {
    constructor(date = new Date()) {
        // super 关键字用于调用对象的父对象上的函数。
        // 调用超类构造函数并执行。相当于Circle.call(this);
        // 并把子类的参数传递给父类。
        super(date);
    }
    getFormattedDate() {
        const week = ['星期天', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
        let date = this.getDate();
        let month = this.getMonth() + 1;
        let year = this.getFullYear();
        date = date < 10 ? '0' + date : date;
        month = month < 10 ? '0' + month : month;
        return `${year}.${month}.${date} ${week[this.getDay()]}`;
    }
} 

const mydate = new myDate();
console.log(mydate.getFormattedDate());
const mydate1 = new myDate('2023-10-1')
console.log(mydate1.getFormattedDate());

扩展Circle的子类对象

class ColorCircle extends Circle {
    constructor(bgcolor = '#000') {
        // 如果子类中定义了构造函数,那么它必须先调用 super() 才能使用 this 。
        super();
        // console.log(super());//返回了子对象,后面才能使用this来指向子对象。
        this.bgcolor = bgcolor;
    }
}

const bgcircle = new ColorCircle('#f30');
console.log(bgcircle);
bgcircle.r = 30;
console.log(bgcircle.area);

class BorderCircle extends Circle {
    constructor(border = '1px solid #000') {
        super();
        this.border =border;
    }
}

const bordercircle = new BorderCircle();
console.log(bordercircle);
bordercircle.border = `5px solid ${bgcircle.bgcolor}`
bordercircle.r = 40;
console.log(bordercircle.area);

现在Class类的写法比起以前原型的复杂写法看起来要清爽很多。

还有一些高级的内容,比如Promise异步、代理等等,后面有时间再写了。

点赞


2
保存到:

相关文章

发表评论:

◎请发表你卖萌撒娇或一针见血的评论,严禁小广告。

Top