现在几乎都是使用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异步、代理等等,后面有时间再写了。
发表评论:
◎请发表你卖萌撒娇或一针见血的评论,严禁小广告。