Javascript——Symbol简单了解

本文详细介绍了JavaScriptES6中的Symbol数据类型,包括其作用、创建方式、共享注册表、方法如Symbol.for和Symbol.keyFor,以及在异步迭代、instanceof操作符和数组连接行为中的应用。
摘要由CSDN通过智能技术生成

一、Symbol(符号)

1.1 MDN链接:yield - JavaScript | MDN (mozilla.org)")

1.2 Symbol简介

Symbol(符号)是ECMAScript6新增的数据类型。符号是原始值,且符号实例是唯一、不可变的。符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。尽管符号听起来跟私有属性有点类似,但符号并不是为了提供私有属性的行为才增加的(尤其是因为Object API提供了方法,可以更方便地发现符号属性)。相反,符号就是用来创建唯一记号,进而用作非字符串形式的对象属性。

① Symbol没有字面量语法,你只要创建Symbol()实例并将其用作对象的新属性,就可以保证它不会覆盖已有的对象属性,无论是符号属性还是字符串属性。

let genericSymbol = Symbol();
console.log(genericSymbol) // Symbol();

let fooSymbol = Symbol('foo');
console.log(fooSymbol); // Symbol(foo);

② Symbol()函数不能用作构造函数,与new关键字一起使用。这样做是为了避免创建符号包装对象,像使用Boolean、String或Number那样,它们都支持构造函数且可用于初始化包含原始值的包装对象。

let mySymbol = new Symbol(); // TypError: Symbol is not a constructor

// 如果确实想使用符号包装对象,可以借用Object()函数
let mySymbol2 = Symbol();
let myWrappedSymbol = Object(mySymbol2);
console.log(typeof myWrappedSymbol); // object

二、符号中的一些方法

2.1 使用全局符号注册表:Symbol.for()

Symbol.for()方法:运行时不同部分需要共享和重用符号实例,可以通过Symbol.for注册在全局注册表。

提示:全局注册表中的符号必须使用字符串来创建,传给Symbol.for的参数会自动转为字符串。

// 验证通过Symbol.for注册在全局注册表的符号是共享的

let fooGlobalSymbol = Symbol.for("foo");
let otherFooGlobalSymbol = Symbol.for("foo");

console.log(fooGlobalSymbol === otherFooGlobalSymbol); // true

2.2 查询全局注册表:Symbol.keyFor()

// 创建全局符号
let s = Symbol.for('foo');
console.log(Symbol.keyFor(s)); //foo

// 创建普通符号
let s2 = Symbol('bar');
console.log(Symbol.keyFor(s2)); // undefined

// 如果传给Symbol.keyFor()的不是符号,则该方法抛出TypeError
Symbol.keyFor(123); // TypeError: 123 is not a Symbol

2.3 使用符号作为属性

凡是可以使用字符串或数值作为属性的地方,都可以使用符号。这就包括了对象字面量属性和Object.defineProperty()/Object.definedProperties()定义的属性。对象字面量只能在计算属性语法中使用符号作为属性。

let s1 = Symbol('foo'),
    s2 = Symbol('bar'),
    s3 = Symbol('baz'),
    s4 = Symbol('qux');

let o = {
    [s1]: 'foo val'
};
// 这样也可以:o[s1] = 'foo val';
console.log(o);
// { Symbol(foo): foo val }

Object.defineProperty(o, s2, { value: 'bar val' });

console.log(o);
// {Symbol(foo): foo val, Symbol(bar): bar val}

Object.defineProperties(o, {
    [s3]: {value: 'baz val'},
    [s4]: {value: 'qux val'}
});

console.log(o);
// {Symbol(foo): foo val, Symbol(bar): bar val,
// Symbol(baz): baz val, Symbol(qux): qux val }


2.4 获取对象中的普通/符号属性数组

(1)Object.getOwnPropertyNames:返回对象实例的常规属性数组;
(2)Object.getOwnPropertySymbols:返回对象实例的符号属性数组

(3) Object.getOwnPropertyDescriptors:返回常规和符号属性描述符对象

(4)Reflect.ownKeys:同时返回常规和符号属性键数组

let s1 = Symbol('foo'),
    s2 = Symbol('bar');

let o = {
  [s1]: 'foo val',
  [s2]: 'bar val',
  baz: 'baz val',
  qux: 'qux val'
};
// ["baz","qux"]
console.log(Object.getOwnPropertyNames(o));

// [Symbol(foo),Symbol(bar)]
console.log(Object.getOwnPropertySymbols(o));

// {baz: {...}, qux: {...}, Symbol(foo): {...}, Symbol(bar): {...}}
console.log(Object.getOwnPropertyDescriptors(o));

// ["baz", "qux", Symbol(foo), Symbol(bar)]
console.log(Reflect.ownKeys(o));

提示:因为符号属性是对内存中符号的一个引用,所以直接创建并用作属性的符号不会丢失。但是,如果没有显示地保存对这些属性的引用,那么必须遍历对象的所有符号属性才能找到相应的属性键。

三、常用内置符号

ECMAScript6引入了一些常用内置符号,用于暴露语言内部行为,开发者可以直接访问、重写或模拟这些行为。

3.1 Symbol.asyncIterator

Symbol.asyncIterator是ES2018规范定义的,因此只有版本非常新的浏览器支持它!

Symbol.asyncIterator 符号指定了一个对象的默认异步迭代器。如果一个对象设置了这个属性,它就是异步可迭代对象,可用于for await …of循环。

3.1.1 自定义异步可迭代对象。

我们可以通过设置[Symbol.asyncIterator]属性来自定义异步可迭代对象。

demo(这里直接用MDN上的一个demo):

const myAsyncIterator = new Object();

myAsyncIterator[Symbol.asyncIterator] = async function* () {
  yield "hello";
  yield "async";
  yield "iteration!";
  yield () => {};
};

(async () => {
  for await (const x of myAsyncIterator) {
    console.log("x=", x);
  }
    // "expected output:
    //     "hello"
    //     "async"
    //     "iteration!"
    //     () => {}
})();
3.1.2 内建异步可迭代对象

目前没有默认设定了[Symbol.asyncIterator]属性的Javascript内建的对象。不过,WHATWG(网页超文本应用技术工作小组)Streams会被设定为第一批异步可迭代对象,[Symbol.asyncIterator]最近已在设计规范中落地。

3.2 Symbol.hasInstance

Symbol.hasInstance用于判断对象是否为构造器的实例。因此你可以用它自定义instanceof操作符在某个类上的行为。该方法决定一个构造器是否认可一个对象是它的实例。由instanceof操作符使用,instanceof操作符可以用来确定一个对象实例的原型链上是否有原型。

instanceof的典型使用场景如下:

function Foo() {};

let f = new Foo();
console.log(f instanceof Foo); // true

class Bar {}
let b = new Bar();
console.log(b instanceof Bar); // true

标注①:在ES6中,instanceof操作符会使用Symbol.hasInstance函数来确定关系。以Symbol.hasInstance为键的函数会执行同样的操作,只是操作数对调了一下:

function Foo() {}
let f = new Foo();
console.log(Foo[Symbol.hasInstance](f)); // true

class Bar {}
let b = new Bar();
console.log(Bar[Symbol.hasInstance](b)); // true

这个属性定义在Function的原型上,因此默认在所有函数和类上都可以调用。由于instanceof操作符会在原型链上寻找这个属性定义,就跟在原型链上寻找其它属性一样,因此可以在继承的类上通过静态方法重新定义这个函数:

class Bar {}
class Baz extends Bar {
    static [Symbol.hasInstance]() {
        return false;
    }
}

let b = new Baz();
console.log(Bar[Symbol.hasInstance](b)); // true
console.log(b instanceof Bar);
console.log(Baz[Symbol.hasInstance](b)); // false
console.log(b instanceof Baz); // false

结论:通过上面这个案例中的代码:“b instanceof Baz”,我们验证了标注①位置的结论:在ES6中的操作符 instanceof实际上会使用Symbol.hasIntance来确定关系(通过Symbol.hasInstance可以重写instanceof操作符的行为)。

3.3 Symbol.isConcatSpreadable

根据ECMAScript规范,这个符号这个符号作为一个属性表示“一个布尔值,如果是true,则意味着对象应该用Array.prototype.concat()打平其数组元素”。ES6中的Array.prototype.concat()方法会根据接收到的对象类型选择如何将一个类数组对象拼接成数组实例。覆盖Symbol.isConcatSpreadable的值可以修改这个行为。

数组对象默认情况下会被打平到已有的数组,false或假值会导致整个对象被追加到数组末尾。类数组对象默认情况下会被追加到数组末尾,true或真值会导致这个类数组对象被打平到数组实例。其它不是类数组对象的对象在Symbol.isConcatSpreadable被设置为true的情况下将被忽略

可以直接理解成,该属性用来配置连接A([1])和B数组[2]时,执行A.concat(B)是否会展开B数组连接,如果给A的Symbol.isConcatSpreadable赋值成false时,此时A.concat(B)的结果时:[1,[2]],如果不给A的Symbol.isConcatSpreadable赋值,此时打印Symbol.isConcatSpreadable的值是undefined,执行后的结果是:[1, 2]。

let initial = ['foo'];
let array = ['bar'];

console.log(array[Symbol.isConcatSpreadable]); // undefined
console.log(initial.concat(array)); // ['foo', 'bar']
array[Symbol.isConcatSpreadable] = false;
console.log(initial.concat(array)); // ['foo', ['bar']]

let arrayLikeObject = {length: 1, 0: 'baz'}
console.log(arrayLikeObject[Symbol.isConcatSpreadable]); // undefined
console.log(initial.concat(arrayLikeObject)); // ['foo', {...}]
arrayLikeObject[Symbol.isConcatSpreadable] = true;
console.log(initial.concat(arrayLikeObject)); // ['foo', 'baz']

let otherObject = new Set().add('qux');
console.log(otherObject[Symbol.isConcatSpreadable]); // undefined
console.log(initial.concat(otherObject));
otherObject[Symbol.isConcatSpreadable] = true;
console.log(initial.concat(otherObject)); // ['foo']

3.4 Symbol.iterator

根据ECMAScript规范,这个符号作为一个属性表示“一个方法,该方法返回对象默认的迭代器。由for-of使用”。换句话说,这个符号实现迭代器的API的函数。for-of循环这样的语言结构会利用这个函数执行迭代操作。循环时,它们会调用以Symbol.iterator为键的函数,并默认这个函数会返回一个实现迭代器API的对象。很多时候,返回的对象是实现该API的Generator:

class Foo {
    *[Symbol.iterator](){}
}
let f = new Foo();
consoe.log(f[Symbol.iterator]();

技术上,这个由Symbol.iterator函数生成的对象应该通过next()方法陆续返回值。可以通过显式地调用next()

class Emitter {
  constructor(max) {
    this.max = max;
    this.idx = 0;
  }

  *[Symbol.iterator] () {
    while(this.idx < this.max) {
      yield this.idx++;
    }
  }
}
function count() {
  let emitter = new Emitter(5);
  for(const x of emitter) {
    console.log(x);
  }
}
count();

3.5 Symbol.match

根据ECMAScript规范,这个符号作为一个属性表示“一个正则表达方法,该方法用正则表达式去匹配字符串。由String.prototype.match为键的函数来对正则表达式求值。正则表达式的原型上默认有这个函数的定义,因此所有的正则表达式实例默认是这个String方法的有效参数;

可以通过Symbol.match重写String.prototype.match

class FooMatcher {
  static [Symbol.match](target) {
    return target.includes('foo');
  }
}

console.log('foobar'.match(FooMatcher)); // true
console.log('barbaz'.match(FooMatcher)); // false

给String.prototype.match这个方法传入非正则表达式值会导致该值被转换成RegExp对象。如果想要改变这种行为,让方法直接使用参数,则可以重新定义Symbol.match函数以取代默认对正则表达式求值的行为,从而让match()方法使用非正则表达式实例。Symbol.match函数接收一个参数,就是调用match()方法的字符串实例。返回的值没有限制:

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Python工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Python开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以扫码获取!!!(备注Python)

img-NGrSEh5I-1712873945207)]

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以扫码获取!!!(备注Python)

img
  • 16
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值