ES6-你真的懂symbol吗❓😸(下)

192 阅读5分钟

所见即所得,所见即所碍。

继续继续继续啦!😈

大概目录:

  • symbol基础
  • well-konwn symbol 暴露内部操作
  • 元编程

内容主要来自

well-konwn symbol 暴露内部操作😺

Symbol.toStringTag 属性

js有时会同时存在多个全局执行环境,比如在Web浏览器中,如果一个页面包含iframe标签🏷️,就会分别为页面和iframe内嵌页面生成两个全局执行环境。在大多数情况下,由于数据可以在不同环境间来回传递,不太需要担心;但是如果对象在不同对象间传递之后,你想确定它的类型呢?

首先明确一下概念,领域指的是javaScript的执行环境,每个领域都有自己的全局作用域,有自己的全局对象,在任何领域创建的数组,都是一个正规的数组。然而,如果把这个数组传递到另一个领域中,instanceof Array 语句的检测结果会返回false,此时 Array 已是另一个领域的构造函数,显然被检测的数组不是由这个构造函数创建的。

针对类型识别问题的解决方案

Object.prototype 能跨越 iframe 的边界来识别数组

function isArray(value) {
    return Object.prototype.toString.call(value) === "[object Array]";
}

console.log(isArray([])); //true

在ECMAScript6 中定义对象字符串标签🏷️

ECMAScript6重新定义了原生对象过去的状态,通过 Symbol.toStringTag这个Symbol改变了调用Object.prototype.toString()时返回的身份标识。这个Symbol所代表的属性在每一个对象中都存在,其定义了调用对象的Object.prototype.toString.call()方法返回的值。对于数组,调用那个函数返回的值通常是"Array",它正是存储在对象的Symbol.toStringTag属性中。

同样的,可以为你自己的对象定义Symbol.toStringTag的值:

function Person(name) {
    this.name = name;
}

Person.prototype[Symbol.toStringTag] = "Person";
let me = new Person("heihei");

console.log(me.toString());                                //"[object Person]"
console.log(Object.prototype.toString.call(me));   //"[object Person]"

除非另有说明,所以对象都会从 Object.prototype 挤成 Symbol.toStringTag这个属性,且默认的属性值为"Object"。

对于开发者定义的对象来说,不限制Symbol.toStringTag属性的值的范围。例如,语言本身不会阻止你使用Array作为Symbol.toStringTag属性的值

function Person(name){
    this.name = name;
}

Person.prototype[Symbol.toStringTag] = "Array";

Person.prototype.toString = function() {
    return this.name;
}

let me = new Person("heihei");

console.log(me.toString());                              //"heihei"
console.log(Object.prototype.toString.call(me))  //"[Object Array]"

在这段代码中,调用Object.prototype.toString() 方法得到的结果是"[object Array]",跟你从一个真实数组中得到的结果完全一样。这也就意味着,Object.prototype.toString()不是一个十分可靠的识别对象类型的方法。


Symbol.unscopables 属性

with 语句是 JavaScript 中最有争议的一个语句,设计它的初衷是可以免于编写重复的代码。但由于加入with语句后,代码变得难以理解,它的执行性能很差且容易导致程序出错,因此被大多数开发者所诟病。最终,标准规定,在严格模式下不可以使用with语句;且这条限制同样影响到了类和模块,默认使用严格模式且没有任何退出的方法。

var values = [1,2,2],
     colors = ["red","green","blue"],
     color = "black";

with(colors) {
    push(color);
    push(...values);
}

console.log(colors);   //"["red", "green", "blue", "black", 1, 2, 2]"

注意上面的代码里有个values变量,但在ECMAScript 6 中,数组中添加了一个values方法。总之,在ECMAScript6环境中,with语句引用的values不是with语句外的变量values,而是数组本身的values方法,这样就脱离代码原本的目标了。因此ECMAScript 6也添加了 Symbol.unscopables这个Symbol来解决这个问题。

Symbol.unscopables这个Symbol通常用于Array.prototype。以在with语句中标示出不创建绑定的属性名。Symbol.unscopables是以对象的形式出现的,它的键是在with语句中要忽略的标识符,其对应的值必须是 true 。这里是数组Symbol.unscopables属性的默认值:

// 已默认内置到ECMAScript 6中
Array.prototype[Symbol.unscopables] = Object.assign(Object.create(null),{
    copyWithin: true,
    entries: true,
    fill: true,
    find: true,
    findIndex: true,
    keys: true,
    values: true
})

☠️不要为自己创建的对象定义Symbol.unscopables属性,除非在代码中使用了with语句并且正在修改代码库中已有的对象。

元编程💻

概述⌨️

元编程是指操作目标是程序本身的行为特性的编程。换句话说,它是对程序的编程的编程。(体会下上面的well-known api)


内省:一种元编程形式,举例来说,如果想要查看对象a和另外一个对象b的关系是否是[[prototype]]链接🔗的,可以使用a.isPrototype(b)

元编程关注以下几点:

  • 代码查看自身
  • 代码修改自身
  • 代码修改默认语言特性

元编程的目标是利用语言本身的内省能力使代码的其余本分更具描述性,表达性和灵活性


元属性🖥

元属性以属性访问的形式提供特殊的其他方法无法获取的元信息。

以 new.target 为🌰,关键字new用作属性访问的上下文。显然,new 本身不是一个对象,因此这个功能很特殊。而在构造器调用(通过new触发的函数/方法)内部使用new.target时,new成了一个虚拟上下文,使得new.target能够指向调用new的目标构造器。

这个是元编程操作的一个明显示例,因为它的目的是从构造器调用内部确定最初new的目标是什么,通用地说用于内省(检查类型/结构)或者静态属性访问。

好吧😨,这个整不来整不来,太吃力了。留个坑,以后回来再填。👻