一篇完全搞懂对象,今后不用担忧没对象啦;
本文从对象定义方法,对象属性,Symbol数据类型,遍历几种方法,对象拷贝,vue2.x和vue3.x拦截对象属性方法及代码实现几个方面由浅入深介绍对象
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
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出来的对象vue
Obejct.create(obj,descriptor),obj是对象,describe描述符属性(可选)es6
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.功能:都能实现对象的声明,并可以赋值和取值
2.继承性:内置方法建立的对象继承到__proto__属性上
3.隐藏属性:三种声明方法会默认为内部的每一个成员(属性或方法)生成一些隐藏属性,这些隐藏属性是能够读取和可配置的,属性分类见下面
4.属性读取:Object.getOwnPropertyDescriptor()或getOwnPropertyDescriptor()
5.属性设置:Object.definePropertype或Object.definePropertiessegmentfault
1.数据属性4个特性:
configurable(可配置),enumerable(可枚举),writable(可修改),value(属性值)数组
2.访问器属性2个特性:
get(获取),set(设置)函数
3.内部属性
由JavaScript引擎内部使用的属性;
不能直接访问,可是能够经过对象内置方法间接访问,如:[[Prototype]]能够经过 Object.getPrototypeOf()访问;
内部属性用[[]]包围表示,是一个抽象操做,没有对应字符串类型的属性名,如[[Prototype]].this
1.定义:将一个属性的全部特性编码成一个对象返回
2.描述符的属性有:数据属性和访问器属性
3.使用范围:
做为方法Object.defineProperty, Object.getOwnPropertyDescriptor, Object.create的第二个参数,编码
1.访问对象存在的属性es5
特性名 | 默认值 |
---|---|
value | 对应属性值 |
get | 对应属性值 |
set | undefined |
writable | true |
enumerable | true |
configurable | true |
因此经过上面三种声明方法已存在的属性都是有这些默认描述符
2.访问对象不存在的属性spa
特性名 | 默认值 |
---|---|
value | undefined |
get | undefined |
set | undefined |
writable | false |
enumerable | false |
configurable | false |
get,set与wriable,value是互斥的,若是有交集设置会报错双向绑定
1.定义属性的函数有两个:Object.defineProperty和Object.defineProperties.例如:
Object.defineProperty(obj, propName, desc)
2.在引擎内部,会转换成这样的方法调用:
obj.[[DefineOwnProperty]](propName, desc, true)
1.赋值运算符(=)就是在调用[[Put]].好比:
obj.prop = v;
2.在引擎内部,会转换成这样的方法调用:
obj.[[Put]]("prop", v, isStrictModeOn)
名称 | 含义 | 用法 |
---|---|---|
in | 若是指定的属性在指定的对象或其原型链中,则in 运算符返回true | 'name' in test //true |
hasOwnProperty() | 只判断自身属性 | test.hasOwnProperty('name') //true |
.或[] | 对象或原型链上不存在该属性,则会返回undefined | test.name //"lei" test["name"] //"lei" |
是一种数据类型;
不能new,由于Symbol是一个原始类型的值,不是对象。
Symbol(),能够传参
var s1 = Symbol(); var s2 = Symbol(); s1 === s2 // false // 有参数的状况 var s1 = Symbol("foo"); var s2 = Symbol("foo"); s1 === s2 // false
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()取到该属性
1.定义:在全局中搜索有没有以该参数做为名称的Symbol值,若是有,就返回这个Symbol值,不然就新建并返回一个以该字符串为名称的Symbol值
2.举例:
var s1 = Symbol.for('foo'); var s2 = Symbol.for('foo'); s1 === s2 // true
1.定义:返回一个已登记的Symbol类型值的key
2.举例:
var s1 = Symbol.for("foo"); Symbol.keyFor(s1) // "foo" var s2 = Symbol("foo"); Symbol.keyFor(s2) // undefined
方法 | 特性 |
---|---|
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)能够拿到不可枚举属性
数据模型:
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);
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.注意:
这个是伪深度拷贝,只能拷贝第一层
1.原理:是将对象转化为字符串,而字符串是简单数据类型
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; }
定义:利用对象内置方法,设置属性,进而改变对象的属性值
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只能劫持对象的属性,所以咱们须要对每一个对象的每一个属性进行遍历,若是属性值也是对象那么须要深度遍历,显然能劫持一个完整的对象是更好的选择
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对象没有构造函数
能够监听数组索引赋值,改变数组长度的变化,
是直接监听对象的变化,不用深层遍历
1.defineProterty是es5的标准,proxy是es6的标准;
2.proxy能够监听到数组索引赋值,改变数组长度的变化;
3.proxy是监听对象,不用深层遍历,defineProterty是监听属性;
3.利用defineProterty实现双向数据绑定(vue2.x采用的核心)
请戳,剖析Vue原理&实现双向绑定MVVM4.利用proxy实现双向数据绑定(vue3.x会采用)