JS之对象(2)

插图

前言

一篇完全搞懂对象,今后不用担忧没对象啦;
本文从对象定义方法,对象属性,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出来的对象vue

1.3 内置方法

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.4 三种方法的优缺点

1.功能:都能实现对象的声明,并可以赋值和取值
2.继承性:内置方法建立的对象继承到__proto__属性上
3.隐藏属性:三种声明方法会默认为内部的每一个成员(属性或方法)生成一些隐藏属性,这些隐藏属性是能够读取和可配置的,属性分类见下面
4.属性读取:Object.getOwnPropertyDescriptor()或getOwnPropertyDescriptor()
5.属性设置:Object.definePropertype或Object.definePropertiessegmentfault

2.对象的属性

2.1 属性分类

1.数据属性4个特性:
configurable(可配置),enumerable(可枚举),writable(可修改),value(属性值)数组

2.访问器属性2个特性:
get(获取),set(设置)函数

3.内部属性
由JavaScript引擎内部使用的属性;
不能直接访问,可是能够经过对象内置方法间接访问,如:[[Prototype]]能够经过 Object.getPrototypeOf()访问;
内部属性用[[]]包围表示,是一个抽象操做,没有对应字符串类型的属性名,如[[Prototype]].this

2.2 属性描述符

1.定义:将一个属性的全部特性编码成一个对象返回
2.描述符的属性有:数据属性和访问器属性
3.使用范围:
做为方法Object.defineProperty, Object.getOwnPropertyDescriptor, Object.create的第二个参数,编码

2.3 属性描述符的默认值

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

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类型值的key
2.举例:

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会采用)

相关文章
相关标签/搜索