前言一篇彻底搞懂对象,从此不用担心没对象啦;本文从对象定义方法,对象属性,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);//123console.log(test2.proto.x);//undefinedconsole.log(test2.proto.x === test2.x);//false1.2 构造函数var test1 = new Object({x:123,y:345});console.log(test1);//{x:123,y:345}console.log(test1.x);//123console.log(test1.proto.x);//undefinedconsole.log(test1.proto.x === test1.x);//falsenew的作用: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);//123console.log(test.proto.x);//3console.log(test.proto.x === test.x);//true1.4 三种方法的优缺点1.功能:都能实现对象的声明,并能够赋值和取值2.继承性:内置方法创建的对象继承到__proto__属性上3.隐藏属性:三种声明方法会默认为内部的每个成员(属性或方法)生成一些隐藏属性,这些隐藏属性是可以读取和可配置的,属性分类见下面4.属性读取:Object.getOwnPropertyDescriptor()或getOwnPropertyDescriptor()5.属性设置:Object.definePropertype或Object.defineProperties2.对象的属性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对应属性值setundefinedwritabletrueenumerabletrueconfigurabletrue所以通过上面三种声明方法已存在的属性都是有这些默认描述符2.访问对象不存在的属性特性名默认值valueundefinedgetundefinedsetundefinedwritablefalseenumerablefalseconfigurablefalse2.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 //truehasOwnProperty()只判断自身属性test.hasOwnProperty(’name’) //true.或[]对象或原型链上不存在该属性,则会返回undefinedtest.name //“lei” test[“name”] //“lei"3.Symbol3.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 // false3.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.for1.定义:在全局中搜索有没有以该参数作为名称的Symbol值,如果有,就返回这个Symbol值,否则就新建并返回一个以该字符串为名称的Symbol值2.举例:var s1 = Symbol.for(‘foo’);var s2 = Symbol.for(‘foo’);s1 === s2 // true3.5 Symbol.keyFor1.定义:返回一个已登记的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.assign1.定义:将源对象(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.stringify1.原理:是将对象转化为字符串,而字符串是简单数据类型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.defineProterty1.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;//1a.b=[];//setteda.b=[1,2,3];//setteda.b[1]=10;//无输出a.b.push(4);//无输出a.b.length=5;//无输出a.b;//[1,10,3,4,undefined];结论:defineProperty无法检测数组索引赋值,改变数组长度的变化; 但是通过数组方法来操作可以检测到6.存在的问题不能监听数组索引赋值和改变长度的变化必须深层遍历嵌套的对象,因为defineProterty只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历,如果属性值也是对象那么需要深度遍历,显然能劫持一个完整的对象是更好的选择6.2 proxy1.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会采用)
JS之对象
February 26, 2019 · 2 min · jiezi