资讯专栏INFORMATION COLUMN

通俗易懂理解ES6 - ES6的变量类型及Iterator

Keven / 2523人阅读

摘要:迭代器在原有的数据结构类型上新增了两种类型,我们在使用的时候还可以通过自由组合的形式使用这些结构类型达到自己想要的数据结构,这就需要一种统一的接口机制供我们调用处理不同的数据结构。

引言

万丈高楼平地起,欲练此功,必先打好基本功: )

在了解 ES6 新增的变量类型前,我们必须先知道 JavaScript 在ES6之前,有如下六种基本数据类型:Null、Undefined、Number、String、Boolean和Object。而 ES6 中,新增了第七种数据类型:Symbol。
上述七种数据类型作如下类型划分:

基本类型: Undefined、Null、Boolean、String、Number,这五种类型的变量都是直接把实际值存储在栈内存当中,操作或访问的时候都是直接对实际值进行的.

引用类型: Object。Object 类型的变量是把指向堆内存的地址值存储在栈内存当中的一类数据。(关于堆栈的知识将会在后面的文章中作介绍。)

有关基本类型和引用类型的说明,网上已经有很多文章有说明介绍,为免篇幅过长,这里就不再重复叙述了。

Symbol类型:

这里我们着重说一下 Symbol 类型:


Symbol 是一个函数,调用该函数,返回的唯一值就是 Symbol 类型值;

不允许在 Symbol 前使用 new,symbol 类型的值可通过直接调用 Symbol 函数创建
let symbol = Symbol();
symbol;         //Symbol()
typeof symbol;  //symbol
let symbol1 = new Symbol();     //Uncaught TypeError: Symbol is not a constructor
Symbol 函数调用时,可接受一个参数,该参数会通过 toString 方法变为字符串,作为 symbol 值的说明,传入的参数不可以为 symbol 类型的值
let testStr = "this is a string",
    testObj = {obj: "this is a object"},
    testArr = ["this","is","a","array"],
    testFn = () => {
        console.log("this is a function");
    },
    testSym = Symbol("this is a symbol"),
    symbolStr = Symbol(testStr),            //Symbol(this is a string)
    symbolObj = Symbol(testObj),            //Symbol([object Object])
    symnolArr = Symbol(testArr),            //Symbol([object Object])
    symbolFn = Symbol(testFn),              //Symbol(() => {console.log("this is a function");})
    symbolSym = Symbol(testSym);            //Uncaught TypeError: Cannot convert a Symbol value to a string
Symbol 函数调用后生成的值是唯一的
let symbol1 = Symbol("test"),
    symbol2 = Symbol("test");
symbol1 == symbol2;         //false
symbol1 === symbol2;        //false
Symbol 值不能与其他类型值进行运算、隐式转换,否则会报错;但能通过 toString 方法显式转为字符串。
let symbol = Symbol("this is symbol"),
    str = "this is string",
    num = 2,
    symStr = symbol.toString();
    
let newStr = symbol + str;          //Uncaught TypeError: Cannot convert a Symbol value to a string
let newNum = Symbol + num;          //Uncaught TypeError: Cannot convert a Symbol value to a number
symStr;                             // Symbol(this is symbol)
Symbol 值的唯一性,用于 Object 的属性中,可以确保不会出现同名属性
let symbol = Symbol("this is symbol"),
    symbol1 = Symbol("this is symbol");
let obj = {
    [symbol]: "this is a",
    [symbol1]: "this is b"
};
obj;            //{Symbol(this is symbol): "this is a", Symbol(this is symbol): "this is b"}

let str = "test",
    str1 = "test",
    obj = {};
obj[str] = "测试非symbol类型命名的属性";
obj;            //{test: "测试非symbol类型命名的属性"}
obj[str1] = "再次测试非symbol类型命名的属性";
obj;            //{test: "再次测试非symbol类型命名的属性"}
Symbol 命名的属性可通过 Object.getOwnPropertySymbols 获取
let symbol = Symbol("this is symbol"),
    symbol1 = Symbol("this is symbol");
let symbolObj = {
    [symbol]: "this is a",
    [symbol1]: "this is b",
}
Object.getOwnPropertySymbols(symbolObj);    //[Symbol(this is symbol), Symbol("this is symbol")]
Symbol 值命名的属性不会出现在 Object.keys、for...in...中,通过Object.getOwnPropertyNames()、JSON.stringify() 也无法得到返回值。但该属性是公开属性,不是私有属性。
let symbol = Symbol("this is symbol"),
    symbol1 = Symbol("this is symbol");
let obj = {
    [symbol]: "this is a",
    [symbol1]: "this is b",
    d: "test"
};
for(key in obj){
    console.log(key);               // d
}
Object.keys(obj);                   // ["d"]
Object.getOwnPropertyNames(obj);    // ["d"]
JSON.stringify(obj);                //{d:"test"}

//仍可访问到Symbol值定义的属性键和属性值
let symKeys = Object.getOwnPropertySymbols(obj);  
symKeys;                    //[Symbol(this is symbol), Symbol(this is symbol)]
symKeys[0];                 //Symbol(this is symbol)
obj[symKeys[0]];            //this is a
可通过 Symbol.for("xxx") 获得以"xxx"作为入参而生成的 Symbol 值,若不存在以"xxx"作为入参生成的 Symbol 值,则会自动创建一个以"xxx"为入参的 Symbol 值。
let symbol = Symbol("this is symbol"),
    symbol1 = Symbol.for("this is symbol"),
    symbol2 = Symbol.for("this is symbol");
symbol === symbol1      //false
symbol === symbol2      //false
symbol1 === symbol2     //true
12. 通过 Symbol.keyFor 方法可返回一个已“登记”的 Symbol 类型值的修饰词。

通过 Symbol 创建的值有如下两种情况:
被登记的与未被登记的。


什么是被登记的?在

let s1 = Symbol.for("foo");
console.log(Symbol.keyFor(s1)); // "foo"

var s2 = Symbol("foo");
console.log(Symbol.keyFor(s2) ); // undefined

这里还有一些 MDN 关于 Symbol 的属性介绍,因为感觉在日常开发中使用的概率比较低,因此也不赘述一些自己的理解了,有兴趣的朋友可以去看看https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol

关于 Symbol 的总结: 对于 Symbol 的使用,实用性最高的我觉得是 symbol 值用于属性命名的情况,在一些开发情况下,对一个 Object 对象进行遍历的时候,希望某一些属性不被 for in 或 Object.keys 遍历出来,避免做 if 或 switch 情况处理,这时候使用 Symbol 定义属性名是个不错的选择。

在原有 ES5 的属性命名和赋值过程中,多人协作开发可能会导致一些属性名重命名而导致值覆盖,对“半私有属性”属性使用 Symbol 命名属性名会是个很好的选择。


Iterator - 迭代器

ES6 在原有的数据结构类型( Array、Object )上新增了两种类型( Map、Set ),我们在使用的时候还可以通过自由组合的形式使用这些结构类型达到自己想要的数据结构,这就需要一种统一的接口机制供我们调用处理不同的数据结构 —— Iterator。

ES6中,只要被遍历“对象”存在 可迭代协议 , 即System.iterator 属性,该对象都是被认为是“可遍历的”。

在 ES6 中,有三类数据结构原生具备 Iterator 接口:__数组、某些类似数组的对象(如 字符串、类似数组形式的对象)、Set 和 Map 结构数据__。

迭代器协议 定义了一种标准的方式来产生一个有限或无限序列的值,每次遍历都会首先调用被遍历数据集合对象中的 [Symbol.iterator]() 方法,该方法返回一个 Symbol对象的iterator属性 ,该属性拥有执行迭代对象的 next 方法,并返回一个对象,如下是一段模拟 next 方法的代码

function Iterator(arr){
    let nextIndex = 0;
    return {
        next: function(){
            return nextIndex < arr.length ? {value: arr[nextIndex++], done: false} : {value: undefined, done: true};
        },
        [Symbol.iterator]: function() { return this }
    }
}
let example = Iterator(["a", "b"]);

example.next()      // {value: "a", done: false}
example.next()      // {value: "b", done: false}
example.next()      // {value: undefined, done: true}       //遍历结束

返回的对象中必须包含如下两个属性

{
    done,       //迭代是否已经执行完毕  迭代完毕时返回true,否则返回false,返回false时会继续执行迭代
    value       //当前成员的值     迭代完毕时返回undefined,否则返回当前成员的值
}
在某些情景下,JS会默认调用 Symbol.iterator (Iterator接口)

扩展运算符

扩展运算符(…)会默认调用 iterator 接口。

解构赋值

对数组和Set结构进行解构赋值时,会默认调用 Symbol.iterator 方法。

yield*

yield*后面跟的是一个可遍历的结构,它会调用该结构的 iterator 接口。

for...of

执行 for...of循环时,会调用 iterator 接口对数据进行处理。

Array.from()

Array.form() 时,会遍历数据,调用 iterator 接口返回相应数据

其它情况还有
Map()Set()WeakMap()WeakSet() (比如new Map([["a",1],["b",2]]))Promise.all()Promise.race()

顺带一提:

在ES6中,具有 System.iterator 属性的对象均可通过 for...of 进行遍历

let arr = ["1","2","3"];
arr.pro = "test";
for (let i in arr) {
  console.log(arr[i]);          //1  2  3  test
}
for(let i of arr) {
    console.log(arr[i]);        //1  2  3
}

for...of 的相比于 forEachfor...in ,其好处在于: forEach 循环无法通过 breakcontinuereturn 跳出循环,而 for...of 可以; for...in 循环设计的目的是用于遍历包含键值对的对象,对数组并不是那么友好,而 for...of 遍历输出的值会在输出数据后默认遍历结束。

关于 Iterator 的总结: Iterator作为一种统一的接口机制供我们调用处理不同的数据结构,让可以用相同的方式来遍历集合,而不用去考虑集合的内部实现,若数据的形式发生改变,只要数据内部还存在 System.iterator 属性,那遍历的代码就可以不用做修改直接使用。 同时,其服务于 for...of 循环, for...of 又有效地避免了以往对数据集仅能通过 forEachfor..in 遍历时遇到的一部分问题。

以上。

文章观点内容如有错误欢迎指出交流,相互进步

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/104180.html

相关文章

  • 通俗易懂理解ES6 - 变量声明命令其块级作用域

    摘要:变量声明结论存在变量提升,可重复多次声明同名变量。和的出现,很好地把中定义变量的给埋了,解决了定义同名变量导致变量间相互污染的问题,保证了同一块级作用域下,变量名的唯一性。中规定,函数本身的作用域在其所在的块级作用域当中。 引言 万丈高楼平地起,欲练此功,必先打好基本功: )。 ES6已经融入到了我们的日常开发当中,甚至一些ES7的特性也已经在普遍使用,但由于浏览器的支持问题,ES6的...

    Olivia 评论0 收藏0
  • ES6—扩展运算符和rest运算符(6)

    摘要:扩展运算符简介扩展运算符是三个点,可以将一个数组转为用逗号分隔的参数序列。在实际项目中灵活应用扩展运算符运算符,能写出更精简易读性高的代码。 1、扩展运算符简介 扩展运算符( spread )是三个点(...),可以将一个数组转为用逗号分隔的参数序列。 说的通俗易懂点,有点像化骨绵掌,把一个大元素给打散成一个个单独的小元素。 showImg(https://segmentfault.c...

    Amio 评论0 收藏0
  • es6 promise面试

    摘要:执行函数会返回一个遍历器对象,每一次函数里面的都相当一次遍历器对象的方法,并且可以通过方法传入自定义的来改变函数的行为。函数可以通过配合函数更轻松更优雅的实现异步编程和控制流管理。它和构造函数的不同点类的内部定义的所有方法,都是不可枚举的。 let const的命令 在ES6之前,声明变量只能用var,var方式声明变量其实是很不合理的,准确的说,是因为ES5里面没有块级作用域是很不合...

    timger 评论0 收藏0
  • ES6(ECMAScript 6)新特性

    摘要:目前的标准化工作正在进行中,预计会在年月份放出正式敲定的版本。反的方法可以接收一个参数并且返回值取决与它的构造函数。之后就可以用这个返回值做为对象的键了。 本文基于lukehoban/es6features ,同时参考了大量博客资料,具体见文末引用。 ES6(ECMAScript 6)是即将到来的新版本JavaScript语言的标准,代号harmony(和谐之意,显然没有跟上我国的步伐...

    impig33 评论0 收藏0
  • JavaScript 是如何工作:模块构建以对应打包工具

    摘要:挂机科了次使用这个结构,匿名函数就有了自己的执行环境或闭包,然后我们立即执行。注意,匿名函数的圆括号是必需的,因为以关键字开头的语句通常被认为是函数声明请记住,中不能使用未命名的函数声明。 这是专门探索 JavaScript 及其所构建的组件的系列文章的第 20 篇。 想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你! 如果你错过了前面的章节,可以在这里找到它们: ...

    hedzr 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<