对象详解

191 阅读6分钟

Object.prototype.hasOwnProperty()

hasOwnProperty表示是否有自己的属性。这个方法会查找一个对象是否有某个属性,但是不会去查找它的原型链

应用:通过原型链继承该方法判断属性是否是对象自身的

var obj = {
    a: 1,
    fn: function(){
    },
    c:{
        d: 5
    }
};
console.log(obj.hasOwnProperty ('a'));// true
console.log(obj.hasOwnProperty ('fn'));// true
console.log(obj.hasOwnProperty ('c'));// true
console.log(obj.c.hasOwnProperty ('d'));// true
console.log(obj.has0wnProperty ('d'));// false,obj对象没有d属性

var str = new String();
// split方法是String这个对象的方法,str对象本身是没有这个split这个属性的
console.log(str.hasOwnProperty ('split'));// false
console.log(String.prototype.hasOwnProperty('split'));// true

Object.defineProperty()

  • 方法:
    直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

  • 语法 :Object.defineProperty(obj, prop, descriptor)

  • 参数
    obj:要定义属性的对象。
    prop:要定义或修改的属性的名称或 Symbol。
    descriptor:要定义或修改的属性描述符对象。

  • 返回值
    被传递给函数的对象。

descriptor 属性描述符对象

  • 数据属性
    1.configurable:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性,默认值为false。
    2.enumerable:表示能否通过for in循环访问属性,默认值为false
    3.writable:表示能否修改属性的值。默认值为false。
    4.value:包含这个属性的数据值。默认值为undefined。

    let obj = {}
    object.defineProperty(obj,'name', {
        value: 'jack',
        configurable: true//是否可以删除属性
        writable: false,//只读,不能修改name属性
        enumerable: true,//for-in遍历属性
    })
    
    console.log(obj)
    // delete obj.name//删除
    obj.name = 'rose'//修改console.log(obj
    
    //遍历
    for (const key in obj){
        console.log(key, obj[key])
    }
    
  • 访问器属性
    1.get:在读取属性时调用的函数,默认值是undefined
    2.set:在写入属性的时候调用的函数,默认值是undefined

    <script>
        let obj = {}
        object.defineProperty(obj, 'age', {
            get() {
                console.log('get');
                return 18
            },
            set(newvalue) {
            console.log('set:', newValue);},
        })
    </script>
    

作用

  • 将对象属性绑定到另一个对象上

    <script>
        let obj = {
            name: "jack ", age: 18,
        }
        let vm = {0}
        //遍历obj对象key,将obj对象属性绑定到vm对象上
        object.keys(obj).forEach(key => {
            object.defineProperty(vm,key,{
                get() {
                    return obj[key]
                },
                set(newValue) {
                    if(obj[key] === newValue){
                        return
                    }
                    obj[key] = newValue
                },
            })
        }
    </script>
    
    =========================================
    let obj = {
        name: "jack ',age: 18,
    }
      let vm = {}
    //遍历obj对象key,将obj对象属性绑定到vm对象上
    for(const key in obj){
        object.defineProperty(vm, key,{
            get() {
                return obj[key]
            },
            set(newValue) {
                if(obj[key] === newValue){
                    return
                }
                obj[key] = newValue),
            }
        })
    }
    
  • 数据劫持,监听对象数据变化,实现数据变化自动更新界面

      <body>
          <div id="app">
              <h2</h2>
          </div>
          
          <script>
              //数据message动态显示到h2标签上,message变化h2的内容自动更新
              const data = {
                  message: 'hello',
              }
              const vm = {}
              
              //1.遍历数据对象上所有属性
              for (let key in data) {
                  //2.使用object.defineProperty()绑定属性到vm对象上,
                  //vm对象上属性值变化自动触发get,set方法
                  object.defineProperty(vm,key, {
                      geto {
                          return data[key]
                      },
                      //value属性的值
                      set(value){
                          if (data[key] === value) return
                          document.queryselector('h2').innerHTML = value
                      },
                  })
              }
          </script>
      </body>
    

Proxy

  • 代理是什么?
    简单理解就是我们不直接对对象、函数或者数组进行操作,而是把它挂载到Proxy(代理)上,直接对代理的做一系列操作。我们去买房,房产中介就相当于我们的代理,我们不需要直接向卖家沟通。

  • JS代理Proxy
    代理是目标对象的抽象。目标对象既可以直接被操作,也可以通过代理来操作。 但直接操作会绕过代理施予的行为。 首先就是空代理,就是什么也不做,在代理对象上执行的所有操作都会无障碍地传播到目标对象。
    代理是使用 Proxy 构造函数创建的。这个构造函数接收两个参数:目标对象和处理程序对象。缺少其中任何一个参数都会抛出 TypeError。

    function test1() {
        //目标对象
        const obj = {
            name: 'jack ',
            age: 18,
        }
        //处理程序对象
        const handler = 0
        //代理对象
        const proxy = new Proxy(obj, handler)
        
        //代理访问对象
        console.log( 'obj.name: ', obj.name, ' proxy.name : ', proxy.name)
        //obj.name : jack proxy . name : jack
        //代理改变对象
        proxy.name = 'rose'
        console.log( 'obj.name : ', obj.name, ' proxy.name : ', proxy.name)
        //obj.name : rose proxy.name : rose
    }
    
  • 捕获器(trap)

使用代理的主要目的是可以定义捕获器(trap)。捕获器就是在处理程序对象中定义的基本操作的“拦截器”。每个处理程序对象可以包含零个或多个捕获器,每个捕获器都对应一种基本操作,可以直接或间接在代理对象上调用。每次在代理对象上调用这些基本操作时,代理可以在这些操作传播到目标对象之前先调用捕获器函数,从而拦截并修改相应的行为

1.捕获器一共13种

apply 、construct、defineProperty、deleteProperty、get、getOwnPropertyDescriptor、getPrototypeOf、has、isExtensible、ownKeys、preventExtensions、set、setPrototypeOf

2.get()

const target = {
    foo:'bar'
}
const handler = {
        get() {
            return 'handler override';
        }
    }

    const proxy = new Proxy(target, handler);

    console.log(target.foo) //bar
    console.log(proxy.foo) //handler override
    
    ==========================================
    function test3() {
        const obj = {
            name: 'jack',
            age: 18,
        }
        const handler = {
            get: function (target, property, receiver) {
                console.log('get >>> target :', target)
            },
        }
    }

3.srt()

让proxy数组只接受数字,当然如果是数字要返回true,不然只要返回false就会触发TypeError

let num = [];

const handler = {
    set(target,property, value) {
        if (typeof value == 'number' ) {
            target[property] = value;
            console.log ('insert success');
            return true;
       }else {
            console.log ('this is not a number! ' );
            return false;
        }
    }
}

let proxy = new Proxy(num, handler);

proxy.push(1);
proxy.push(2);

console:log(proxy, length); //2

proxy.push('test'); //TypeError: 'set' om proxy: trap

4.ownKeys()

let user = {
    name:'Sonic' ,
    age: 18,
    _pwd :    '123'
};

let handler = {
    ownkeys (target) {
        return Object.keys(target).filter ( key => ! key. startswith( '_' ));
    }
}
let proxy = new Proxy(user,handler);
for (let key in proxy){
    console.log ( key);
}

//name
// age

console.log(0bject.keys(proxy));// [ 'name ', 'age' ]
console.log(0bject.values(proxy));// [ 'Sonic' , 18 J

5.deleteProperty()

有一个普遍的约定,即以下划线 _ 开头的属性和方法是内部的。不应从对象外部访问它们。

et user = {
    name: 'Sonic',
    age: 15,
    _pwd: '123456'
};
 
let handler = {
    get(target, prop) {
        if (prop.startsWith('_')) {
            throw new Error('Access defined');
        }
 
        let value = target[prop];
        return (typeof value === 'function') ? value.bind(target) : value;
    },
 
    set(target, prop, val) {
        if (prop.startsWith('_')) {
            throw new Error('Access defined');
        } else {
            target[prop] = val;
            return true;
        }
    },
 
    deleteProperty(target, prop) {
        if (prop.startsWith('_')) {
            throw new Error('Access defined');
        } else {
            delete target[prop];
            return true;
        }
    },
 
    ownKeys(target) {
        return Object.keys(target).filter(key => !key.startsWith('_'));
    }
};
 
let proxy = new Proxy(user, handler);
 
try {
    console.log(proxy._pwd);
} catch (e) {
    console.log(e.message);
};
 
try {
    proxy._pwd = '456789';
} catch (e) {
    console.log(e.message);
};

try {
    delete proxy._pwd;
} catch (e) {
    console.log(e.message);
};

for (let prop in proxy) console.log(prop);
  • 应用vue3.x数据劫持

      <h2 id="app">数据劫持</h2>
      <script>
          //模拟vue中的data 选项let data = {
              msg: "hello",
              count: e,
          }
          //模拟 vue实例
          let vm = new Proxy(data,{
              //当访问vm的成员会执行
              get(target, key){
                  console.log( 'get, key: ", key,target [key])
                  return target[key]
              }
              //当设置vm的成员会执行
              set(target,key,newValue) {
                  console.log( 'set, key: ", key,newValue)
                  if (target[key] === newValue) {
                      return
                  }
                  target[key] = newValue
                  document.querySelector('#app').textContent = target[key]
              },
          })
          // data = vm
          //测试
          vm.msg ='Hello world'
          console.log(vm.msg)
      </script>
    

对象代理

const name = ititle: "Joker"};
const proxy = new Proxy(name,{
    get(obj, property) {
        return obj[property] ;
    },
    set(obj, property, value) {
        obj[property] = value;
        return true;
    }
});
console.log(proxy.title); //Joker
proxy.title = "newJoker" ;
console.log(proxy.title) // newJoker

函数代理

function factorial(num) {
    return num === 1 ? 1 : num* factorial(num - 1)
}

let proxy = new Proxy(factorial,{
    apply(func, obj, args) {
        console.log (func.apply(this,args)) ; //120
    }
});
proxy(5)

数组使用代理

let arr = [iname: "Joker" , age: 19},{name: "newJoker" , age: 20}];

let proxy = new Proxy(arr, {
    get(array,key) {
        console.log(array) ;
        console.log(array [ key].name) ;
    }
});
proxy[0];
proxy[1];

深拷贝与浅拷贝

拷贝 - 复制obj对象得到一个全新的对象

  1. 浅拷贝 – 复制的obj对象只复制一层,如果对象属性值是对象则不能复制

image.png

  1. 深拷贝 – 完全复制的obj对象,如果对象属性值是对象一起复制得到全新对象

image.png

实现方式

  1. JSON.parse(JSON.stringify(obj))
    缺点: 数据类型是Function或数据值为undefined无法拷贝

     const obj = {
         name: "jack",age: 18,
         hobby: iswiming: "游泳"},
         arr: [{score: 98 }],
         say: () =>{}, //Function无法拷贝
         number: undefined, // undefined无法拷贝
     }
    
  2. Object.assign(obj)或展开运算符{…obj}
    缺点:只能拷贝一层,如果属性值是对象,无法拷贝

     const obj = {
         name: "jack",
         age: 18,
         hobby: iswiming:"游泳"},// object无法拷贝
         arr: [{score: 98}], // Array无法拷贝
         say: () => {}, //Function无法拷贝
         number: undefined, //undefined
     }
    
  3. 递归 cloneDeep()

     const cloneDeep = data => {
         const newData = Array.isArray(data) ? [] : {}
         for (let key in datay {
             if(data[key] && typeof data[key] === 'object') {
                 newData[key] = cloneDeep(data[key])
             } else {
                 newData[key] = data[ key]
             }
         }
         return newData
     }
     //test递归-拷贝
     const testcloneDeep = () => {
         const obj = {
             name: "jack",
             age: 18,
             hobby: {swiming:"游泳"},} // object
             arr: [{ score: 98 }],
             say: () => {}, //Function
             number: undefined, //undefined
             students: [
                 {
                     student: {
                         num: {
                             id: 1,
                         },
                     },
                 }.
             },
         }
         const cloneobj = cloneDeep(obj)
         cloneobj.name= "rose"
         cloneobj.age = 20
         cloneobj.hobby.swiming = "不会游泳"
         cloneobj.arr[0].score= 100
         cloneobj.students[0].student.num.id = 100
         console.log('obj:', obj, "\n cloneobj :', cloneobj)
         console.log(cloneobj.students[0].student.num.id === obj.students[0].student.num.id)