class(ES6)
- ES6(ECMAScript2015)新标准使用class定义类
- 类的本质是构造函数,原型链的语法糖
定义类的方法:
- 类声明:
class Person {} - 类表达式:
var Animal = class {}
类的构造函数
- 每个类都有且仅有一个自己的构造函数:constructor
- 当通过new操作一个类的时候会调用这个类的构造函数
- 在内存中创建一个新对象(空对象)
- 这个对象内部的[[prototype]]属性会被赋值为该类的prototype属性
- 构造函数内部的this,会指向创建出来的新对象
- 执行构造函数的内部代码(函数体代码)
- 如果构造函数没有返回对象,则将创建出的新对象返回
类的实例方法
-
通过创建出来的对象进行访问
-
... constructor(name, age) { this.name = name; this.age = age; this._address = "广州市"; } // 实例方法 // 通过创建出来的对象进行访问 p1.eating() eating() { console.log(this.name + "在吃饭"); } ... var p1 = new Person("zzy", 22); p1.eating() // zzy在吃饭
-
类的访问器方法
-
通过创建出来的对象进行访问
-
... constructor(name, age) { this.name = name; this.age = age; this._address = "广州市"; } // 实例方法 // 通过创建出来的对象进行访问 p1.address get address() { console.log("拦截访问操作"); return this._address; } ... console.log(p1.address); // 广州市 p1.address = "北京市"; console.log(p1.address); // 北京市
-
类的静态方法(类方法)
-
通过类名进行访问
-
var names = ['aa', 'bb', 'cc'] ... constructor(name, age) { this.name = name; this.age = age; this._address = "广州市"; } // 类的静态方法(类方法) // 通过类名进行访问 Person.randomPerson() static randomPerson() { var nameIndex = Math.floor(Math.random() * names.length) var age = Math.ceil(Math.random() * 100) var name = names[nameIndex] return new Person(name, age) } ... for(var i = 0; i < 50; i++) { console.log(Person.randomPerson()); }
-
类的继承(extends)
- ES6中新增了extends关键字,可以方便我们实现继承
继承内置类
-
class MyArray extends Array { firstItem() { return this[0] } lastItem() { return this[this.length - 1] } } var arr = new MyArray(1,2,3) console.log(arr.firstItem()); // 1 console.log(arr.lastItem()); // 3
类的混入(mixin)
-
JS的类只支持单继承:也就是只能有一个父类
-
混入(mixin)可以在一个类中添加更多相似的功能
-
class Person {} function mixinRunning(BaseClass) { class NewClass extends BaseClass { running() { console.log("running"); } } return NewClass; } function mixinEater(BaseClass) { return class extends BaseClass { eating() { console.log("eating"); } }; } // JS中只能有一个父类: 单继承 class Student extends Person {} var NewStudent = mixinEater(mixinRunning(Student)); var ns = new NewStudent(); ns.running(); // running ns.eating(); // eating
react中class的使用技巧(高阶组件)
super关键字
-
*在子(派生)类的构造函数中使用this或者返回默认对象之前,必须先通过super调用父类的构造函数
-
super的使用位置
-
子类的构造函数、
-
// 调用父对象/父类的构造函数 super([arguments])
-
-
实例方法
-
// 调用父对象/父类上的方法 super.functionOnParent([arguments])
-
-
静态方法
-
静态方法不是放到原型上的,而是和原型属性同级
// 调用父对象/父类上的方法 super.functionOnParent([arguments])
-
-
class Person { constructor(name, age) { this.name = name; this.age = age; } running() { console.log(this.name + 'running~'); } personMethod() { console.log('逻辑1'); } static personStaticMethod() { console.log('personStaticMethod'); } } class Student extends Person { // 实现继承的话,必须在this或者return前调用super() constructor(name, age, sno) { super(name, age) this.sno = sno } studying() { console.log(this.name + 'studying~'); } // 子类对父类方法的重写,不使用父类中的方法 running() { console.log(this.name + '子类running~'); } //重写personMethod方法 personMethod() { // 复用父类中实例方法的处理逻辑 super.personMethod() console.log('逻辑2'); } // 重写静态方法 static personStaticMethod() { // 复用父类中静态方法的处理逻辑 super.personStaticMethod() console.log('子类的personStaticMethod'); } } var stu = new Student('aaa', 22, 3171242) console.log(stu); // Student { name: 'aaa', age: 22, sno: 3171242 } console.log(Object.getOwnPropertyDescriptors(stu.__proto__)); // 只有studying方法 console.log(Object.getOwnPropertyDescriptors(stu.__proto__.__proto__)); // 只有running方法 stu.running() // aaarunning~ stu.studying() // aaastudying~ stu.personMethod() // 逻辑1 逻辑2 Student.personStaticMethod() // personStaticMethod 子类的personStaticMethod
-
babel(ES6->ES5)
- 在线网站:babeljs.io
- 本地下载babel
ES6->ES5(class)
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
running() {
console.log(this.name + "running~");
}
}
// babel转换(IE10)
"use strict";
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
Object.defineProperty(Constructor, "prototype", { writable: false });
return Constructor;
}
// 立即执行函数,结果返回Person,相当于Person,为了防止与全局变量产生冲突
// /*#__PURE__*/纯函数 webpack中会通过tree-shaking进行压缩,如果这段代码没有被使用,就会被删掉
// 纯函数没有副作用
var Person = /*#__PURE__*/ (function () {
function Person(name, age) {
_classCallCheck(this, Person);
this.name = name;
this.age = age;
}
_createClass(Person, [
{
key: "running",
value: function running() {
console.log(this.name + "running~");
}
}
]);
return Person;
})();
ES6->ES5(extends)
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
running() {
console.log(this.name + "running~");
}
}
class Students extends Person {
constructor(name, age, sno) {
super(name, age);
this.sno = sno;
}
learning() {
console.log(this.name + "learning");
}
}
// babel转换(IE10)
("use strict");
function _typeof(obj) {
"@babel/helpers - typeof";
return (
(_typeof =
"function" == typeof Symbol && "symbol" == typeof Symbol.iterator
? function (obj) {
return typeof obj;
}
: function (obj) {
return obj &&
"function" == typeof Symbol &&
obj.constructor === Symbol &&
obj !== Symbol.prototype
? "symbol"
: typeof obj;
}),
_typeof(obj)
);
}
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function");
}
Object.defineProperty(subClass, "prototype", {
value: Object.create(superClass && superClass.prototype, {
constructor: { value: subClass, writable: true, configurable: true },
}),
writable: false,
});
// 静态方法的继承
// Student.__proto__ = Person
if (superClass) _setPrototypeOf(subClass, superClass);
}
function _setPrototypeOf(o, p) {
_setPrototypeOf =
Object.setPrototypeOf ||
function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
function _createSuper(Derived) {
var hasNativeReflectConstruct = _isNativeReflectConstruct();
return function _createSuperInternal() {
var Super = _getPrototypeOf(Derived),
result;
if (hasNativeReflectConstruct) {
var NewTarget = _getPrototypeOf(this).constructor;
result = Reflect.construct(Super, arguments, NewTarget);
} else {
result = Super.apply(this, arguments);
}
return _possibleConstructorReturn(this, result);
};
}
function _possibleConstructorReturn(self, call) {
if (call && (_typeof(call) === "object" || typeof call === "function")) {
return call;
} else if (call !== void 0) {
throw new TypeError(
"Derived constructors may only return object or undefined"
);
}
return _assertThisInitialized(self);
}
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError(
"this hasn't been initialised - super() hasn't been called"
);
}
return self;
}
function _isNativeReflectConstruct() {
if (typeof Reflect === "undefined" || !Reflect.construct) return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === "function") return true;
try {
Boolean.prototype.valueOf.call(
Reflect.construct(Boolean, [], function () {})
);
return true;
} catch (e) {
return false;
}
}
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf
? Object.getPrototypeOf
: function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
Object.defineProperty(Constructor, "prototype", { writable: false });
return Constructor;
}
var Person = /*#__PURE__*/ (function () {
function Person(name, age) {
_classCallCheck(this, Person);
this.name = name;
this.age = age;
}
_createClass(Person, [
{
key: "running",
value: function running() {
console.log(this.name + "running~");
},
},
]);
return Person;
})();
var Students = /*#__PURE__*/ (function (_Person) {
// 实现寄生式继承
_inherits(Students, _Person);
var _super = _createSuper(Students);
function Students(name, age, sno) {
var _this;
_classCallCheck(this, Students);
// 创建出来的对象
_this = _super.call(this, name, age);
_this.sno = sno;
return _this;
}
_createClass(Students, [
{
key: "learning",
value: function learning() {
console.log(this.name + "learning");
},
},
]);
return Students;
})(Person);
多态
维基百科定义多态
- 多态(polymorphism)指为不同数据类型的实体提供统一的接口,或使用一个单一的符号来表示多个不同的类型
总结多态的定义
-
不同的数据类型进行同一个操作,表现出不同的行为,就是多态的体现
-
function calcArea(foo) { console.log(foo.getArea()); } var obj1 = { name: 'zzy', getArea: function() { return 100 } } class Person { getArea() { return 100 } } var p = new Person() calcArea(obj1) calcArea(p) // 也是多态的体现 function sum(n, m) { return m + n } sum(20, 30) sum('a', 'b')
传统的面向对象的多态
-
必须有继承(继承是多态的前提)
-
必须有重写(子类重写父类的方法)
-
必须有父类引用指向子类对象
-
class Shape { getArea() {} } class Rectangle extends Shape { getArea() { return 100; } } class Circle extends Shape { getArea() { return 200; } } var r = new Rectangle() var c = new Circle() // 多态:当不同的数据类型执行同一个操作时,如果表现出来的行为(形态)不一样,那么就是堕胎的体现 function calcArae(shape: Shape) { console.log(shape.getArea()); } calcArae(r) calcArae(c)
字面量的增强(ES6)
-
ES6中对对象字面量进行了增强,增强对象字面量(Enhanced object literals)
-
属性简写:Property Shorthand
-
方法简写:Method Shorthand
-
计算属性名:Computed Property Names
-
var name = 'zzy' var age = 22 var obj = { // 1.property shorthand(属性简写) // 当key与value相同时,可只写一个 // name: name, // age: age, name, age, // 2.method shorthand(方法简写) // fn: function() {} => fn() {} // foo: function () { // console.log(this); // } foo() { console.log(this); }, // 与箭头函数不一样 bar: () => { console.log(this); }, // 3.computed property names(计算属性名) [name + 123]: 'aaa' } console.log(obj); // { name: 'zzy', age: 22, foo: [Function: foo] } obj.foo() // { name: 'zzy', age: 22, foo: [Function: foo] } obj.bar() // {} // 在ES6中可以直接在对象内计算属性名 console.log(obj); // 会多出一个 zzy123: 'aaa' // 在ES5中这样写 obj[name + 231] = 'bb' console.log(obj); // 会多出一个 zzy231: 'bb'
-
解构(ES6)
- ES6中新增了一个从数组或对象中获取数据的方法,称之为解构(Destructuring)
数组解构
-
var names = ['aa', 'bb', 'cc'] // ES5这样写 var item1 = names[0] var item2 = names[1] var item3 = names[2] // 1.ES6对数组的解构 var [i1, i2, i3] = names console.log(i1, i2, i3); // aa bb cc // 2.解构后面的元素 var [, b, c] = names var [, , C] = names console.log(b, c); // bb cc console.log(C); // cc // 3.解构出一个元素,后面的元素放到一个新数组中 var [ia, ...newArr] = names console.log(ia); // aa console.log(newArr); // [ 'bb', 'cc' ] // 4.解构的默认值 var [ita, itb, itc, itd = 'dd'] = names console.log(itd); // dd
对象解构
-
var obj = { name: "zzy", age: 22, height: 1.75, }; // 1.对象的解构:{} var { name, age, height } = obj; console.log(name, age, height); // zzy 22 1.75 // 2.根据key去获取value,所以没有顺序限制 var { age, height, name } = obj; console.log(age, height, name); // 22 1.75 zzy // 3.修改key名 var {name: newName} = obj console.log(newName); // zzy // 4.默认值 var {address = '广州市'} = obj console.log(address); // 广州市 // 5.默认值并给新的名字 var {gender: sex = '男'} = obj console.log(sex); // 男
应用场景
-
获取到变量时,自动对其解构
-
对函数的参数解构
// 函数参数中解构 function foo(info) { console.log(info.name, info.age); } foo(obj) // zzy 22 function bar({name, age}) { console.log(name, age); } bar(obj) // zzy 22
let/const(ES6)
注意事项
- 被const定义后的变量不能修改,
- 但是如果传递的是一个引用类型(内存地址),可以通过引用找到对应的对象,去修改对象内部的属性
- 通过let/const定义的变量名是不可以重复定义的
作用域提升
-
var声明的变量会进行作用域提升
-
let/const声明的变量不会进行作用域提升
-
let/const声明的变量会被创建在包含他们的词法环境被实例化时,但是是不可以访问它们的,直到词法绑定被求值
-
// var声明的变量作用域会提升 console.log(foo); // undefined var foo = 'foo' // let/const声明的变量作用域不会提升 // console.log(foo1); // 会报错 // console.log(foo2); // 会报错 let foo1 = 'foo1' const foo2 = 'foo2'
作用域提升定义
- 在声明变量的作用域中,如果这个变量可以在声明之前被访问,那么我们就可以称之为作用域提升
- let/const虽然在执行上下文创建阶段被创建出来,但是不能被访问,所以不能称之为作用域提升
window对象添加属性
-
在全局通过var声明一个变量,会在window上添加一个属性
-
在全局通过let/const声明一个变量,不会在window上添加一个属性
变量被保存到VariableMap
- 用let/const声明的变量和环境记录是被添加到变量环境(VE)中的
- v8中通过VariableMap的一个hashmap来实现存储
- window对象是早期的GO对象,在最新的实现中是浏览器添加的全局对象,并且一直保持了window和var之间值的相等性
块级作用域
ES6之前的块级作用域
-
JS在ES6之前只会形成两个作用域
- 全局作用域
- 函数作用域
-
ES5中放到一个代码中定义的变量,外面是可以访问的
-
if/switch/for代码块内部也是块级作用域
-
// if代码块是块级作用域 if (true) { var foo = "foo"; let bar = "bar"; } console.log(foo); // foo // console.log(bar); // 报错 // switch代码块是块级作用域 var color = "red"; switch (color) { case "red": var foo2 = "foo2"; let bar2 = "bar2"; } console.log(foo2); // 2 // console.log(bar2); // 报错i // for代码块是块级作用域 for (var i = 0; i < 2; i++) {} console.log(i); // 2 for (let j = 0; i < 2; i++) {} // console.log(j); // 报错
-
ES6中的块级作用域
- 对于let/const/function/class声明的类型是有效的,
- 对var声明的类型无效
- 函数拥有块级作用域,但是外面依然可以访问
- 不同浏览器有不同实现(大部分浏览器为了兼容以前的代码,引擎会对函数的声明进行特殊的处理,允许像var那样进行提升,让function没有块级作用域)
块级作用域的应用
-
<button>按钮1</button> <button>按钮2</button> <button>按钮3</button> <button>按钮4</button> <script> const btns = document.getElementsByTagName("button"); // ES6之前 // for (var i = 0; i < btns.length; i++) { // btns[i].onclick = function () { // console.log(`第${i}个按钮被点击`); // 第4个按钮被点击 全局作用域中的i是最终的i // }; // } // ES6之前通过立即执行函数解决问题,函数会形成作用域 // for (var i = 0; i < btns.length; i++) { // (function(i) { // btns[i].onclick = function () { // console.log(`第${i}个按钮被点击`); // 第0-3个按钮被点击 这里的i是每次遍历的i // }; // })(i); // } // ES6之后,通过let,let会形成快级作用域 for (let i = 0; i < btns.length; i++) { btns[i].onclick = function () { console.log(`第${i}个按钮被点击`); // 第0-3个按钮被点击 这里的i是每次遍历的i }; } </script>
for...of(ES6)
-
遍历可迭代数组 / 对象
-
const names = ["a", "b", "c"]; // for...of:ES6新增的遍历数组/对象 for(var item of names) { console.log(item); // a b c } console.log(item); // c 可以访问var定义的变量 for(let item of names) { console.log(item); // a b c } for(const item of names) { console.log(item); // a b c }
-
暂时性死区(ES6)
-
在代码中,使用let/const声明的变量,在声明之前,变量不可访问,称为暂时性死区(temporal dead zone,TED)
-
var foo = "a"; if(true) { console.log(foo); // 由于let声明的变量不会作用于提升,会出现暂时性死区 let foo = 'b' } function bar() { console.log(foo); // 由于let声明的变量不会作用于提升,会出现暂时性死区 let foo = 'c' } bar()
var,let,const的选择
- var的使用
- 作用域提升,window全局对象,没有块级作用域等都是历史遗留问题
- 是JS设计之初的一种语言缺陷
- 开发中不推荐使用var来定义变量
- let/const的使用
- 优先推荐const,可以保证数据的安全性,不会被随意修改
- 只有当我们明确知道一个变量后续会需要被重新赋值时,再使用let
模板字符串(ES6)
基本使用
-
ES6之前,将字符串和动态变量拼接起来,非常麻烦
-
ES6允许使用模板字符串来嵌入JS的变量或表达式来进行拼接
- 使用单反引号(`)来编写字符串,称之为模板字符串
- 在模板字符串中,通过**${expression}来嵌入动态的内容**
-
const name = 'zzy' const age = 22 // 拼接字符串和其他标识符 // ES6之前 (代码丑陋,可读性差) console.log("my name is " + name); // my name is zzy // ES6模板字符串(`${}`) console.log(`my name is ${name}`); // my name is zzy //模板字符串可以进行计算 console.log(`my age is ${age * 2}`); // my age is 44 // 模板字符串进行函数调用 function doubleAge() { return age * 2 } console.log(`my age is ${doubleAge()}`); // my age is 44
标签模板字符串
-
Tagged Template Literals
-
第一个参数依然是模板字符串中整个字符串,只是被${}分割成多块,放到了一个数组中
-
第二个参数是模板字符串中第一个${}
-
...
-
// - 第一个参数依然是模板字符串中整个字符串,只是被${}分割成多块,放到了一个数组中 // - 第二个参数是模板字符串中第一个${} function foo(m, n, x) { console.log(m, n, x, '---'); // 20 30 40 --- } // 普通调用 foo(20, 30, 40); // 标签模板字符串方式调用 const name = 'zzy' const age = 22 foo`Hello${name}Wo${age}rld` // [ 'Hello', 'Wo', 'rld' ] zzy 22 ---
React的styled-components库
函数的默认参数(ES6)
ES6之前
-
缺点
- 写起来麻烦,可读性差
- 这种写法有bug, 如果传入0/“ ”/null/false会选择默认值
-
function foo(m, n, q) { m = m || "a"; n = n || "b"; q = q || "c"; console.log(m, n, q); } foo(0, null, ""); // a b c
ES6
-
可以给函数参数提供默认值
-
function bar(m = "a", n = "b") { console.log(m, n); } bar(0, null); // 0, null -
函数的参数是对象
-
可以参数的属性设置默认值并解构
-
function printInfo({ name, age } = { name: "zzy", age: 22 }) { console.log(name, age); } printInfo(); // zzy 22 printInfo({ name: "aaa", age: 12 }); // aaa 12 -
可以给解构后的属性设置默认值
-
function printInfo2({ name, age } = { name: "zzy", age: 22 }) { console.log(name, age); } printInfo2(); // zzy 22 printInfo2({ name: "bbb", age: 21 }); // bbb 21
-
-
有默认值的形参最好放到最后
-
function baz(x, y, z = 30) { console.log(x, y, z); } baz(10, 20); // 10 20 30 -
默认值会改变函数的length的个数,默认值以及后面的参数都不计算在length之内
-
function boo(x, y, z = 30, m, n) { console.log(x, y, z, m, n); } console.log(boo.length); // 2
函数的剩余参数(ES6)
-
rest parameter
-
如果最后一个参数是**...为前缀的,那么它会将剩余的参数放到该参数中,并且作为一个数组**
-
剩余参数必须放到最后一个位置
-
function foo(m, n, ...args) { console.log(m, n, args); } foo(20, 30, 40, 50); // 20 30 [ 40, 50 ]
-
-
剩余参数与arguments的区别
- 剩余参数只包含那些没有对应的实参,而arguments对象包含了传给函数的所有实参
- arguments对象不是一个真正的数组,而rest参数是一个真正的数组,可以进行数组的所有操作
- arguments是早期的ECMAScript中为了方便去获取所有的参数提供的一个数据结构;rest参数是ES6中提供并且希望以此来替代arguments的
函数的箭头参数(ES6)
- 箭头函数是没有显式原型的,所以不能作为构造函数,使用new来创建对象
展开语法(ES6)
-
Spread syntax
- 可以在函数调用/数组构造时,将数组表达式或者string在语法层面展开
- 可以在构造字面量对象时,将对象表达式按key-value的方式展开
-
展开语法的场景
-
函数调用时
const names = ['aa', 'bb', 'cc'] const name = 'zzy' function foo(x, y, z) { console.log(x, y ,z); } // apply一般在this绑定时使用 // foo.apply(null, names) // aa bb cc foo(...names) // aa bb cc foo(...name) // z z y -
数组构造时
const names = ['aa', 'bb', 'cc'] const name = 'zzy' const newNames = [...names, ...name] console.log(newNames); // [ 'aa', 'bb', 'cc', 'z', 'z', 'y' ] -
构建对象字面量时(ES2018/ES9)
- 数组会按照索引值: 元素的格式添加到对象中
const names = ['aa', 'bb', 'cc'] const info = {name: 'zzy', age: 22} // 数组会按照索引值和元素的格式添加到对象中 const obj = {...info, address: '广州市', ...names} console.log(obj); // {'0': 'aa', '1': 'bb', '2': 'cc', name: 'zzy', age: 22, address: '广州市' }
-
-
展开运算符本质是进行浅拷贝
-
const info = { name: 'zzy', friends: { name: 'aaa' } } const obj = {...info, name: 'lll'} // zzy被覆盖 console.log(obj); // { name: 'lll', friends: { name: 'aaa' } } obj.friends.name = 'bbb' // aaa被覆盖 console.log(info.friends.name); // bbb
-
数值的表示(ES6)
-
const num1 = 100 // 10进制 const num2 = 0b100 // 2进制 binary const num3 = 0o100 // 8进制 octonary const num4 = 0x100 // 16进制 hexodecimal const num = 1_000_000_000 // 大的数值的连接符(ES2021/ES12) console.log(num1, num2, num3, num4, num); // 100 4 64 256 1000000000
Symbol(ES6)
-
新增的基本数据类型,符号
-
ES6之前,对象的属性名都是字符串形式,容易造成属性名的冲突
-
添加新的属性和值容易覆盖掉之前内部存在的
-
Symbol可以生成一个独一无二的值
- 通过Symbol函数来生成,生成后可以作为属性名
- 在ES6中,对象的属性名可以使用字符串,也可以使用Symbol值
-
Symbol即使多次创建值,他们也是不同的:Symbol函数执行后每次创建出来的值都是独一无二的
-
// 1.ES6之前,对象的属性名(key) var obj = { name: "zzy", friends: { name: "aaa", }, }; console.log(Object.keys(obj)); // [ 'name', 'friends' ] // 2.ES6中Symbol const s1 = Symbol(); const s2 = Symbol(); console.log(s1 === s2); // false // ES2019/ES10中,Symbol还有一个描述(description) const s3 = Symbol("aa"); console.log(s3.description); // aa // 3.Symbol值作为key // 3.1 定义对象字面量时 const obj1 = { [s1]: "abc", [s2]: "cba", }; // 3.2 新增属性时 obj1[s3] = "nba"; // 3.3 Object.defineProperty方式 const s4 = Symbol(); Object.defineProperty(obj1, s4, { enumerable: true, configurable: true, writable: true, value: "mba", }); // 不能通过.语法获取属性值 console.log(obj1[s1], obj1[s2], obj1[s3], obj1[s4]); // abc cba nba mba // 4.使用Symbol作为key的属性名,在遍历/Object.keys等中获取不到这些Symbol值,需要通过Object.getOwnPropertySymbols()来获取所哟Symbol的key console.log(Object.keys(obj1)); // [] console.log(Object.getOwnPropertyNames(obj1)); // [] console.log(Object.getOwnPropertySymbols(obj1)); // [ Symbol(), Symbol(), Symbol(aa), Symbol() ] const sKeys = Object.getOwnPropertySymbols(obj1) for(const sKey of sKeys) { console.log(obj1[sKey]); // abc cba nba mba } // 5.Symbol.for(key) / Symbol.keyFor(symbol) const sa = Symbol.for("aaa") const sb = Symbol.for("aaa") console.log(sa === sb); // true const key = Symbol.keyFor(sa) console.log(key); // aaa const sc = Symbol.for(key) console.log(sa === sc); // true
Set(ES6)
基本使用
-
ES6之前存储数据的结构:数组,对象
-
ES6新增两种数据结构:Set,Map,WeakSet,WeakMap
-
Set可以用来保存数据,类似于数组,但是元素不能重复
-
创建Set通过Set构造函数
const set = new Set() set.add(10) set.add(20) set.add(10) // 元素不能重复,不会被添加 set.add({}) set.add({}) // 两个对象的内存地址不同,都会被添加 const obj = {} set.add(obj) set.add(obj) // 引用内存地址相同,是同一个对象,不会被添加 console.log(set); // Set(5) { 10, 20, {}, {}, {} }
-
-
使用Set给数组去重
// 1.遍历方法 const arr = [1,2,3,3,4,1] const newArr = [] for(const item of arr) { // 检索的字符串值没有出现过,返回-1 if(newArr.indexOf(item) === -1) { newArr.push(item) } } console.log(newArr); // [ 1, 2, 3, 4 ] // 2.set方法 const arrSet = new Set(arr) console.log(arrSet); // Set(5) { 1, 2, 3, 4 } // Array.from()将set结构变为数组结构 const newArr1 = Array.from(arrSet) console.log(newArr1); // [ 1, 2, 3, 4 ] // ...展开运算符将set结构变为数组结构 const newArr2 = [...arrSet] console.log(newArr2); // [ 1, 2, 3, 4 ]
Set常见属性
- size:返回Set中的元素个数
Set常见方法
-
add(value):添加某个元素,返回Set本身
-
delete(value):删除和这个值相等的元素,返回布尔类型,成功移除返回true
-
has(value):判断set中是否存在某个元素,返回布尔类型
-
clear():清空set中所有元素,没有返回值
-
forEach(callback, [,thisArg]):遍历set
-
for...of:遍历set
const arr = [1, 2, 3, 3, 4, 1]; const arrSet = new Set(arr); console.log(arrSet); // Set(4) { 1, 2, 3, 4 } // 4.size属性 console.log(arrSet.size); // 4 // 5.常见方法 arrSet.add(100) console.log(arrSet); // Set(5) { 1, 2, 3, 4, 100 } arrSet.delete(4) console.log(arrSet); // Set(4) { 1, 2, 3, 100 } console.log(arrSet.has(2)); // true arrSet.clear() console.log(arrSet); // Set(0) {} // 6.遍历Set arrSet.forEach(i => { console.log(i); }) for(const i of arrSet) { console.log(i); }
WeakSet
基本使用
- 与Set类似的数据结构,内部元素不能重复
- 和Set区别
- WeakSet中只能存放对象类型,不能存放基本数据类型
- WeakSet对元素的引用是弱引用,如果没有其他引用对这个对象进行引用,那么GC可以对该对象进行回收
WeakSet常见方法
- add(value):添加某个元素,返回WeakSet本身
- delete(value):删除和这个值相等的元素,返回布尔类型,成功移除返回true
- has(value):判断WeakSet中是否存在某个元素,返回布尔类型
注意
- WeakSet不能遍历
- WeakSet只是对对象的弱引用,如果可以遍历获取其中元素,会影响GC回收
- 所以WeakSet中的对象不能获取
应用场景
- 如果用Set的话会对this(即对象)有强引用,销毁对象的时候(将对象置为null)该对象无法被回收,还需要delete才能销毁
const personSet = new WeakSet()
class Person {
constructor() {
personSet.add(this)
}
running() {
if(!personSet.has(this)) {
throw new Error('不能通过非构造函数创建出来的对象调用running方法')
}
console.log('running', this);
}
}
const p = new Person()
p.running() // running Person {}
p.running.call({name: 'zzy'}) // 报错
Map(ES6)
-
用于存储映射关系
-
JS中对象中不允许使用对象作为key
- 对象存储映射关系只能用字符串(ES6新增Symbol)作为属性(key)
- 不会将对象作为key,而是将对象转成的字符串作为key
-
Map允许对象使用对象或其他类型作为key
const map = new Map() map.set(obj1, 'aaa') map.set(obj2, 'bbb') map.set(1, 'ccc') console.log(map); // Map(3) {{ name: 'zzy' } => 'aaa', { name: 'lll' } => 'bbb', 1 => 'ccc' } const map2 = new Map([[obj1, 'aaa'], [obj2, 'bbb'], [2, 'ccc']]) console.log(map2); // Map(3) {{ name: 'zzy' } => 'aaa', { name: 'lll' } => 'bbb', 2 => 'ccc' }
Map常见属性
- size:返回Map中的元素个数
Map常见方法
-
set(key,value):添加某个元素,返回Map本身
-
get(key):获取某个元素,返回返回某个Map对象中的一个指定元素。
-
delete(key):删除和这个值相等的元素,返回布尔类型,成功移除返回true
-
has(key):判断Map中是否存在某个元素,返回布尔类型
-
clear():清空Map中所有元素,没有返回值
-
forEach(callback, [,thisArg]):遍历Map
-
for...of:遍历Map
const map2 = new Map([[obj1, 'aaa'], [obj2, 'bbb'], [2, 'ccc']]) // size console.log(map2.size); // 3 // set map2.set({height: 1.22}, 1.88) console.log(map2); // get(key) console.log(map2.get(2)); // ccc 获取key为2的value值 // has(key) console.log(map2.has(2)); // true 判断属性2是否存在 // delete(key) map2.delete(2) console.log(map2); // 移除了属性2 // clear map2.clear() // 清空map2的所有属性 console.log(map2); // Map(0) {} // 遍历 map2.forEach((value, key)=> { console.log(value, key); }) for(const value of map2) { console.log(value); // 打印出数组,键值,以逗号分割 console.log(value[0], value[1]); // 打印键值 } // 对数组解构 for(const [key, value] of map2) { console.log(key, value); // 打印键值 }
WeakMap
基本使用
- 与Map类似的数据结构,以键值对的形式存在
- 和Map区别
- WeakMap的key只能使用对象,不接受其他的类型作为key
- WeakMap的key对对象的引用是弱引用,如果没有其他引用对这个对象进行引用,那么GC可以对该对象进行回收
WeakMap常见方法
- get(key):获取WeakSet中的某个属性
- delete(obj):删除和这个值相等的属性,返回布尔类型,成功移除返回true
- has(obj):判断WeakSet中是否存在某个属性,返回布尔类型
注意
- 不支持size属性
- 不支持clear
- WeakMap不能遍历
- WeakMap只是对对象的弱引用,如果可以遍历获取其中元素,会影响GC**回收
- 所以WeakMap中的对象不能获取**
应用场景
-
Vue3的响应式原理
// Vue3响应式原理 const obj1 = { name: 'zzy', } function obj1Name1() { console.log('obj1Name1~'); } function obj1Name2() { console.log('obj1Name2~'); } const obj2 = { name: 'lll', } function obj2Name1() { console.log('obj2Name1~'); } function obj2Name2() { console.log('obj2Name2~'); } // 1.创建WeakMap const weakMap = new WeakMap() // 2.收集依赖结构 // obj1 const obj1Map = new Map() obj1Map.set('name', [obj1Name1,obj1Name2]) // WeakMap中只能使用对象作为key weakMap.set(obj1, obj1Map) // obj2 const obj2Map = new Map() obj1Map.set('name', [obj2Name1,obj2Name2]) // WeakMap中只能使用对象作为key weakMap.set(obj2, obj2Map) // 3.obj1.name发生了改变 // 通过Proxy/Object.defineProperty监听数据是否改变 obj1.name = 'aaa' const targetMap = weakMap.get(obj1) const fns = targetMap.get('name') fns.forEach(i => i()) // obj2Name1~ obj2Name2~