Object.defineProperty与 Proxy区别

  1. 所有属性监听
  • Object.defineProperty无法一次性监听对象所有属性,必须遍历或者递归来实现
  • Proxy的实现就不需要遍历
    • Proxy 的get方法用于拦截某个属性的读取操作,可以接收三个参数,依次为目标、属性名和Proxy实例本身,其中最后一个参数为可选参数
    • set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和Proxy实本身,其中最后一个为可选参数
  1. 新增属性监听
  • Object.defineProPerty无法监听新增属性,如果需要监听新增属性,需要手动再做一次监听,在Vue中想动态监听属性,一般用Vue.set(对象实例, “新增对象属性”)这种形式来添加

  • Proxy可以监听新增属性

  1. 数组操作
  • Object.defineProperty 无法响应数组操作

    • 可以监听数组变化,但无法对新增数组变化进行监听,因此Mobk中为了监听数组变化,默认将数组长度设置为1000,监听0-999的属性变化

    • 如果想要监听push、shift、pop、unshift等方法,该怎么做?Vue和Mobx中都是通过重写原型的方法实现的:在定义变量的时候,判断是否是数组,如果是数组,那么就修改它的proto,将其指向subArrProto,从而实现重写原型链。

      vue源码:

      响应式原理

场景:

  • 利用Proxy set get 应用proxy表单验证
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//验证规则
const validators = {
name: {
validate(value) {
return value.length > 6;
}
message:'用户名长度不能小于六'
},
password: {
validate(value) {
return value.length > 10;
},
message: '密码长度不能小于十'
},
moblie: {
validate(value) {
return /^1(3|5|기8|9)[0-9]{9}$/.test(value);
},
message:'手机号格式错误'
}
}

//验证方法
function validator(obj, validators) {
return new Proxy(obj, {
set(target, key, value) {
const validator = validators[key]
if (!validator) {
target[key] = value;
}else if (validator.validate(value)) {
target[key] = value;
}else { alert (validator.message "");}
}
}
}
let form = {};
form = validator(form, validators);
form.name = '666'; //用户名长度不能小于六
form.password = '113123123123123';
  • get 用来拦截私有属性的读取, 用_ 开头的属性是私有属性, 禁止私有属性读取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const person ={
name: 'tom',
age: 20,
_sex: 'male'
}
const proxy = new Proxy(person, {
get(target, prop) {
if (prop[0] === '_') {
throw new Error(`${prop} is private attribute`);
}
return target[prop]
}
})
proxy.name; //tom
proxy._sex; // _sex is private attribute
  • 等等还有其他Proxy的方法使用 …….(get/ set/ apply/ construct/ has/ delete/ delete)
  • !!! Proxy 可以提高开发效率和代码质量,但在使用过程中需要注意性能优化、避免循环引用导致栈溢出、合理使用拦截器和兼容性等方面的细节。

展开运算符

  • […arr]

  • 支持展开运算符是要有Symbol.iterator

    arr[Symbol.iterator]

面试题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//我们能否以某种方式为下面的语句使用展开运算而不导致类型错误 ?
//错误代码示例
let obj = {x: 1, y: 2, z: 3}
console.log([...obj]) //报错 根本原因是因为对象是不具备迭代器属性的数据结构

//正确代码
obj[Symbol.iterator] = function(){
return {
next: function() {
let objArr = Reflect.ownKeys(obj)
if(this.index < objArr.length -1){
let key = objArr[this.index]
this.index++
return{
value: obj[key]
}
}else{
return {done: true}
}
},
index: 0
}
}
console.log([...obj]) // [1, 2, 3]

// 也可以在Object 构造函数的原型上添加,为所有对象提供迭代器
Object.prototype[Symbol.iterator] = function(){

}

//ES7 也提出了新的写法
//console.log({...objj}) 会涉及到对象的拷贝操作,相当于对obj进行了浅拷贝
console.log({...obj}) // {x: 1, y: 2, z: 3}


  • 对象转数组案例
  1. 使用 Object.keys() 方法获取对象的所有键,然后使用 map() 方法将每个键对应的值存入新数组中。
1
2
3
const obj = { a: 1, b: 2, c: 3 };
const arr = Object.keys(obj).map(key => obj[key]);
console.log(arr); // [1, 2, 3]
  1. 使用 Object.values() 方法获取对象的所有值,直接将值存入新数组中。
1
2
3
const obj = { a: 1, b: 2, c: 3 };
const arr = Object.values(obj);
console.log(arr); // [1, 2, 3]
  1. 使用 Object.entries() 方法获取对象的键值对数组,然后对每个键值对进行处理。
1
2
3
const obj = { a: 1, b: 2, c: 3 };
const arr = Object.entries(obj).map(([key, value]) => value);
console.log(arr); // [1, 2, 3]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#### 高效运算符  '?.' 和 '??' 和 '??='  的区别

* 可选链运算符(?.)

可选链运算符, 允许读取位于连接对象深处的属性值,而不必明确验证链式中的每个引用是否有效。

使用场景:当我们不确定一个对象是否存在时,调用对象属性,可以使用该操作符

* 空值合并运算符(??)

空值合并运算符,是一个逻辑运算符,当左侧的操作为null或undefined时,返回其右侧操作数,否则返回左侧操作数

有点类似于 ||(逻辑或) ,与 || 区别在于 ??. 对0和 " 会判断为真

* 逻辑空赋值(??=)

逻辑空赋值运算符(X ?? = Y) 仅在X是空值(null 或undefined) 时对其赋值。

使用场景:当你需要通过if判断某个变量不存在时,才需要为该变量赋值时使用

#### 原型和原型链

1. **原型(Prototype)**:每个对象都有一个原型对象,可以通过`__proto__`属性来访问。原型对象可以包含共享的属性和方法,可以被对象实例共享。
2. **原型链(Prototype Chain)**:当访问对象的属性或方法时,如果对象本身没有该属性或方法,JavaScript引擎会顺着原型链向上查找,直到找到对应的属性或方法或者到达原型链的顶端。

​~~~ js
// 定义了一个构造函数Person,并在其原型对象上定义了一个方法greet。创建了一个Person对象实例person1,并演示了访问属性和方法以及原型链的关系
function Person(name, age) {
this.name = name;
this.age = age;
}

// 在Person的原型对象上定义一个方法
Person.prototype.greet = function() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}

// 创建一个Person对象实例
let person1 = new Person('Alice', 30);

// 访问实例对象的属性和方法
console.log(person1.name); // Output: Alice
person1.greet(); // Output: Hello, my name is Alice and I am 30 years old.

// 演示原型链 实例的__proto__ === 其构造函数的prototype
console.log(person1.__proto__ === Person.prototype); // Output: true
// 构造函数的原型Object
console.log(Person.prototype.__proto__ === Object.prototype); // Output: true
// Object的原型为null
console.log(Object.prototype.__proto__); // Output: null
  • 其他
    • 继承:通过原型链,我们可以实现对象之间的继承关系,子对象可以继承父对象的属性和方法。可以尝试创建一个新的构造函数,让它继承自Person构造函数,并添加新的属性或方法。
    • 原型修改:可以尝试修改Person构造函数的原型对象上的方法,然后看看实例对象是否能够访问到修改后的方法。
    • 原型链终止:在原型链的顶端是Object.prototype,它的原型是null。可以思考一下为什么原型链的顶端是Object.prototype,而不是其他对象。