{"action":"opened","issue":{"url":"https://api.github.com/repos/LouisWT/Blog/issues/10","repository_url":"https://api.github.com/repos/LouisWT/Blog","labels_url":"https://api.github.com/repos/LouisWT/Blog/issues/10/labels{/name}","comments_url":"https://api.github.com/repos/LouisWT/Blog/issues/10/comments","events_url":"https://api.github.com/repos/LouisWT/Blog/issues/10/events","html_url":"https://github.com/LouisWT/Blog/issues/10","id":370416396,"node_id":"MDU6SXNzdWUzNzA0MTYzOTY=","number":10,"title":"[JavaScript] ES6 之 Symbol","user":{"login":"LouisWT","id":22902565,"node_id":"MDQ6VXNlcjIyOTAyNTY1","avatar_url":"https://avatars3.githubusercontent.com/u/22902565?v=4","gravatar_id":"","url":"https://api.github.com/users/LouisWT","html_url":"https://github.com/LouisWT","followers_url":"https://api.github.com/users/LouisWT/followers","following_url":"https://api.github.com/users/LouisWT/following{/other_user}","gists_url":"https://api.github.com/users/LouisWT/gists{/gist_id}","starred_url":"https://api.github.com/users/LouisWT/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/LouisWT/subscriptions","organizations_url":"https://api.github.com/users/LouisWT/orgs","repos_url":"https://api.github.com/users/LouisWT/repos","events_url":"https://api.github.com/users/LouisWT/events{/privacy}","received_events_url":"https://api.github.com/users/LouisWT/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2018-10-16T02:40:54Z","updated_at":"2018-10-16T02:40:54Z","closed_at":null,"author_association":"OWNER","body":"### 前言\r\nSymbol 是 ES6 新引入的数据类型，是除了 Number String Boolean Object undefined null 以外的第七个数据类型。\r\n\r\n### 一. 用法\r\n\r\n#### 1.1 创建一个唯一标识符 \r\n\r\nSymbol 可以用来创建一个独一无二的值。\r\n\r\n```\r\nlet s1 = Symbol('foo')\r\nlet s2 = Symbol('foo');\r\n\r\ns1 === s2 // false\r\n```\r\n可见即使是相同字符串创建出来的 Symbol 值也是不同的。\r\n\r\n#### 1.2 作为对象的键值\r\n\r\n```\r\nconst s = Symbol('key')\r\nlet a = {\r\n    [s]: 1,\r\n}\r\n\r\na[s] // 1\r\n```\r\n\r\n不能用 . 运算符的写法，因为它不会计算属性名，跟的总是字符串\r\n\r\n#### 1.3 获取对象所有的 Symbol 属性\r\n\r\n使用 Symbol 的属性名，不会出现在 for ... in, for ... of 循环中，可以使用 `Object.getOwnPropertySymbols` 来获取一个对象的符号属性名。如果既想获取普通属性也想获取符号属性，可以用 `ReFlect.ownKeys()`\r\n\r\n```\r\nlet a = {\r\n    [Symbol()]: 1,\r\n    b: 2,\r\n}\r\n\r\nObject.getOwnPropertySymbols(a) // [Symbol()]\r\nReflect.ownKeys(a) // [\"b\", Symbol()] \r\n```\r\n\r\n#### 1.4 注册全局符号\r\n由于每个符号是唯一的，那么在使用时不免将符号变量传来传去的来使用。这时候就可以使用注册全局符号的方式来避免。\r\n\r\n```\r\nlet b = Symbol('key3')\r\nlet a = {\r\n    [Symbol.for('key1')]: 1,\r\n    [Symbol('key2')]: 2,\r\n    [b]: 3,\r\n}\r\n\r\na[Symbol.for('key1')] // 1\r\na[Symbol('key1')] // undefined\r\na[b] // 3\r\n```\r\n`Symbol.for(key)` 使用给定的key在全局Symbol中搜索现有的symbol，如果找到则返回该symbol。否则将使用给定的key在全局symbol注册表中创建一个新的symbol。\r\n\r\n`Symbol.keyFor(symbol)` 与 `Symbol.for(key)`的过程相反，它是根据一个 symbol 值在全局注册表中搜寻相应的键值\r\n\r\n### 二. 公开符号\r\n\r\nES6 预先定义了一些内置符号。这些符号主要是为了提供一些专门的元属性。\r\n\r\n#### 2.1 `Symbol.iterator`\r\nSymbol.iterator 表示任意对象的一个专门属性，JS会自动在这个属性上寻找一个方法，用这个方法构造一个迭代器来消耗这个对象的值。\r\n\r\n可以通过修改这个属性值来定制迭代器逻辑。\r\n\r\n```\r\nconst arr = [1,2,3,4,5,6,7];\r\n\r\nfor (let v of arr) {\r\n    console.log(v) // 1 2 3 4 5 6 7\r\n}\r\n\r\narr[Symbol.iterator] = function*() {\r\n    for (let idx = 1; idx < this.length; idx += 2) {\r\n        yield this[idx];\r\n    }\r\n}\r\n\r\nfor (let v of arr) {\r\n    console.log(v) // 2 4 6\r\n}\r\n```\r\n\r\n#### 2.2 `Symbol.toStringTag` 与 `Symbol.hasInstance`\r\n`Symbol.toStringTag` 影响的是对象的 toString() 的结果\r\n\r\n`Symbol.hasInstance` 影响的是 instaceof 的结果\r\n\r\n```\r\nfunction Foo(greeting) {\r\n    this.greeting = greeting;\r\n}\r\n\r\nFoo.prototype[Symbol.toStringTag] = \"Foo\";\r\n\r\nObject.defineProperty(Foo, Symbol.hasInstance, {\r\n    value: function(instance) {\r\n        return instance.greeting === 'hello';\r\n    }\r\n})\r\n\r\nlet a = new Foo('hello');\r\nlet b = new Foo('world');\r\n\r\nb[Symbol.toStringTag] = 'Bar'\r\n\r\na.toString() // [object Foo]\r\nString(b) // [object Bar]\r\n\r\na instanceof Foo // true\r\nb instanceof Foo // false\r\n```\r\n\r\nSymbol.hasInstance 属性默认 writable: false，所以要用 Object.defineProperty 方式来定义。\r\n\r\n#### 2.3 `Symbol.isConcatSpreadable`\r\n用于配置某对象作为 Array.prototype.concat 方法的参数时是否展开其数组元素。\r\n\r\n1. 对数组来说\r\n```\r\nlet a = [4,5,6];\r\nlet b = [1,2,3];\r\n\r\na.concat(b); // [4,5,6,1,2,3]\r\n\r\nb[Symbol.isConcatSpreadable] = false;\r\na.concat(b); // [4,5,6,[1,2,3]]\r\n```\r\n2. 类数组对象\r\n```\r\nlet a = [1,2,3];\r\nlet arraylike = {\r\n    [Symbol.isConcatSpreadable]: true,\r\n    length: 2,\r\n    0: 'hello',\r\n    '1': 'world'\r\n};\r\n\r\na.concat(arraylike) // [1, 2, 3, \"hello\", \"world\"]\r\n```\r\n将类数组对象的 Symbol.isConcatSpreadable 设置为 true，并指定 length 属性。就可以对这个类数组对象展开了。\r\n\r\n#### 2.4 `Symbol.unscopables`\r\n\r\n指用于指定对象值，其对象自身和继承的从关联对象的 with 环境绑定中排除的属性名称。\r\n\r\n不建议使用 with 语法，所以这个符号最好也少用\r\n\r\n#### 2.5 `Symbol.species`\r\n\r\n`Symbol.species`的值会被构造函数用来创建派生对象\r\n\r\n```\r\nclass MyArray extends Array {\r\n  // 覆盖 species 到父级的 Array 构造函数上\r\n  static get [Symbol.species]() { return Array; }\r\n}\r\nlet a = new MyArray(1,2,3);\r\n\r\nconsole.log(a instanceof MyArray); // true\r\nconsole.log(a instanceof Array); // true\r\n\r\nlet mapped = a.map(x => x * x);\r\n\r\nconsole.log(mapped instanceof MyArray); // false\r\nconsole.log(mapped instanceof Array); // true\r\n```\r\n\r\n```\r\nclass MyArray extends Array {}\r\nlet a = new MyArray(1,2,3);\r\n\r\nconsole.log(a instanceof MyArray); // true\r\nconsole.log(a instanceof Array); // true\r\n\r\nlet mapped = a.map(x => x * x);\r\n\r\nconsole.log(mapped instanceof MyArray); // true\r\nconsole.log(mapped instanceof Array); // true\r\n```\r\n在实际应用场景中，如果你是一个库作者，在一个库的内部使用了一些内部类，但是在导出方法时想返回常规类型来供其他人使用，那么就可以覆写 Symbol.species。\r\n\r\n[具体戳这里](https://stackoverflow.com/questions/38082965/symbol-species-example-from-mdn-not-making-sense)\r\n\r\n#### 2.6 `Symbol.toPrimitive`\r\n通过这个属性可以将对象转换为原始值\r\n\r\n```\r\nconst obj1 = {};\r\nconsole.log(+obj1);     // NaN\r\nconsole.log(`${obj1}`); // \"[object Object]\"\r\nconsole.log(obj1 + \"\"); // \"[object Object]\"\r\n\r\n// 拥有 Symbol.toPrimitive 属性的对象\r\nconst obj2 = {\r\n  [Symbol.toPrimitive](hint) {\r\n    if (hint == \"number\") {\r\n      return 10;\r\n    }\r\n    if (hint == \"string\") {\r\n      return \"hello\";\r\n    }\r\n    return true;\r\n  }\r\n};\r\nconsole.log(+obj2);     // 10      -- hint is \"number\"\r\nconsole.log(`${obj2}`); // \"hello\" -- hint is \"string\"\r\nconsole.log(obj2 + \"\"); // \"true\"  -- hint is \"default\"\r\n```\r\nhint 有三个值，`number/string/default`\r\n\r\n#### 2.7 正则表达式 Symbol\r\n\r\n- Symbol.match\r\n- Symbol.split\r\n- Symbol.replace\r\n- Symbol.search\r\n\r\n1. `Symbol.match`\r\n\r\n为 true 表明这个对象是正则表达式；为false表示这个对象当做字符串使用。\r\n\r\n```\r\n'/bar/'.startsWith(/bar/) // TypeError: First argument to String.prototype.startsWith must not be a regular expression\r\n\r\nconst re = /foo/;\r\nre[Symbol.match] = false;\r\n'/foo/'.startsWith(re); // true\r\n'/bar/'.startsWith(re); // false\r\n```\r\n\r\n2. `Symbol.split`\r\n指向一个正则表达式的索引出分割字符串的方法\r\n\r\n```\r\nconst exp = {\r\n    pattern: 'in',\r\n    [Symbol.split](str) {\r\n        return str.split(this.pattern);\r\n    }\r\n}\r\n\r\n'outinoutdayinday'.split(exp); // [\"out\", \"outday\", \"day\"]\r\n```\r\n\r\n这样感觉没啥意义，因为直接用 `'outinoutdayinday'.split('in');`也没啥区别\r\n\r\n3. `Symbol.replace`/`Symbol.search`\r\n\r\n一个替换匹配字符串的子串的方法.\r\n\r\n一个返回一个字符串中与正则表达式相匹配的索引的方法\r\n\r\n### 三. 总结\r\nSymbol 单独用感觉主要是用来消除魔法字符串的，也就是说项目中那些约定的字符串，但是在 Symbol.for(key) 中使用的 key 其实还是另外一种形式的魔法字符串，只能说这好像并不能完全避免魔法字符串的使用。\r\n\r\n公开符号主要是用于元编程的。\r\n\r\n### 参考\r\n- [ES6入门](http://es6.ruanyifeng.com/#docs/symbol)\r\n- [MDN文档](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol)"}}
{"actor":{"display_login":"LouisWT"}}