JS之对象

3次阅读

共计 6429 个字符,预计需要花费 17 分钟才能阅读完成。

前言
一篇彻底搞懂对象, 从此不用担心没对象啦; 本文从对象定义方法, 对象属性,Symbol 数据类型, 遍历几种方法, 对象拷贝,vue2.x 和 vue3.x 拦截对象属性方法及代码实现几个方面由浅入深介绍对象
1. 对象的声明方法
1.1 字面量
var test2 = {x:123,y:345};
console.log(test2);//{x:123,y:345};
console.log(test2.x);//123
console.log(test2.__proto__.x);//undefined
console.log(test2.__proto__.x === test2.x);//false

1.2 构造函数
var test1 = new Object({x:123,y:345});
console.log(test1);//{x:123,y:345}
console.log(test1.x);//123
console.log(test1.__proto__.x);//undefined
console.log(test1.__proto__.x === test1.x);//false

new 的作用:1. 创了一个新对象;2.this 指向构造函数;3. 构造函数有返回, 会替换 new 出来的对象, 如果没有就是 new 出来的对象
1.3 内置方法
Obejct.create(obj,descriptor),obj 是对象,describe 描述符属性(可选)
let test = Object.create({x:123,y:345});
console.log(test);//{}
console.log(test.x);//123
console.log(test.__proto__.x);//3
console.log(test.__proto__.x === test.x);//true
1.4 三种方法的优缺点
1. 功能: 都能实现对象的声明, 并能够赋值和取值 2. 继承性: 内置方法创建的对象继承到__proto__属性上 3. 隐藏属性: 三种声明方法会默认为内部的每个成员(属性或方法)生成一些隐藏属性,这些隐藏属性是可以读取和可配置的, 属性分类见下面 4. 属性读取:Object.getOwnPropertyDescriptor()或 getOwnPropertyDescriptor()5. 属性设置:Object.definePropertype 或 Object.defineProperties
2. 对象的属性
2.1 属性分类
1. 数据属性 4 个特性:configurable(可配置),enumerable(可枚举),writable(可修改),value(属性值)
2. 访问器属性 2 个特性:get(获取),set(设置)
3. 内部属性由 JavaScript 引擎内部使用的属性; 不能直接访问, 但是可以通过对象内置方法间接访问, 如:[[Prototype]]可以通过 Object.getPrototypeOf()访问; 内部属性用 [[]] 包围表示, 是一个抽象操作, 没有对应字符串类型的属性名, 如[[Prototype]].
2.2 属性描述符
1. 定义: 将一个属性的所有特性编码成一个对象返回 2. 描述符的属性有: 数据属性和访问器属性 3. 使用范围: 作为方法 Object.defineProperty, Object.getOwnPropertyDescriptor, Object.create 的第二个参数,
2.3 属性描述符的默认值
1. 访问对象存在的属性

特性名
默认值

value
对应属性值

get
对应属性值

set
undefined

writable
true

enumerable
true

configurable
true

所以通过上面三种声明方法已存在的属性都是有这些默认描述符 2. 访问对象不存在的属性

特性名
默认值

value
undefined

get
undefined

set
undefined

writable
false

enumerable
false

configurable
false

2.3 描述符属性的使用规则
get,set 与 wriable,value 是互斥的, 如果有交集设置会报错
2.4 属性定义
1. 定义属性的函数有两个:Object.defineProperty 和 Object.defineProperties. 例如:Object.defineProperty(obj, propName, desc)
2. 在引擎内部, 会转换成这样的方法调用:obj.[[DefineOwnProperty]](propName, desc, true)
2.5 属性赋值
1. 赋值运算符 (=) 就是在调用[[Put]]. 比如:obj.prop = v;
2. 在引擎内部, 会转换成这样的方法调用:obj.[[Put]](“prop”, v, isStrictModeOn)
2.6 判断对象的属性

名称
含义
用法

in
如果指定的属性在指定的对象或其原型链中,则 in 运算符返回 true
‘name’ in test //true

hasOwnProperty()
只判断自身属性
test.hasOwnProperty(‘name’) //true

. 或[]
对象或原型链上不存在该属性,则会返回 undefined
test.name //”lei” test[“name”] //”lei”

3.Symbol
3.1 概念
是一种数据类型; 不能 new, 因为 Symbol 是一个原始类型的值,不是对象。
3.2 定义方法
Symbol(), 可以传参
var s1 = Symbol();
var s2 = Symbol();
s1 === s2 // false

// 有参数的情况
var s1 = Symbol(“foo”);
var s2 = Symbol(“foo”);
s1 === s2 // false
3.3 用法
1. 不能与其他类型的值进行运算;2. 作为属性名
let mySymbol = Symbol();

// 第一种写法
var a = {};
a[mySymbol] = ‘Hello!’;

// 第二种写法
var a = {
[mySymbol]: ‘Hello!’
};

// 第三种写法
var a = {};
Object.defineProperty(a, mySymbol, { value: ‘Hello!’});

// 以上写法都得到同样结果
a[mySymbol] // “Hello!”

3. 作为对象属性名时,不能用点运算符, 可以用[]
let a = {};
let name = Symbol();
a.name = ‘lili’;
a[name] = ‘lucy’;
console.log(a.name,a[name]);

4. 遍历不会被 for…in、for…of 和 Object.keys()、Object.getOwnPropertyNames()取到该属性
3.4 Symbol.for
1. 定义: 在全局中搜索有没有以该参数作为名称的 Symbol 值,如果有,就返回这个 Symbol 值,否则就新建并返回一个以该字符串为名称的 Symbol 值 2. 举例:
var s1 = Symbol.for(‘foo’);
var s2 = Symbol.for(‘foo’);
s1 === s2 // true

3.5 Symbol.keyFor
1. 定义: 返回一个已登记的 Symbol 类型值的 key2. 举例:
var s1 = Symbol.for(“foo”);
Symbol.keyFor(s1) // “foo”

var s2 = Symbol(“foo”);
Symbol.keyFor(s2) // undefined

4. 遍历
4.1 一级对象遍历方法

方法
特性

for … in
遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)

Object.keys(obj)
返回一个数组, 包括对象自身的 (不含继承的) 所有可枚举属性(不含 Symbol 属性)

Object.getOwnPropertyNames(obj)
返回一个数组, 包括对象自身的所有可枚举属性(不含 Symbol 属性)

Object.getOwnPropertySymbols(obj)
返回一个数组, 包含对象自身的所有 Symbol 属性

Reflect.ownKeys(obj)
返回一个数组, 包含对象自身的所有 (不枚举、可枚举和 Symbol) 属性

Reflect.enumerate(obj)
返回一个 Iterator 对象, 遍历对象自身的和继承的所有可枚举属性(不含 Symbol 属性)

总结:1. 只有 Object.getOwnPropertySymbols(obj)和 Reflect.ownKeys(obj)可以拿到 Symbol 属性 2. 只有 Reflect.ownKeys(obj)可以拿到不可枚举属性
4.2 多级对象遍历
数据模型:
var treeNodes = [
{
id: 1,
name: ‘1’,
children: [
{
id: 11,
name: ’11’,
children: [
{
id: 111,
name: ‘111’,
children:[]
},
{
id: 112,
name: ‘112’
}
]
},
{
id: 12,
name: ’12’,
children: []
}
],
users: []
},
];
递归:
var parseTreeJson = function(treeNodes){
if (!treeNodes || !treeNodes.length) return;

for (var i = 0, len = treeNodes.length; i < len; i++) {

var childs = treeNodes[i].children;

console.log(treeNodes[i].id);

if(childs && childs.length > 0){
parseTreeJson(childs);
}
}
};

console.log(‘————- 递归实现 ——————‘);
parseTreeJson(treeNodes);
5. 深度拷贝
5.1 Object.assign
1. 定义: 将源对象(source)的所有可枚举属性,复制到目标对象(target)2. 用法:
合并多个对象
var target = {a: 1, b: 1};
var source1 = {b: 2, c: 2};
var source2 = {c: 3};
Object.assign(target, source1, source2);

3. 注意: 这个是伪深度拷贝, 只能拷贝第一层
5.2 JSON.stringify
1. 原理: 是将对象转化为字符串, 而字符串是简单数据类型
5.3 递归拷贝
function deepClone(source){
const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象
for(let keys in source){// 遍历目标
if(source.hasOwnProperty(keys)){
if(source[keys] && typeof source[keys] === ‘object’){// 如果值是对象,就递归一下
targetObj[keys] = source[keys].constructor === Array ? [] : {};
targetObj[keys] = deepClone(source[keys]);
}else{// 如果不是,就直接赋值
targetObj[keys] = source[keys];
}
}
}
return targetObj;
}

6. 数据拦截
定义: 利用对象内置方法, 设置属性, 进而改变对象的属性值
6.1 Object.defineProterty
1.ES5 出来的方法;2. 三个参数: 对象(必填), 属性值(必填), 描述符(可选);3.defineProterty 的描述符属性
数据属性:value,writable,configurable,enumerable
访问器属性:get,set
注: 不能同时设置 value 和 writable, 这两对属性是互斥的

4. 拦截对象的两种情况:
let obj = {name:”,age:”,sex:”},
defaultName = [“ 这是姓名默认值 1 ″,” 这是年龄默认值 1 ″,” 这是性别默认值 1 ”];
Object.keys(obj).forEach(key => {
Object.defineProperty(obj, key, {
get() {
return defaultName;
},
set(value) {
defaultName = value;
}
});
});

console.log(obj.name);
console.log(obj.age);
console.log(obj.sex);
obj.name = “ 这是改变值 1 ″;
console.log(obj.name);
console.log(obj.age);
console.log(obj.sex);

let objOne={},defaultNameOne=” 这是默认值 2 ”;
Object.defineProperty(obj, ‘name’, {
get() {
return defaultNameOne;
},
set(value) {
defaultNameOne = value;
}
});
console.log(objOne.name);
objOne.name = “ 这是改变值 2 ″;
console.log(objOne.name);
5. 拦截数组变化的情况
let a={};
bValue=1;
Object.defineProperty(a,”b”,{
set:function(value){
bValue=value;
console.log(“setted”);
},
get:function(){
return bValue;
}
});
a.b;//1
a.b=[];//setted
a.b=[1,2,3];//setted
a.b[1]=10;// 无输出
a.b.push(4);// 无输出
a.b.length=5;// 无输出
a.b;//[1,10,3,4,undefined];

结论:defineProperty 无法检测数组索引赋值, 改变数组长度的变化;
但是通过数组方法来操作可以检测到

6. 存在的问题
不能监听数组索引赋值和改变长度的变化
必须深层遍历嵌套的对象, 因为 defineProterty 只能劫持对象的属性, 因此我们需要对每个对象的每个属性进行遍历,如果属性值也是对象那么需要深度遍历, 显然能劫持一个完整的对象是更好的选择

6.2 proxy
1.ES6 出来的方法, 实质是对对象做了一个拦截, 并提供了 13 个处理方法 13 个方法详情请戳, 阮一峰的 proxy 介绍
2. 两个参数: 对象和行为函数
let handler = {
get(target, key, receiver) {
console.log(“get”, key);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
console.log(“set”, key, value);
return Reflect.set(target, key, value, receiver);
}
};
let proxy = new Proxy(obj, handler);
proxy.name = “ 李四 ”;
proxy.age = 24;

3. 问题和优点 reflect 对象没有构造函数可以监听数组索引赋值, 改变数组长度的变化, 是直接监听对象的变化, 不用深层遍历
6.3 defineProterty 和 proxy 的对比
1.defineProterty 是 es5 的标准,proxy 是 es6 的标准;
2.proxy 可以监听到数组索引赋值, 改变数组长度的变化;
3.proxy 是监听对象, 不用深层遍历,defineProterty 是监听属性;
3. 利用 defineProterty 实现双向数据绑定 (vue2.x 采用的核心) 请戳, 剖析 Vue 原理 & 实现双向绑定 MVVM4. 利用 proxy 实现双向数据绑定(vue3.x 会采用)

正文完
 0