ES6(二)
文章目录
Symbol的基本使用
基本概念
ES6引入了一种新的原始数据类型Symbol(因为不是对象,所以不能添加属性),表示独一无二的值。他是JS语言的第七种数据类型,是一种类似于字符串的数据类型。
ES5的对象属性名都是字符串,这容易造成属性名的冲突,所以我们新增Symbol类型,保证每个属性的名字独一无二,可以从根本上防止属性名的冲突
- Symbol 的值是唯一的,用来解决命明冲突的问题
- Symbol 值不能与其他数据类型进行运算,会报错。(但可以进行类型转化,利用Boolean()转化为布尔值,利用String()转化为字符串。
- Symbol 定义的对象不能使用for…in循环遍历,但是可以使用Reflect.ownKeys来获取对象的所有键名
//创建Symbol
let s = Symbol();//通过函数调用,返回一个Symbol类型的值
console.log(s, typeof s);//类型是“symbol”
let s2 = Symbol('Joseph');//括号里的字符串相当于对这个s2的描述
let s3 = Symbol('Joseph');
s2.toSring();//得到"Symbol(Joseph)";
let name = s2.description;//可以取出描述值,这个description是在Symbol.prototype上的
console.log(s2 === s3);
//得到false
作为属性名时候的Symbol
- 注意:不能用点运算符,只能用方括号
let s1 = Symbol();
//第一种写法
let a = {};
a[s1] = "Joseph"
//第二种
let a = {
[s1] : "Joseph"
}
//第三种
let a = {};
Object.defineProperty(a,s1,{value:'Hello'});
console.log(a[s1]);
//都可以得到"Joseph"
- 使用点运算符新增的属性,属性名的类型都是字符串类型,利用[]查找时,里面应该是"属性名"
const s1 = Symbol();
const a = {}
a.s1 = "Joseph";
//这里实质上新增了一个属性,他的属性名是字符串类型的,与代码第一行无关
console.log(a['s1']);//可以有结果
console.log(a[s1]);//结果未被定义
- 因此在对象内部定义的时候,Symbol的值(即属性名)必须放在方括号中
let s1 = Symbol();
let obj = {
[s1] : function (arg){}
}
obj[s1]();
//这里时是调用方法
属性名遍历中的Symbol
- Symbol作为属性名时,遍历对象for(var prop in 对象){}时候,不会检索到它,也不会被一些对象的方法Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回,即得到的里面没有Symbol类型的属性名
- 但是它可以利用Object.getOwnPropertySymbols()方法,获取指定对象中的所有Symbol属性名。该方法返回的是数组
const obj = {};
const s1 = Symbol('s1');
obj[s1] = 'joseph';
for(let i in obj){
console.log(i);
//这里得不到输出
}
Object.getOwnPropertySymbols(obj);//可以得到[Symbol(foo)];
Object.getOwnPropertyNames(obj);//得到空数组[]
Reflect.ownKeys()方法则可以返回所有类型的键名
let obj = {
[Symbol('s1')] : 1,
enum : 2
}
Reflect.ownKeys(obj);
//得到[ Symbol(s1),"enum"];
class初识
- 类似于JS自定义构造函数,可以定义并生成新对象(联系JS笔记三创建对象的方式)
- 函数声明和类声明的最大区别在于函数声明会出现声明提前
//声明一个类
class Rectangle {
constructor(height,width){
this.height = height;
this.width = width;
}
//get 方法即调用将会返回的值(只读)
get area(){
return this.calcArea();
}
calcArea{
return this.height * this.width
}
//static 为一个静态方法,new构造出来的对象无法使用该方法
//一般用法 Rectangle.heightlength(s1);
static heightlength(obj){
return obj.height;
}
}
//下面是使用
const s1 = new Rectangle(100,100);
console.log(s1.area);
Symbol.for()
- 利用for创造的话,传进去相同的描述值,赋出来的Symbol是能够做到一样的
var s1 = Symbol.for('Joseph');
var s2 = Symbol.for('Joseph');
s1 === s2 //得到true,一般我们利用这个来得到同一个Symbol值
//同样不能运算
- 区别:Symbol.for和Symbol都是生成新的Symbol。区别在于前者会被登记在全局环境中供搜索,后者不会。即Symbol()没有登记机制
Symbol.keyFor()
- 得到Symbol值的key名字(但是这里要注意只能返回有登记机制的Symbol,即用Symbol.for创建的)
var s1 = Symbol.for('Joseph');
var s2 = Symbol('koseph');
console.log(Symbol.keyFor(s1));
//得到'Joseph'
console.log(Symbol.keyFor(s2));
//得到undefined
往对象里添加Symbol类型属性
- 一般对象自身可能就有一些方法,我们不想因为新加的属性名将其覆盖,所以采用添加Symbol类型的属性
方式一:
let game = {
//up和down是对象本身自带的,我们不想将其覆盖
//单独设置一个对象存Symbol
let methods = {
up : Symbol(),
dowm : Symbol()
}
game[methods.up] = function(){
....
}
game[methods.down] = function(){
}
}
方式二:
let game = {
name : 'Mario',
[Symbol('jump')] : ()=>{
...
}
[Symbol('eat')] : function(){
..
}
}
Symbol内置的值(指向内部使用的方法)
Symbol.hasInstance(构造函数的)
- instanceof 的本质,比如 foo instance of Foo ,实质是Foo[Symbol.hasInstance] (foo)
class Person {
[Symbol.hasInstance](param){
console.log(param);
console.log('instanceof Person的实质就是调用Person里面的这个内置的Symbol值');
return true;
}
}
let obj = {}
console.log(obj instanceof Person);
//得到{} 代码第四行 和 true
Symbol.isConcatSpreadable(数组的)
- 一般我们调用数组的concat方法,连接数组里面的元素,就是我们会把传进去的数组进行扩展,再一个个push,但我们利用这个内置的值可以令他不扩展连接
let arr1 = ['a','b'];
let arr2 = ['c','d'];
//下面是concat默认情况
console.log(arr1.concat(arr2));
//得到['a','b','c','d'];
console.log(arr1[Symbol.isConcatSpreadable]);
//得到undefined
arr1[Symbol.isConcatSpreadable] = false;
console.log(arr1.concat(arr2));
//得到['a','b',['c','d']];
所以当Symbol.isConcatSpreadable是true和undefined时都是默认展开
还有一系列关于字符串的,参考阮一峰的ES6
迭代器
- 迭代器(iterator)是一种接口,他是对象中的
Symbol.iterator
这个属性,指向该对象的默认遍历器方法 ,为各种不同的数据结构提供统一的访问机制,任何数据结构只要部署了iterator接口,就可以完成遍历操作。 - ES6创造的一种新的遍历命令for…of,使用for…of循环时,会调用
Symbol.iterator
方法 - 原生具备iterator的数据结构
- Array
- Arguments
- Set
- Map
- String
- TypedArray
- NodeList
const arr1 = ['a','b','c','d'];
//获取这个迭代器的指针对象,因为返回的才是指针对象,所以要加括号执行这个方法
let iterator = arr1[Symbol.iterator]();
console.log(iterator.next());
//得到{valun:'a',done:false}
console.log(iterator.next());
//得到{valun:'b',done:false}
console.log(iterator.next());
//得到{valun:'c',done:false}
console.log(iterator.next());
//得到{valun:'d',done:false}
console.log(iterator.next());
//得到{valun:undefined,done:true}
//当done为true,会自动停止循环
迭代器工作原理
当我们想自定义遍历数据,要想到迭代器
- 创建一个指针对象,指向当前数据结构的起始位置
- 第一次调用对象的next方法,指针自动指向数据结构的第一个成员
- 接下来不断调用next方法,指针一直往后移动,直至指向最后一个成员
- 每次调用next方法返回一个包含value和done属性的对象
//声明一个对象,对象一般遍历用for...in
//现在我们想遍历对象中的某个特定数组,用for..of实现
//对象本身没有[Symbol.iterator],我们要自定义
var obj = {
name: "Joseph",
capacity: ["fly", "attack", "defend", "jump"],
[Symbol.iterator]() {
//创建索引变量
var index = 0;
//返回指针对象
return {
//指针对象中的next方法
next: () => {
//我们这里利用箭头函数,让this指向next声明时的作用域
//next方法同样要有返回结果,里面有value和dobe
if (index < this.capacity.length) {
const result = { value: this.capacity[index], done: false };
index++;
//下标自增
return result;
//返回结果
} else return { value: undefined, done: true };
},
};
},
};
for (let c of obj) {
console.log(c);
}
生成器
- 生成器函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同
//一个解决异步编程的特殊函数
//之前一般都是利用回调函数的方式实现异步
function * gen(){
console.log('Hello generator');
}
let iterator = gen();
console.log(iterator);
//没有得到第5行代码的内容
//得到的是一个迭代器对象,类似返回了一个指针对象,里面有next方法
iterator.next();
//这里能得到第五行代码的内容
- 函数代码的分隔符 yield(后面跟表达式或者自变量)
//三个分隔符产生四块代码,由next一块块执行
function * Sn(){
console.log(1);
yield 'The first';
console.log(2);
yield 'Second';
console.log(3);
yield 'Third';
console.log(4);
//这里是第四段结尾
}
let interator = Sn();
interator.next();//输出1
interator.next();//输出2
interator.next();//输出3
interator.next();//输出4
console.log(interator.next());
//这里next()返回的会是一个含valun和done的对象,前提是我们要先把上面13-16行代码给注掉
console.log(interator.next());
//2
//{value:"Second",done:false}
console.log(interator.next());
//3
//{value:"Third",done:false}
console.log(interator.next());
//4
//{valun:undefined,done:true}
//因为是一个指针对象,所以我们可以用for...of来遍历
//同样首先需要把上面有关next的语句先注释掉
for(let v of interator){
console.log(v);
}
//得到yield里面的值,一共得到三条语句
关于生成器的函数参数
function * Sn(arg){
console.log(arg);
let one = yield 'The first';
console.log(one);
yield 'Second';
yield 'Third';
}
let interator = Sn("AAA");
//获取迭代器对象,并传入实参
console.log(iterator.next());
//得到'AAA' 和 {value:'The first',done:false}
//next方法也可以传入实参,实参会赋值给yield语句的返回结果
//第二次调用next传入实参会成为第一次yield的返回结果
console.log(iterator.next('BBB'));
//自上面14行代码,得到'BBB' , {value:'Second',done:false}
生成器函数实例
//异步编译 --比如文件操作,网络操作(ajax,request), 数据库操作
//1s 后在控制台输出111, 2s后输出222, 3s后输出 333
//回调地狱
setTimeout(()=>{
console.log(111);
setTimeout(()=>{
console.log(222);
setTimeout(()=>{
console.log(333);
},3000)
},2000)
},1000)
//现在我们利用生成器函数,把三个函数的调用把在yield里面
function one(){
setTimeout(()=>{console.log(111)},1000);
iterator.next();
}
function two(){
setTimeout(()=>{console.log(222)},2000);
iterator.next();
}
function three(){
setTimeout(()=>{console.log(333)},3000);
iterator.next();
}
function * behave(){
yield one();
yield two();
yield three();
}
//获取这个生成器函数
let iterator = behave();
iterator.next();
//重点注意18,22,26行
- 模拟获取(先获取用户数据,再订单数据,商品数据)
function getUsers() {
setTimeout(() => {
let data = "用户数据";
interator.next(data);
}, 1000);
//调用next方法,并且将数据传入实参
}
function getOrders() {
setTimeout(() => {
let data = "订单";
interator.next(data);
}, 1000);
}
function getGoods() {
setTimeout(() => {
let data = "商品数据";
interator.next(data);
}, 1000);
}
function* query() {
let users = yield getUsers();
console.log(users);
let orders = yield getOrders();
console.log(orders);
let goods = yield getGoods();
console.log(goods);
}
let interator = query();
interator.next();
//这里结果是每间隔1s出现数据,因为interator.next();是在定时器里面的
文献参考:阮一峰的ES6,尚硅谷ES6教学