JS中的Object对象

JS中的Object对象

Posted by SkioFox on March 25, 2019

浅拷贝

深拷贝

  • ES5 常用克隆对象方法

    • 数组和普通对象的克隆
            // 只能实现数组或者普通对象的克隆,不能实现包装对象Number,String,Boolean,以及正则对象RegExp和Date对象的克隆,
            function deepClone(obj){
                // 判断是数组还是对象
                var newObj= obj instanceof Array ? []:{};
                // for循环遍历对象的属性
                for(var item in obj){
                    // 如果对象内部某个属性是对象则递归
                    var temple= typeof obj[item] == 'object' ? deepClone(obj[item]):obj[item];
                    newObj[item] = temple;
                }
                return newObj;
            }
      
    • valueof()函数实现原始类和 包装对象的克隆

      所有对象都有valueOf方法,valueOf方法对于:如果存在任意原始值,它就默认将对象转换为表示它的原始值。对象是复合值,而且大多数对象无法真正表示为一个原始值,因此默认的valueOf()方法简单地返回对象本身,而不是返回一个原始值。数组、函数和正则表达式简单地继承了这个默认方法,调用这些类型的实例的valueOf()方法只是简单返回这个对象本身。

            function baseClone(base){
                return base.valueOf();
            }
            //Number
            var num=new Number(1);
            var newNum=baseClone(num);
            //newNum->1
            //String
            var str=new String('hello');
            var newStr=baseClone(str);
            // newStr->"hello"
            //Boolean
            var bol=new Boolean(true);
            var newBol=baseClone(bol);
            //newBol-> true
      
      
    • Date类型克隆

            Date.prototype.clone=function(){
                return new Date(this.valueOf()); // 日期类定义的valueOf()方法会返回它的一个内部表示:1970年1月1日以来的毫秒数
            }
            var date=new Date('2010');
            var newDate=date.clone();
            // newDate->  Fri Jan 01 2010 08:00:00 GMT+0800
      
    • RegExp对象克隆

            RegExp.prototype.clone = function() {
                var pattern = this.valueOf();
                var flags = '';
                flags += pattern.global ? 'g' : '';
                flags += pattern.ignoreCase ? 'i' : '';
                flags += pattern.multiline ? 'm' : '';
                return new RegExp(pattern.source, flags);
            };
            var reg=new RegExp('/111/');
            var newReg=reg.clone();
            //newReg->  /\/111\//
      

      Object.assign()

      通过复制一个或多个对象来创建一个新的对象。

      Object.create()

      使用指定的原型对象和属性创建一个新对象。

      Object.defineProperty()和Object.defineProperties()

Object.defineProperty()给对象添加一个属性并指定该属性的配置。 Object.defineProperties()给对象添加多个属性并分别指定它们的配置。

  • 在ES5中可以通过Object.defineProperty来实现已有属性的监听

    缺点:

      1. 无法监听数组的变化: 数组的这些方法是无法触发set的:push, pop, shift, unshift,splice, sort, reverse.,vue中能监听是因为对这些方法进行了重写
        
      2. 只能监听属性,而不是监听对象本身,需要对对象的每个属性进行遍历。对于原本不在对象中的属性难以监听。
    
    // Object.defineProperty(obj, prop, descriptor)
    var obj={};
    Object.defineProperty(obj,'data',{
        get:function(){
            return data;
        },
        set:function(newValue){
            data=newValue;
            console.log('set :',newValue);
            //需要触发的渲染函数写在这...
        }
    })

    // 当我们给obj的data赋值的时候,就会触发set 的方法

    obj.data=5;//set: 5

    // 如果要一下子定义多个变量的getter和setter,你可以使用Object.defineProperties(obj,props)
    // obj:要在其上定义属性的对象。props:要定义其可枚举属性或修改的属性描述符的对象。
    var obj = {};
    Object.defineProperties(obj, {
        a: {
            configurable: true, //表示该属性描述符可以被改变(默认为false)
            get: function() {
                console.log('get: ',a)
                return a
            },
            set: function(newValue) {
                a = newValue;
                console.log('set: ',a)
            }
        },
        b: {
            configurable: true,
            get: function() {
                console.log('get: ',b)
                return b;
            },
            set: function(newValue) {
                b = newValue;
                console.log('set: ',b)
            }
        }
    })

    // 脏值检测原理就是比较新值和旧值, 当值真的发生改变时再去更改DOM,目前Angular使用脏值检测
    //  缺点是如果不注意,每次脏值检测会检测大量的数据, 而很多数据是没有检测的必要的,容易影响性能。

    let data = { price: 5, quantity: 2 }
    // 监听属性变化的函数
    let target = null
    // 依赖收集
    class Dep {
        constructor () {
            this.subscribers = []
        }
        depend () {
            if (target && !this.subscribers.includes(target)) {
                this.subscribers.push(target) 
            }
        }
        notify () {
            this.subscribers.forEach(sub => sub())
    }
    }
    // 遍历对象:返回一个包含所有给定对象自身可枚举属性名称的数组。
    Object.keys(data).forEach(key => {
        let internalValue = data[key]

        const dep = new Dep()

        Object.defineProperty(data, key, {
            // 对象方法的简写形式
            get() {
                dep.depend()
                return internalValue
            },
            set(newVal) {
                internalValue = newVal
                dep.notify()
            }
        })
    })
    function watcher(myFun) {
        target = myFun
        target()
        target = null
    }
    watcher(() => {
        data.total = data.price * data.quantity
    })
    console.log("total = " + data.total)
    data.price = 20
    console.log("total = " + data.total)
    data.quantity = 10
    console.log("total = " + data.total)

    // vue中的响应式原理就是如此,遍历所有的响应式的数据,进行依赖收集,响应数据的改变。
  • 在ES6中可以通过Proxy来实现

优点:

1. 可以监听数组变化

2. 监听的是对象本身

3. 有13种拦截方法

4. Proxy允许我们创建一个对象的虚拟代理(替代对象),并为我们提供了在访问或修改原始对象时,可以进行拦截的处理方法(handler),如set()、get()和deleteProperty()等等。
    // 我们可以使用以下方法在data对象上建立一个代理,而不是遍历每个属性来添加getter/setter。

    //  data 是我们准备要创建代理的源对象
    const observedData = new Proxy(data, { 
        get() {
            //  访问源对象属性时调用
        },
        set() {
            //  修改源对象属性时调用
        },
        deleteProperty() {
            //  删除源对象属性时调用
        }
    });
    // 使用Proxy实现响应式
    let data = { price: 5, quantity: 2 }
    let deps = new Map(); // 创建一个Map对象
        // Dep class并不需要改动。单纯使用Proxy替换Object.defineProperty
    class Dep {
        constructor () {
            this.subscribers = []
        }
        depend () {
            if (target && !this.subscribers.includes(target)) {
                this.subscribers.push(target)
            }
        }
        notify () {
            this.subscribers.forEach(sub => sub())
        }
    }
    Object.keys(data).forEach(key => {
        // 为每个属性都设置一个依赖实例并放入deps 中
        deps.set(key, new Dep());
    });
    let data_without_proxy = data; // 保存源对象
    data = new Proxy(data_without_proxy, {
    // 重写数据以在中间创建一个代理
        get(obj, key) {
            deps.get(key).depend(); // <-- 依旧为存储target
            return obj[key]; // 返回原始数据
        },
        set(obj, key, newVal) {
            obj[key] = newVal; // 将原始数据设置为新值
            deps.get(key).notify(); // <-- 依旧为重新运行已存储的target
            return true;
        }
    });
    function watcher(myFun) {
        target = myFun
        target()
        target = null
    }
    watcher(() => {
        total = data.price * data.quantity;
    });
    console.log("total = " + total);
    data.price = 20;
    console.log("total = " + total);
    data.quantity = 10;
    console.log("total = " + total);
  • 使用Proxy在未声明情况下添加新的响应式属性
    let data = { price: 5, quantity: 2 };
    let target = null;
    class Dep {
        constructor() {
            this.subscribers = [];
        }
        depend() {
            if (target && !this.subscribers.includes(target)) {
                this.subscribers.push(target);
            }
        }
        notify() {
            this.subscribers.forEach(sub => sub());
        } 
    }
    // 前边的代码都没变
    let deps = new Map(); // 创建一个Map对象
    Object.keys(data).forEach(key => {
    // 为每个属性都设置一个依赖实例 并放入 deps 中
        deps.set(key, new Dep());
    });
    let data_without_proxy = data; // 保存源对象
    data = new Proxy(data_without_proxy, {
    // 重写数据以在中间创建一个代理
        get(obj, key) {
            deps.get(key).depend(); // <-- 依旧为存储target
            return obj[key]; // 返回原始数据
        },
        set(obj, key, newVal) {
            obj[key] = newVal; // 将原始数据设置为新值
            deps.get(key).notify(); // <-- 依旧为重新运行已存储的targets
            return true;
        }
    });
    // 用来监听具有响应性属性的代码
    function watcher(myFunc) {
        target = myFunc;
        target();
        target = null;
    }
    let total = 0
    watcher(() => {
        total = data.price * data.quantity;
    });
    console.log("total = " + total); 
    data.price = 20;
    console.log("total = " + total);
    data.quantity = 10;
    console.log("total = " + total);
    // 为dep添加一个新属性,存储依赖到Map中
    deps.set('discount', new Dep()) 
    // 添加不存在data中的属性
    data['discount'] = 5;
    let salePrice = 0;
    // 添加监听,会增加target 对其进行监听,其中包括我们新添加的属性
    watcher(() => {
        salePrice = data.price - data.discount;
    });
    console.log("salePrice = " + salePrice); // 15
    data.discount = 7.5 // 此时就会调用我们的监听函数,达到响应式的目的
    console.log("salePrice = " + salePrice); // 12.5

Object.getOwnPropertyDescriptor()

返回对象指定的属性配置。

Object.getPrototypeOf()

返回指定对象的原型对象。 获取一个对象的原型,在chrome中可以通过_proto_的形式,或者在ES6中可以通过Object.getPrototypeOf的形式。

    // 那么Function.proto是什么么?也就是说Function由什么对象继承而来,我们来做如下判别。

    Function.__proto__==Object.prototype //false

    Function.__proto__==Function.prototype//true

    // 我们发现Function的原型也是Function。

avatar

Object.getOwnPropertyNames()

返回一个数组,它包含了指定对象所有的可枚举或不可枚举的属性名。

Object.keys()

Object.keys()返回一个包含所有给定对象自身可枚举属性名称的数组。Object.values() 返回给定对象自身可枚举值的数组。

Object.entries()

返回给定对象自身可枚举属性的 [key, value] 数组。

Object.observe()

Object.observe() 方法用于异步地监视一个对象的修改。当对象属性被修改时,方法的回调函数会提供一个有序的修改流。然而,这个接口已经被废弃并从各浏览器中移除。你可以使用更通用的 Proxy 对象替代。

    // 打印changes 
    var obj = {
    foo: 0,
    bar: 1
    };

    Object.observe(obj, function(changes) {
    console.log(changes);
    });

    obj.baz = 2;
    // [{name: 'baz', object: <obj>, type: 'add'}]

    obj.foo = 'hello';
    // [{name: 'foo', object: <obj>, type: 'update', oldValue: 0}]

    delete obj.baz;
    // [{name: 'baz', object: <obj>, type: 'delete', oldValue: 2}]
    // 数据绑定

    // 一个数据模型
    var user = {
        id: 0,
        name: 'Brendan Eich',
        title: 'Mr.'
    };

    // 创建用户的greeting
    function updateGreeting() {
        user.greeting = 'Hello, ' + user.title + ' ' + user.name + '!';
    }
    updateGreeting();

    Object.observe(user, function(changes) {
        changes.forEach(function(change) {
            // 当name或title属性改变时, 更新greeting
            if (change.name === 'name' || change.name === 'title') {
                updateGreeting();
            }
        }); 
    });

Object.prototype.toString.call()

用于准确判断对象的类型的方法

同样是检测对象obj调用toString方法(关于toString()方法的用法的可以参考toString的详解),obj.toString()的结果和Object.prototype.toString.call(obj)的结果不一样,这是为什么?

这是因为toString为Object的原型方法,而Array 、Function等类型作为Object的实例,都重写了toString方法。不同的对象类型调用toString方法时,根据原型链的知识,调用的是对应的重写之后的toString方法(Function类型返回内容为函数体的字符串,Array类型返回元素组成的字符串…..),而不会去调用Object上原型toString方法(返回对象的具体类型),所以采用obj.toString()不能得到其对象类型,只能将obj转换为字符串类型;因此,在想要得到对象的具体类型时,应该调用Object上原型toString方法。

请阅读MDN