JS高级
Symbol
symbol 是一种基本数据类型。
语法
Symbol(description?)
参数
- description:
string
,对 symbol 的描述,可用于调试但不是访问 symbol 本身
示例
// 1. 通过Symbol函数创建一个Symbol
const s1 = Symbol()
// 2. 创建的时候传入一个description
const s2 = Symbol('s2')
// 3. Symbol函数每次创建出来的值都是独一无二的
console.log(Symbol() == Symbol()); // falses
console.log(Symbol() === Symbol()); // false
// 4. Symbol作为对象属性的标识符
const obj = {
[s1]: 'name',
[s2]: 'age'
}
console.log(obj); // {Symbol(): 'name', Symbol(s2): 'age'}
// 5. 获取Symbol对应的key
console.log(Object.getOwnPropertySymbols(obj)) // [Symbol(), Symbol(s2)]
// 6. Symbol.for(key)
const s3 = Symbol.for('s3')
console.log(s3) // Symbol(s3)
// 7. 相同的key,通过`Symbol.for()`可以生成相同的Symbol值
const s4 = Symbol.for('ss')
const s5 = Symbol.for('ss')
console.log(s4 === s5) // true
// 8. 通过`Symbol.keyFor()` 可以获取通过Symbol.for()传入的key
console.log(Symbol.keyFor(s2)) // undefined
console.log(Symbol.keyFor(s5)) // ss
API
- Symbol(description?):``,创建一个Symbol
- Object.getOwnPropertySymbols(obj):``,返回一个给定对象自身的所有 Symbol 属性的数组
- 属性
- Symbol.prototype.description:``,(只读),返回 Symbol 对象的可选描述的字符串
- 方法
- Symbol.for(key):
key
,来从运行时的 symbol 注册表中找到对应的 symbol,如果找到了,则返回它,否则,新建一个与该键关联的 symbol - Symbol.keyFor(sym):``,用来获取全局 symbol 注册表中与某个 symbol 关联的键key
基本使用
Symbol是什么呢?Symbol是ES6中新增的一个基本数据类型,翻译为符号。
那么为什么需要Symbol呢?
- 在ES6之前,对象的属性名都是字符串形式,那么很容易造成属性名的冲突;
- 比如原来有一个对象,我们希望在其中添加一个新的属性和值,但是我们在不确定它原来内部有什么内容的情况下,很容易造成冲突,从而覆盖掉它内部的某个属性;
- 比如我们前面在讲apply、call、bind实现时,我们有给其中添加一个fn属性,那么如果它内部原来已经有了fn属性了呢?
- 比如开发中我们使用混入,那么混入中出现了同名的属性,必然有一个会被覆盖掉;
Symbol就是为了解决上面的问题,用来生成一个独一无二的值。
- Symbol值是通过Symbol函数来生成的,生成后可以作为属性名;这是该数据类型仅有的目的
- 也就是在ES6中,对象的属性名可以使用字符串,也可以使用Symbol值;
Symbol即使多次创建值,它们也是不同的:Symbol函数执行后每次创建出来的值都是独一无二的;
我们也可以在创建Symbol值的时候传入一个描述description:这个是ES2019(ES10)新增的特性;
Symbol作为属性名
我们通常会使用Symbol在对象中表示唯一的属性名
相同值的Symbol
前面我们讲Symbol的目的是为了创建一个独一无二的值,那么如果我们现在就是想创建相同的Symbol应该怎么来做呢?
- 我们可以使用Symbol.for方法来做到这一点
- 并且我们可以通过Symbol.keyFor方法来获取对应的key
相同的key,通过Symbol.for()
可以生成相同的Symbol值
const s4 = Symbol.for('ss')
const s5 = Symbol.for('ss')
console.log(s4 === s5) // true
通过Symbol.keyFor()
可以获取通过Symbol.for()传入的key
console.log(Symbol.keyFor(s2)) // undefined
console.log(Symbol.keyFor(s5)) // ss
Set
Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。
语法
new Set(iterable?)
参数
- iterable:``,如果传递一个可迭代对象,它的所有元素将不重复地被添加到新的 Set 中
返回值
- 一个新的
Set
对象
示例
const mySet = new Set();
mySet.add(1); // Set [ 1 ]
mySet.add(5); // Set [ 1, 5 ]
mySet.add(5); // Set [ 1, 5 ]
mySet.add('some text'); // Set [ 1, 5, 'some text' ]
const o = { a: 1, b: 2 };
mySet.add(o);
API
- 属性
- size:``,返回Set中元素的个数
- 方法
- add(value):
返回:Set对象
,添加某个元素 - delete(value):
返回:Boolean
,从set中删除和这个值相等的元素 - has(value):
返回:Boolean
,判断set中是否存在某个元素 - clear():
返回:void
,清空set中所有的元素 - forEach(callback, thisArg?):
返回:undefined
,通过forEach遍历set- 参数
- callback:
function(value?, key?, set?)
,为集合中每个元素执行的回调函数 - thisArg:
callback
时作为this
使用
注意:Set支持for of的遍历
常见方法
添加元素
// 2. 添加Set - add()
set.add('Tom')
console.log(set); // Set(1) {'Tom'}
// 3. Set中不能放入重复的元素
set.add('Jack')
set.add('Jack')
console.log(set) // Set(2) {'Tom', 'Jack'}
删除元素
// 5. 常见方法 - delete()
console.log(set) // Set(2) {'Tom', 'Jack'}
set.delete('Tom')
console.log(set) // Set(1) {'Jack'}
是否包含某个元素
// 6. 常见方法 - has()
console.log(set.has('Jack')) // true
清空set
// 7. 常见方法 - clear()
set.clear()
console.log(set) // Set(0) {size: 0}
forEach遍历
// 8. 常见方法 - forEach()
set2.forEach((item, index, set) => {
console.log(item, index, set) // 刘备 刘备 Set(4) {'刘备', '关羽', '张飞', '吕布'}
})
基本使用
在ES6之前,我们存储数据的结构主要有两种:数组、对象。
- 在ES6中新增了另外两种数据结构:Set、Map,以及它们的另外形式WeakSet、WeakMap。
Set是一个新增的数据结构,可以用来保存数据,类似于数组,但是和数组的区别是元素不能重复。
- 创建Set我们需要通过Set构造函数(暂时没有字面量创建的方式):
我们可以发现Set中存放的元素是不会重复的,那么Set有一个非常常用的功能就是给数组去重。
创建Set
// 1. 创建Set
const set = new Set()
console.log(set) // Set(0) {size: 0}
2个空对象不是重复的元素
// 10. 2个空对象不是重复的元素
const set4 = new Set()
set4.add({})
set4.add({})
console.log(set4) // Set(2) {{…}, {…}}
应用:数组去重
// 4. 应用:数组去重
const arr = ['刘备', '关羽', '张飞', '吕布', '关羽', '刘备']
const set2 = new Set(arr)
console.log(set2) // Set(4) {'刘备', '关羽', '张飞', '吕布'}
const set3 = Array.from(set2)
console.log(set3) // (4) ['刘备', '关羽', '张飞', '吕布']
// 简单写法一
console.log(Array.from(new Set(arr))) // (4) ['刘备', '关羽', '张飞', '吕布']
// 或者写法二
console.log([...new Set(arr)]) // (4) ['刘备', '关羽', '张飞', '吕布']
之前数组去重的做法
set支持for...of遍历
只要是可迭代对象都可以通过for...of遍历
// 9. 通过for...of遍历Set
for (const item of set2) {
console.log(item) // 刘备 关羽 张飞 吕布
}
WeakSet
WeakSet 对象允许你将弱保持对象存储在一个集合中
语法
API
- 方法
- add(value):
返回:WeakSet对象
,添加某个元素 - delete(value):
返回:Boolean
,从WeakSet中删除和这个值相等的元素 - has(value):
返回:Boolean
,判断WeakSet中是否存在某个元素
基本使用
和Set类似的另外一个数据结构称之为WeakSet,也是内部元素不能重复的数据结构。
那么和Set有什么区别呢?
- 区别一:WeakSet中只能存放对象类型,不能存放基本数据类型;
- 区别二:WeakSet对元素的引用是弱引用,如果没有其他引用对某个对象进行引用,那么GC可以对该对象进行回收;
WeakSet中只能存放对象类型
普通对象的内存图
解释:普通对象被重新赋值为null时,就断开了和内存中对象的联系,但是由于之前已经将对象的内存地址赋值给了数组arr,赋值为null后这些对象依然被数组arr所引用,所以它们并不会被销毁
WeakSet内存图
解释: 添加到WeakSet中的对象都是弱引用,可能会被GC随时回收
注意:WeakSet不能遍历
-
因为WeakSet只是对对象的弱引用,如果我们遍历获取到其中的元素,那么有可能造成对象不能正常的销毁。
-
所以存储到WeakSet中的对象是没办法获取的;
应用:限制类中方法的调用者
- 事实上这个问题并不好回答,我们来使用一个Stack Overflow上的答案;
此处用WeakSet的好处:想要销毁实例对象p的时候,可以直接通过p = null
销毁,如果使用Set的话,由于实例对象一直被Set引用,所以无法销毁
Map
基本使用
另外一个新增的数据结构是Map,用于存储映射关系。
但是我们可能会想,在之前我们可以使用对象来存储映射关系,他们有什么区别呢?
-
事实上我们对象存储映射关系只能用字符串(ES6新增了Symbol)作为属性名(key);
-
某些情况下我们可能希望通过其他类型作为key,比如对象,这个时候会自动将对象转成字符串来作为key;
那么我们就可以使用Map
常用方法
Map常见的属性:
- size:返回Map中元素的个数;
Map常见的方法:
- set(key, value):在Map中添加key、value,并且返回整个Map对象;
- get(key):根据key获取Map中的value;
- has(key):判断是否包括某一个key,返回Boolean类型;
- delete(key):根据key删除一个键值对,返回Boolean类型;
- clear():清空所有的元素;
- forEach(callback, [, thisArg]):通过forEach遍历Map;
Map也可以通过for of进行遍历。
WeakMap
基本使用
和Map类型的另外一个数据结构称之为WeakMap,也是以键值对的形式存在的。
那么和Map有什么区别呢?
- 区别一:WeakMap的key只能使用对象,不接受其他的类型作为key;
- 区别二:WeakMap的key对对象想的引用是弱引用,如果没有其他引用引用这个对象,那么GC可以回收该对象;
WeakMap常见的方法有四个:
- set(key, value):在Map中添加key、value,并且返回整个Map对象;
- get(key):根据key获取Map中的value;
- has(key):判断是否包括某一个key,返回Boolean类型;
- delete(key):根据key删除一个键值对,返回Boolean类型;
应用
注意:WeakMap也是不能遍历的
- 没有forEach方法,也不支持通过for of的方式进行遍历;
那么我们的WeakMap有什么作用呢?(后续专门讲解)
手写
手写call,aplly,bind
函数对象原型关系
函数foo对象的隐式原型 === Function的显式原型
// 函数foo对象的隐式原型 === Function的显式原型
console.log(foo.__proto__ === Function.prototype) // true
console.log(Function.prototype.apply) // f apply()
console.log(Function.prototype.call) // f call()
console.log(Function.prototype.bind) // f bind()
console.log(Function.prototype.apply === foo.apply) // true
结论:
- 对象中的某些属性和方法是来自Function.prototype的
- 在Function.prototype中添加的属性和方法,可以被所有的函数获取
在Function的原型中添加方法bar
手写apply方法
给函数对象添加方法
function foo () {
console.log('foo', this)
}
Function.prototype.mrapply = function(mrthis) {
// 相当于 mrthis.fn = this
Object.defineProperty(mrthis, 'fn', {
configurable: true,
value: this
})
// 隐式调用fn,可以让fn函数的this指向 mrthis
mrthis.fn()
// 删除多出来的临时函数fn
delete mrthis.fn
}
foo.mrapply({name: "Tom"})
如果传入的参数是一个String或者Number的类型,需要将其包裹成对象类型,才能在它上面添加属性
调用mrapply时,传递参数
function foo (age, height) {
console.log('foo', this, age, height)
}
+ Function.prototype.mrapply = function(mrthis, args) {
// 当this不是对象时,需要用Object包裹
mrthis = (mrthis === null || mrthis === undefined) ? window : Object(mrthis)
// 相当于 mrthis.fn = this
Object.defineProperty(mrthis, 'fn', {
configurable: true,
value: this
})
// 隐式调用fn,可以让fn函数的this指向 mrthis
+ mrthis.fn(...args)
// 删除多出来的临时函数fn
delete mrthis.fn
}
+ foo.mrapply({name: "Tom"}, [18, 1.88])
foo.mrapply(null, [18, 1.88])
foo.mrapply(undefined, [18, 1.88])
foo.mrapply(true, [18, 1.88])
foo.mrapply(123, [18, 1.88])
foo.mrapply('aaaa', [18, 1.88])
手写call方法
function foo(age, height) {
console.log('foo', this, age, height)
}
+ Function.prototype.mrcall = function(mrthis, ...args) {
mrthis = (mrthis === null || mrthis === undefined) ? window : Object(mrthis)
Object.defineProperty(mrthis, 'fn', {
configurable: true,
value: this
})
+ mrthis.fn(...args)
delete mrthis.fn
}
+ foo.mrcall({ name: "张飞" }, 20, 1.77)
抽取封装公共函数
/* 抽取封装的函数 */
+ Function.prototype.mrexec = function(mrthis, args) {
mrthis = (mrthis === null || mrthis === undefined) ? window : Object(mrthis)
// mrthis.fn = this
Object.defineProperty(mrthis, 'fn', {
configurable: true,
value: this
})
mrthis.fn(...args)
delete mrthis.fn
}
/* 手写apply */
Function.prototype.mrapply = function(mrthis, args) {
this.mrexec(mrthis, args)
}
/* 手写call */
Function.prototype.mrcall = function(mrthis, ...args) {
this.mrexec(mrthis, args)
}
// 测试
function foo(age, height) {
console.log('foo', this, age, height)
}
foo.mrapply({name: "Tom"}, [19, 1.66])
foo.mrcall({name: "Jack"}, 22, 1.99)
手写bind方法
和apply, call不同,bind执行后是返回一个新的函数newFoo
基础实现
思路:想办法实现如下:
// 伪代码
{ name: "why" }.foo(name, age)
/* 手写bind */
Function.prototype.mrbind = function(mrthis, ...args) {
+ return (...moreArgs) => {
mrthis = (mrthis === null || mrthis === undefined) ? window : Object(mrthis)
Object.defineProperty(mrthis, 'fn', {
configurable: true,
value: this
})
+ const allArgs = [...args, ...moreArgs]
+ mrthis.fn(...allArgs)
+ delete mrthis.fn // 可以删除fn,因为每次调用newFoo,都会重新生成一个mrthis.fn
}
}
// 测试
function foo(name, age, height, address) {
console.log('foo', this, name, age, height, address)
}
const newFoo = foo.mrbind({name: "Jerry"}, '张飞', 45)
console.log(newFoo)
+ newFoo(1.88, '成都')
+ newFoo(1.88, '成都')
浅拷贝,深拷贝
引用赋值
浅拷贝
方式:
- 解构赋值:
const info = {...obj}
浅拷贝修改info2.name后,obj的name依然是"why",被修改的只是info2
浅拷贝的内存图
如果obj对象中有其他对象(或数组)时的内存图
深拷贝
方式:
- 1、借助第三方库:
underscore
- 2、利用现有JS机制:
JSON
- 3、自己实现:
2、利用现有JS机制:JSON
语法:
const info3 = JSON.parse(JSON.stringify(obj))
缺点: 该方法不能实现方法的深拷贝,会忽略obj对象中的方法
const obj = {
name: 'Tom',
age: 18,
friend: {
name: 'Jack'
},
run: function() {
console.log(this.name + '在跑步~');
}
}
// 利用JSON机制实现深拷贝
+ const info = JSON.parse(JSON.stringify(obj))
// 测试
console.log(info)
// 修改info的深度属性,obj的深度属性保持不变
+ info.friend.name = '张飞'
+ console.log('obj', obj.friend.name); // obj Jack
+ console.log('info', info.friend.name); // obj 张飞
// 不能实现方法的深拷贝,会忽略obj对象中的方法
+ info.run() // ncaught TypeError: info.run is not a function
标签:mrthis,Set,console,log,Symbol,S07,01,key,JS
From: https://www.cnblogs.com/pikachu/p/17167175.html