1. 字符串的拓展
1. 字符串遍历器接口
ES6位字符串添加了遍历接口,使得字符串可以被for...of循环遍历。
for (let codePoint of 'foo') {
console.log(codePoint)
}
// "f"
// "o"
// "o"
2. 模板字符串
1. 标签模板
当模板字符串里有变量,就不是简单的调用,而是将模板字符串先处理成多个参数,再调用函数。
let a = 5;
let b = 10;
function tag(s, v1, v2) {
console.log(s[0]);
console.log(s[1]);
console.log(s[2]);
console.log(v1);
console.log(v2);
return "OK";
}
tag`Hello ${ a + b } world ${ a * b}`; //会转为tag(['Hello ', ' world ', ''], 15, 50)
// "Hello "
// " world "
// ""
// 15
// 50
// "OK"
再看一个更加复杂的例子,我们如何将各个参数按照原来的位置拼合回去
let total = 30;
let msg = passthru`The total is ${total} (${total*1.05} with tax)`;
//他会先处理成literals = ['The total is',30,'31.5 with tax']
function passthru(literals) {
let result = '';
let i = 0;
while (i < literals.length) {
result += literals[i++];
if (i < arguments.length) {
result += arguments[i];
}
}
return result;
}
msg // "The total is 30 (31.5 with tax)"
既然他会把变量部分放在最后面,那么我们就可以采用rest参数的写法如下
function passthru(literals, ...values) {
let output = "";
let index;
for (index = 0; index < values.length; index++) {
output += literals[index] + values[index];
}
output += literals[index]
return output;
}
3. 字符串的新增方法
1. String.raw()
该方法返回一个斜杠都被转义(即前面再加一个斜杠)的字符串。往往处理模板字符串
String.raw`Hi\n${2+3}!`
// 实际返回 "Hi\\n5!",显示的是转义后的结果 "Hi\n5!"
String.raw()本质上是一个正常的函数,只是专用于上述的模板字符串的标签函数。如果正常去写的话,第一个参数则是一个具有raw属性的对象,raw的值则是一个模板字符串转化出来值得数组。如下
// `foo${1 + 2}bar`
// 等同于
String.raw({ raw: ['foo', 'bar'] }, 1 + 2) // "foo3bar"
他的实现逻辑和上述的passthru一致,这里不再赘述。
2. 实例方法
1. includes(),startsWith(),endsWith()
在ES6之前,Js只有indexOf方法,来确定字符串是否包含。ES6中又提供了三种新方法。
- includes() :返回布尔值,表示是否找到了参数字符串。
- startsWith() :返回布尔值,表示参数字符串是否在原字符串的头部。
- endsWith() :返回布尔值,表示参数字符串是否在原字符串的尾部。
let s = 'Hello world!';
s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true
上述三种方法都支持第二个参数,即开始搜索的位置。
2. repeat()
repeat方法返回一个新字符串,表示将原字符串重复n次
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
如果参数是小数,则会取证。参数是Infinity或负数,则会报错。0~-1取0,。NAN取0,字符串先转为数字。
3. padStart(),penEnd()
ES7中引入了上述两种不全字符串长度的两种方法,一共接受两个参数,第一个为补全最大长度,第二个为补全的字符串。
'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'
pdaStart()的常见用途比如为数字补全指定位数。
'123456'.padStart(10, '0') // "0000123456"
另一个用途则是提示字符串格式。
'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"
'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"
4. trimStart(),trimEnd()
ES9对字符串实例新增了上述两种方法。即消除头部、尾部空格
5. replaceAll()
在历史上,字符串的实例方法replace()只能替换第一个匹配。
'aabbcc'.replace('b', '_')
// 'aa_bcc'
在ES2021中,引入了上述方法,可以一次性替换所有的匹配。
'aabbcc'.replaceAll('b', '_')
// 'aa__cc'
他们都是返回一个新的字符串,不会改变原字符串。
6. at()
at方法返回参数指定位置的字符,支持负索引。
const str = 'hello';
str.at(1) // "e"
str.at(-1) // "o"
2. 函数的拓展
1. 函数参数的默认值
function Point(x = 0, y = 0) {
this.x = x;
this.y = y;
}
const p = new Point();
p // { x: 0, y: 0 }
1. 与解构赋值默认值结合使用
function foo({x, y = 5}) {
console.log(x, y);
}
foo({}) // undefined 5
foo({x: 1}) // 1 5
foo({x: 1, y: 2}) // 1 2
foo() // TypeError: Cannot read property 'x' of undefined
上述代码中,使用了对象的解构赋值,但是如果参数不穿对象时,则会报错,所以可以用默认值给到一个空对象。例如
function foo({x, y = 5} = {}) {
console.log(x, y);
}
2. 参数带有默认值的位置
通常情况下,定义了默认值的参数,应该是函数的尾函数。如果不是尾函数设置默认值,则这个参数不能省略。
function f(x = 1, y) {
return [x, y];
}
f(, 1) // 报错
3.函数的length属性
在制定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说length属性表明预期传入参数的个数,给了默认值就不算在预期中了,所以rest参数也不会计入。
4. 带有默认值的作用域
一旦参数设置了默认值,那么函数在进行声明初始化时,参数会形成一个单独的作用域。
let x = 1;
function f(y = x) {
let x = 2;
console.log(y);
}
f() // 1
上述代码中,给了参数y一个初始值为x,形成了一个作用域,在作用域中x没有定义,则指向外层的全局变量x,如果全局中也没有x,则会报错。
2. rest参数
ES6中引入了rest参数,这样就不需要使用arguments对象了,来看一下他们的区别
// arguments变量的写法
function sortNumbers() {
return Array.from(arguments).sort();
}
// rest参数的写法
const sortNumbers = (...numbers) => numbers.sort();
因为arguments对象不是数组,而是一个类似数组的对象,需要用Array.from来转为数组。
注意rest参数之后就不能再有其他参数了。
3. name属性
返回函数的函数名。在ES5中,将一个匿名函数赋值给一个变量,会返回空。ES6中则会返回变量名。
var f = function () {};
// ES5
f.name // ""
// ES6
f.name // "f"
Function构造函数返回的函数实例,name属性的值为anonymous。
(new Function).name // "anonymous"
bind返回的函数,name属性值会加上bound前缀。
function foo() {};
foo.bind({}).name // "bound foo"
(function(){}).bind({}).name // "bound "
4. 箭头函数
箭头函数可以与变量结构结合使用
const full = ({ first, last }) => first + ' ' + last;
// 等同于
function full(person) {
return person.first + ' ' + person.last;
}
箭头函数也可以与rest参数结合使用
const numbers = (...nums) => nums;
numbers(1, 2, 3, 4, 5)
// [1,2,3,4,5]
箭头函数的注意事项
(1)箭头函数没有自己的this对象(详见下文)。
(2)不可以当作构造函数,也就是说,不可以对箭头函数使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
首先第一点,箭头函数没有this,他的this始终指向函数定义时(上下文)所在的作用域。而普通函数的this则指向运行时所在的作用域,观察下述代码。
function Timer() {
this.s1 = 0;
this.s2 = 0;
// 箭头函数
setInterval(() => this.s1++, 1000);
// 普通函数
setInterval(function () {
this.s2++;
}, 1000);
}
var timer = new Timer();
setTimeout(() => console.log('s1: ', timer.s1), 3100);
setTimeout(() => console.log('s2: ', timer.s2), 3100);
// s1: 3
// s2: 0
前者的this绑定在Timer函数,后者的this则绑定在运行时的全局作用域中。 不适合使用箭头函数的情况
比如在对象中的方法中,就不适合使用箭头函数带this,因为对象没有单独的作用域,this为全局作用域。
globalThis.s = 21;
const obj = {
s: 42,
m: () => console.log(this.s)
};
obj.m() // 21
5. 尾调用优化
什么是尾调用呢???
尾调用就是在函数的最后一步,调用了另一个函数。比如
function f(x){
return g(x);
}
如果在调用g(x)后,还有赋值操作,则不属于尾调用。 那怎么进行尾调用优化呢?
尾调用优化即在最后调用了g函数,那么f中的mn变量则不需要了,这样就可以完全删除f(x)的调用帧,而只保留g(3)的调用帧。- 只有safari支持。。。
function f() {
let m = 1;
let n = 2;
return g(m + n);
}
f();
// 等同于
function f() {
return g(3);
}
f();
// 等同于
g(3);
6. Function.prototype.toString()
ES2019对函数实例的toString()方法做了修改。会返回代码本身,包括注释和空格,原封不动!
function /* foo comment */ foo () {}
foo.toString()
// "function /* foo comment */ foo () {}"
3. 数组的拓展
1. 扩展运算符
它好比res参数的逆运算,将一个数组转为用逗号分隔的参数序列
console.log(...[1, 2, 3])
// 1 2 3
1. 替代apply方法
之前如果我们想将数组转为函数的参数,需要借助apply方法,如下。
function f(x, y, z) {
// ...
}
var args = [0, 1, 2];
f.apply(null, args);
// ES6 的写法
function f(x, y, z) {
// ...
}
let args = [0, 1, 2];
f(...args);
使用push时,因为push的参数不能是数组,所以也可以利用扩展运算符。
// ES5 的写法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1, arr2);
// ES6 的写法
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
arr1.push(...arr2);
2. 扩展运算符的应用
1. 复制数组
数组如果直接复制的话,只是复制了底层数据结构的指针,而不是克隆一个全新的数组。
const a1 = [1, 2];
const a2 = a1;
a2[0] = 2;
a1 // [2, 2]
所以上述代码,在改变a2时,其实是在改变指针,所以a1也会发生变化。在ES5中,我们可以使用concat()来复制数组
const a1 = [1, 2];
const a2 = a1.concat();
a2[0] = 2;
a1 // [1, 2]
而如果使用扩展运算符,则十分的简单。
const a1 = [1, 2];
// 写法一
const a2 = [...a1];
// 写法二
const [...a2] = a1;
2. 合并数组
const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];
// ES5 的合并数组
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]
// ES6 的合并数组
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]
上述合并数组的方式都是通过对原数组的引用,都是浅拷贝。
3. 与解构赋值结合
const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest // [2, 3, 4, 5]
const [first, ...rest] = [];
first // undefined
rest // []
const [first, ...rest] = ["foo"];
first // "foo"
rest // []
4. 字符串
扩展运算符可以将字符串转成真数组
[...'hello']
// [ "h", "e", "l", "l", "o" ]
5. 实现了Iterator接口的对象
任何定义了遍历器(Iterator)接口的对象,都可以用扩展运算符转为真正的数组。
let nodeList = document.querySelectorAll('div');
let array = [...nodeList];
2. Array.from()
Array.from()方法用于将类数组、可遍历的对象转为真数组。
在实际应用中,DOM操作返回的NodeList集合,以及函数内部的arguments对象,都可以通过Array.from()来转换。
// NodeList 对象
let ps = document.querySelectorAll('p');
Array.from(ps).filter(p => {
return p.textContent.length > 100;
});
// arguments 对象
function foo() {
var args = Array.from(arguments);
// ...
}
Array.from()还可以接受一个函数作为第二个参数,作用类似于数组的map()方法.
Array.from(arrayLike, x => x * x);
// 等同于
Array.from(arrayLike).map(x => x * x);
3.Array.of()
Array.of()方法将一组值转换为数组。
Array.of(3, 11, 8) // [3,11,8]
4.实例方法
1. copyWithin()
将指定位置的成员复制到其他位置,会修改当前数组。将3号和4号位(第三号位元素)之间的数值覆盖到0号位。
// 将3号位复制到0号位
[1, 2, 3, 4, 5].copyWithin(0, 3, 4)
// [4, 2, 3, 4, 5]
2. find()和findIndex()
find()找到第一个符合条件的数组成员,没有符合条件的成员则返回undefined。
[1, 4, -5, 10].find((n) => n < 0)
// -5
findIndex()则是返回第一个符合条件的数组成员的位置,没有则返回-1
上述两种方法都可以接受第二个参数,来绑定回调函数的this对象。
function f(v){
return v > this.age;
}
let person = {name: 'John', age: 20};
[10, 12, 26, 15].find(f, person); // 26
3. fill()
fill方法使用定值填充数组
['a', 'b', 'c'].fill(7, 1, 2)
// ['a', 7, 'c']
4. entries(),keys().values()
ES6 提供三个新的方法——entries(),keys()和values()——用于遍历数组。
如果不使用for...of循环,可以手动调用遍历器对象的next方法,进行遍历。
let letter = ['a', 'b', 'c'];
let entries = letter.entries();
console.log(entries.next().value); // [0, 'a']
console.log(entries.next().value); // [1, 'b']
console.log(entries.next().value); // [2, 'c']
5. includes()
查看数组中是否存在给定的值,与字符串中的includes()方法类似。
对比indexOf(),它内部使用===来进行判断,这回导致对NaN进行误判。
6. flat(),flatMap()
将多层嵌套数组拉平,接收参数为拉平几层,拉平到底参数为Infinity。
[1, 2, [3, [4, 5]]].flat(2)
// [1, 2, 3, 4, 5]
flatMap()则是先对数组进行map()再进行flat(),只能展开一层数组。
// 相当于 [[[2]], [[4]], [[6]], [[8]]].flat()
[1, 2, 3, 4].flatMap(x => [[x * 2]])
// [[2], [4], [6], [8]]
4. 对象的新增方法
1. Object.is()
在ES5中比较两值是否相等,只有== ===他们都有缺点。在ES6中引入Object.is()来比较两个值是否严格相等。
Object.is('foo', 'foo') // true
Object.is({}, {}) // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
2. Object.assign()
用于对象的合并,将源对象的所有可枚举的属性,复制到给定对象中。如果有同名属性,则会覆盖。
const target = { a: 1, b: 1 };
const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
如果参数不是对象则先转为对象,如果是undefined和null则会报错
注意
- 浅拷贝
Object.assign()方法实行的是浅拷贝。
- 数组的处理
Object.assign()方法可以用来处理数组,但是会把数组视为对象。
Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3]
- 取值函数的处理 如果复制的值是一个取值函数,那么先求值,再复制。
const source = {
get foo() { return 1 }
};
const target = {};
Object.assign(target, source)
// { foo: 1 }
3. Object.getOwnPropertyDescriptors()
再ES7中,引入该方法,返回的是自身属性的描述对象。该方法引入的目的是为了解决Object.assign()无法正确拷贝到get和set属性的问题,它只会拷贝一个属性的值,而不会拷贝他背后的get和set。
我们可以通过Object.getOwnPropertyDescriptors()方法配合Object.defineProperties()方法,就可以实现正确拷贝。
const source = {
set foo(value) {
console.log(value);
}
};
const target2 = {};
//将source的属性值给到target2
Object.defineProperties(target2,Object.getOwnPropertyDescriptors(source);
Object.getOwnPropertyDescriptor(target2, 'foo')
// { get: undefined,
// set: [Function: set foo],
// enumerable: true,
// configurable: true }
4. __proto__属性,Object.setPrototypeOf(),Object.getPrototypeOf()
__proto__属性他是一个内部属性,我们可以用下面的Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替。
5.Symbol
1. 概述
Symbol可以生成一个独一无二的值,它可以接收一个字符串作为参数,主要是为了在控制台显示,或者转成字符串便于区分。注意即使参数相同的Symbol函数的返回值也是不相等的。
let s1 = Symbol('foo');
let s2 = Symbol('bar');
s1 // Symbol(foo)
s2 // Symbol(bar)
s1.toString() // "Symbol(foo)"
s2.toString() // "Symbol(bar)"
2. Symbol.prototype.description
我们在读取Symbol显式的字符串时,只能toString(),再ES2019中,增加了description来直接返回描述。
3. 作为属性名的Symbol
既然每一个Symbol都不相等,这样我们就可以使用Symbol的值作为标识符。
let mySymbol = Symbol();
// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';
// 第二种写法
let a = {
[mySymbol]: 'Hello!'
};
// 第三种写法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });
// 以上写法都得到同样结果
a[mySymbol] // "Hello!"
注意Symbol值作为对象属性名时,不能用点运算符,在对象内部使用时,也必须放在方括号之中。
4. 属性名遍历
Symbol作为属性名,在遍历对象时,不会被返回,可以通过Object.getOwnPropertySymbols()方法,获取所有的Symbol属性名,返回一个数组。
另一个新的 API,Reflect.ownKeys()方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。
5. Symbol.for(),Symbol.keyFor()
当我们想要多次使用同一个Symbol值时,可以使用Symbol.for(),他会在全局注册,当你下次使用时,会在全局中搜索,如果存在就返回之前注册的值。
Symbol.keyFor()则是返回一个已经登记的Symbol的key,使用Symbol注册的是没有登记的哦。
6. Set和Map数据结构
1. Set
1. 基本用法
Set类似于数组,但是成员的值都是唯一的。他本身是一个构造函数,用来生成Set数据结构。
既然他成员的值唯一,那么他就可以去除数组中重复的字符。在Set中,两个NaN是相等的,两个空对象是不相等的。
// 去除数组的重复成员
[...new Set(array)]
2. Set的实例属性&方法
Set 结构的实例有以下属性。
Set.prototype.constructor:构造函数,默认就是Set函数。Set.prototype.size:返回Set实例的成员总数。
Set 实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。下面先介绍四个操作方法。
Set.prototype.add(value):添加某个值,返回 Set 结构本身。Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。Set.prototype.clear():清除所有成员,没有返回值。
Array.from方法可以将 Set 结构转为数组。
const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);
3.遍历操作
Set.prototype.keys():返回键名的遍历器Set.prototype.values():返回键值的遍历器Set.prototype.entries():返回键值对的遍历器Set.prototype.forEach():使用回调函数遍历每个成员
for (let item of set.entries()) {
console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]
注意Set结构的键名和键值是一个值。
使用Set和扩展运算符可以很容易的实现并集、交集、差集
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}
// (a 相对于 b 的)差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}
2. WeakSet
他跟Set类似,但是有两个区别。
第一:WeakSet的成员只能是对象,如果我们将a做为WeakSet得参数,那意味着a的成员必须是对象才可以。如果a = [1,2,3,4],就会报错。
const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a);
// WeakSet {[1, 2], [3, 4]}
3. Map
1.基本用法
在Js中的对象只能用字符串当做键,为了解决这个问题,ES6提供了Map数据结构,但是键的范围不限于字符串。
2. 实例的属性和方法
1. size
size属性返回Map结构的成员总数
2. Map.prototype.set(key,value),Map.prototype.get(key),Map.prototype.has(key),Map.prototype.delete(key),Map.prototype.clear()
设置键值对,返回整个Map结构;读取key对应的键值;是否存在当前Map之中;删除key,返回是否删除成功;清空所有成员,没有返回值。
3.遍历相关内容同Set基本一直
4. WeakMap
WeakMap只接收对象为键名。他主要是为了解决如下问题:
当我们在对象上存放某些数据时,可能会引用其他元素,这样当我们不需要该对象时,如果我们不手动删除这个引用,那么垃圾回收机制将不会释放。
const e1 = document.getElementById('foo');
const e2 = document.getElementById('bar');
const arr = [
[e1, 'foo 元素'],
[e2, 'bar 元素'],
];
而WeakMap里面的键名引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内,也就是说在其他引用清除后,WeakMap中的引用会自动消失。
最典型的应用场合就是DOM节点作为键名。一旦这个DOM节点删除,该状态就会自动消失。
let myWeakmap = new WeakMap();
myWeakmap.set(
document.getElementById('logo'),
{timesClicked: 0})
;
document.getElementById('logo').addEventListener('click', function() {
let logoData = myWeakmap.get(document.getElementById('logo'));
logoData.timesClicked++;
}, false);
7. Proxy
1. Proxy概述
Proxy可以理解成在目标对象之前,设置一层拦截(代理)
var obj = new Proxy({}, {
get: function (target, propKey, receiver) {
console.log(`getting ${propKey}!`); //getting count!
return Reflect.get(target, propKey, receiver);
},
set: function (target, propKey, value, receiver) {
console.log(`setting ${propKey}!`);
return Reflect.set(target, propKey, value, receiver);
}
});
ES6提供了Proxy构造函数,用来生成Proxy实例。
var proxy = new Proxy(target,handler); //target表示拦截的目标对象; handler用来定制拦截行为。
var proxy = new Proxy({} , {
get(target,propKey){
return 35;
}
});
proxy.time // 35
proxy.name // 35
Proxy 实例也可以作为其他对象的原型对象。
var proxy = new Proxy({}, {
get: function(target, propKey) {
return 35;
}
});
let obj = Object.create(proxy);
obj.time // 35
2. proxy实例方法
1. get()
var a = {name:'张三'}
a.name // 张三
a.age // undefined
//设置get拦截器,target表示拦截对象,propKey为属性名,receiver为实例本身
var proxy = new Proxy(a,{
get(target,propKey,receiver){
if(propKey in target){
return target[propKey]
} else {
throw new Error(propKey + 'does not exist')
}
}
})
proxy.getReceiver === proxy // true
proxy.age // 抛出错误
get 方法可以继承
let obj = Object.create(proxy)
obj.age // 抛出错误
如果一个属性不可配置(configurable)且不可写(writable),则 Proxy 不能修改该属性,否则通过 Proxy 对象访问该属性会报错。
const target = Object.defineProperties({}, {
foo: {
value: 123,
writable: false,
configurable: false
},
});
const handler = {
get(target, propKey) {
return 'abc';
}
};
const proxy = new Proxy(target, handler);
proxy.foo
// TypeError: Invariant check failed
2. set()
set()和get()类似,参数多了一个修改的value,下面例子为对象中以_开头的对象为不可使用。
const handler = {
get (target, key) {
invariant(key, 'get');
return target[key];
},
set (target, key, value) {
invariant(key, 'set');
target[key] = value;
return true;
}
};
function invariant (key, action) {
if (key[0] === '_') {
throw new Error(`Invalid attempt to ${action} private "${key}" property`);
}
}
const target = {};
const proxy = new Proxy(target, handler);
proxy._prop
// Error: Invalid attempt to get private "_prop" property
proxy._prop = 'c'
// Error: Invalid attempt to set private "_prop" property
3. this问题
虽然Proxy可以代理目标对象,但是目标对象的内部this关键字会指向Proxy代理。
const target = {
m: function () {
console.log(this === proxy);
}
};
const handler = {};
const proxy = new Proxy(target, handler);
target.m() // false
proxy.m() // true
拦截函数handler的this指向的是handler对象
const handler = {
get: function (target, key, receiver) {
console.log(this === handler);
return 'Hello, ' + key;
},
set: function (target, key, value) {
console.log(this === handler);
target[key] = value;
return true;
}
};
const proxy = new Proxy({}, handler);
proxy.foo
// true
// Hello, foo
proxy.foo = 1
// true