• JS基础学习——对象


    JS基础学习——对象

    什么是对象

    对象object是JS的一种基本数据类型,除此之外还包括的基本数据类型有string、number、boolean、null、undefined。与其他数据类型不同的是,对象是一种复合值,由多个键值对组成,这些键值对也可以看成对象的属性集合,键为属性名,值为属性值(任意数据类型)。

    object又可以分成多种子类型,称为JS的内置对象,包括String、Number、Boolean、Function、Array、Data、RegExp(regular expressions)、Error。这些内置对象其实都是构造函数,也可通过new关键字创建新的对应对象。

    可以看到内置对象中的String、Number、Boolean在基本数据类型中都存在同名类型,它们是不一样的,但也存在联系。

    比如说string类型只是一串原始字符串,我们无法操作它无法改变它,但是String对象除了包含存储值的value属性外,还有很多方便实用的方法,比如检查字符串、访问字符串局部。幸运的是,JS提供了对象自动转换的功能,string类型可以直接调用String方法,如code 1所示,好像是string类型自动转成String对象一样,但其实JS引擎只是临时根据string类型创建类一个同值的String对象,最终执行方法的是这个新创建的String对象,所以code 1在调用String方法后再查看strPrimitive对象,显示的还是string。number和Numbe、boolean和Boolean之间存在类似的转换关系。

    /*-----------code 1----------*/
    var strPrimitive = "I am a string";
    console.log( strPrimitive.length ); // 13
    console.log( strPrimitive.charAt( 3 ) ); // "m"
    console.log(typeof strPrimitive);//string
    

    创建对象

    JS有三种创建对象的语法,包括new构造函数、对象字面量、create函数构造函数。

    new构造函数

    使用构造函数调用的方法创建对象,构造函数可以是Object()或是其他对象子类型构造函数,如String(),或是自定义的构造函数。

    /*-----------code 2----------*/
    var person = new Object();
    person.name = 'bai'
    person.age = 29;
    
    var person2 = new String('person.name = bai');
    
    function Person()
    {
    	this.name = 'bai'
    	this.age = 29;
    }
    var person3 = new Person();
    

    对于Object构造函数,code 2中是它的无参构造形式;当构造函数中传入的是原始类型的值,则返回对象是该值的包装对象,code 2中的person2创建方式等价于code 3;当传入参数为对象时,则直接返回这个对象,如code 4所示;当传入参数为null或是undefined时,则创建一个空对象。

    /*-----------code 3----------*/
    var person2 = new Object('person.name = bai');
    
    /*-----------code 4----------*/	
    var person = new Object({name:'bai',age:29});
    
    var o1 = {a: 1};
    var o2 = new Object(o1);
    

    对象字面量

    对象字面量较构造函数更为简单,是大多原生对象的创建方式,它是若干键值对构成的映射表,键值之间用冒号隔开,键值对之间用逗号隔开,如code 5所示。

    /*-----------code 5----------*/
    var person = {
        name : 'bai',
        age : 29,
        5 : true
    };
    

    create函数

    ES5引入了一个Object.create()方法,用于创建对象,第一个参数是继承对象,第二个参数是新增属性的描述。当第二个参数不使用时,create就是用来创建一个新方法,当第二个参数使用时,则可以实现对原型对象的继承和扩展。

    /*-----------code 6----------*/
    var o1 = Object.create({z:3},{
      x:{value:1,writable: false,enumerable:true,configurable:true},
      y:{value:2,writable: false,enumerable:true,configurable:true}
    }); 
    
    var o2 = Object.create(parents,{t:{value:2},k:{value:3}});//第二个参数必须以属性描述的形式写,不能写t:2,k:3;
    

    Object.create(null)会创建一个没有任何继承的对象,所以这个对象不能调用任何基础方法,比如toString()或valueOf()。

    对象的属性访问

    JS有两种对象属性访问形式:myObject.a;myObject['a'];

    这两种形式的主要区别是,myObject.a;格式对属性名有格式要求,属性名必须满足标识符的命名规范要求,而[]的形式不需要,适用性更广,比如一个名为‘hello word’的属性,就只能以[]的形式访问。同时[]也可以接收一个字符串变量,比如myobject[a],其中a是一个变量名,这样我们就能以编程的方式动态得到属性名了。因为对象属性只能是string类型,所以任何不适string类型的变量传入[]都会自动转换成string。

    用[]访问对象属性时,特别注意数组对象,因为数组对象根据索引值访问数组内容时,用的也是[],容易出现错误,所以对象的属性名最好不要用数字直接命名。

    ES6还增加了可计算的属性名,即[]里可以包含运算符,如code 7所示。

    /*-----------code 7----------*/
    var prefix = "foo";
    var myObject = {
    [prefix + "bar"]: "hello",
    [prefix + "baz"]: "world"
    };
    myObject["foobar"]; // hello
    myObject["foobaz"]; // world
    

    当访问对象属性时,引擎实际上会调用的内部缺省值[[Get]]操作([[Put]]用于设置值),它不仅会在对象上查找属性,而且还会遍历对象的原型链,直到找到属性,但如果找不到该属性,[[Get]]操作会返回undefined,而不是报错。

    属性描述符

    对象属性描述符的类型有两种:数据描述符和访问描述符。

    数据描述符

    【1】 writable:表示属性value值是否可修改,默认为true。当writable:false时,在非严格模式下,任何修改操作将会失效,在严格模式下会报TypeError;

    【2】 configurable:表示数据描述符是否可修改,以及属性是否可删除(delete myObject.property),默认为true。当configurable:false时,除了writable:true可以修改,writable:false、configurable、enumerable都不可修改,修改的话会报TypeError,同时删除对象delete myObject.property在非严格模式下会返回false,在严格模式下会报TypeError;

    【3】enumerable:表示属性是否可枚举,默认为true。当enumerable:false时,Object.keys( myObject );返回的对象属性列表中将不出现该属性,属性也不会出现在for-in循环中。可以用myObject.propertyIsEnumerable(property);方法查看对象该属性的可没举性。

    【4】 value:属性的值,可以为任意类型的数据,当数据为Function类型时,该属性称为对象的方法,默认值为undefined。[[Get]]和[[Put]]操作的就是这个属性。

    访问描述符

    【5】get:访问属性值的时候自动会调用的方法,默认为undefined。get属性定义之后将覆盖属性的[[Get]]操作,因此属性的值不再由value值决定,而是由get方法决定。

    【6】set:设置属性值时自动调用的方法,默认为undefined。get属性定义之后将覆盖属性的[[Put]]操作,且writable:false将失效,属性值永远可修改。

    注意:get和set方法只要定义了其中一个,默认的[[Get]]和[[Put]]操作就会同时失效,所以如果只定义了get方法,就无法对属性值进行修改了,如果定义了set方法,访问到的属性值都只能为undefined。

    查询或设置属性描述符的方法

    【1】Object.defineProperty(o,name,desc):创建或修改属性的描述符,需要注意,如果用这个方法直接创建一个不存在的属性,则描述符的默认值为false。如code 9所示。

    /*-----------code 9----------*/
    var obj = {};
    //{a:1}
    console.log(Object.defineProperty(obj,'a',{
            value:1,
            writable: true
        }));
    
    //由于没有配置enumerable和configurable,所以它们的值为false
    //{value: 1, writable: true, enumerable: false, configurable: false}
    console.log(Object.getOwnPropertyDescriptor(obj,'a'));	
    

    【2】Object.defineProperty(o,descriptors):创建或修改对象的多个属性的描述符,如code 10所示。

    /*-----------code 10----------*/
    var obj = {
        a:1
    };
    //{a: 1, b: 2}
    console.log(Object.defineProperties(obj,{
            a:{writable:false},
            b:{value:2}
        }));
    
    //{value: 1, writable: false, enumerable: true, configurable: true}
    console.log(Object.getOwnPropertyDescriptor(obj,'a'));
    //{value: 2, writable: false, enumerable: false, configurable: false}
    console.log(Object.getOwnPropertyDescriptor(obj,'b'));
    

    【3】Object.create(proto,descriptors):使用指定的原型和属性来创建一个对象,如code 11所示。

    var o = Object.create(Object.prototype,{
        a:{writable: false,value:1,enumerable:true}
    });
    //{value: 1, writable: false, enumerable: true, configurable: true}
    console.log(Object.getOwnPropertyDescriptor(obj,'a'));
    

    【4】Object.getOwnPropertyDescriptor(myObject,property):可以查询指定属性的描述符。如code 8所示。

    /*-----------code 8----------*/
    var obj = {a:1};
    //Object {value: 1, writable: true, enumerable: true, configurable: true}
    console.log(Object.getOwnPropertyDescriptor(obj,'a'));
    //undefined
    console.log(Object.getOwnPropertyDescriptor(obj,'b'));
    

    【5】myObject.hasownproperty(property):判断对象是否拥有指定属性名的属性,不会查找原型链,因为对象继承的是Object的方法,而有的对象没有链接到Object对象上,无法执行hasownproperty方法,因此Object.hasownproperty.call(myObject,property)的写法更为健壮。如code 9所示。

    【6】in关键词:判断对象是否拥有指定属性名的属性,当对象没有时,它会在对象的原型链中继续查找。如code 9所示。

    /*-----------code 9----------*/
    var myObject = {
    a: 2
    };
    ("a" in myObject); // true
    ("b" in myObject); // false
    myObject.hasOwnProperty( "a" ); // true
    myObject.hasOwnProperty( "b" ); // false
    Object.hasOwnProperty.call(myObject,"b")//false
    

    【7】for...in循环:遍历对象的所有可枚举的属性名,但如果对象正好是数组,则会先遍历数组中的所有值,然后遍历数组对象可枚举的属性名,如code 10所示。如果你指向访问可枚举的属性名,则利用Object.keys(myObject)方法,如code 11所示,如果想访问所有属性名,则利用Object.getOwnPropertyNames(myObject)方法,如code 12所示。

    /*-----------code 10----------*/
    var myArray = [1, 2, 3];
    myArray.a = 2;
    
    // 1 2 3 a
    for(var t in myArray){
    	console.log(t);
    }
    
    var keysOfArray = object.keys(myArray);
    for(var key in keysOfArray){
    	console.log(t);
    }
    

    对象拷贝

    对象拷贝有浅拷贝和深拷贝两种,浅拷贝中对象包含的对象属性只是引用拷贝,因此浅拷贝中原对象和拷贝对象的对象属性是指向相同内存地址的,深拷贝中对象的对象属性是进行值拷贝的,所以拷贝对象和原对象的修改不会相互发生影响。

    而对于非对象的基本类型,发生的拷贝都是值拷贝,拷贝对象和元对象之间的修改都不会相互影响。

    浅拷贝的方法

    【1】for循环拷贝属性引用,如code 11所示。

    /*-----------code 11----------*/
    function simpleClone(obj){
        if(typeof obj != 'object'){
            return false;
        }
        var cloneObj = {};
        for(var i in obj){
            cloneObj[i] = obj[i];
        }
        return cloneObj;
    }
    
    var obj1={a:1,b:2,c:[1,2,3]};
    var obj2=simpleClone(obj1);
    console.log(obj1.c); //[1,2,3]
    console.log(obj2.c); //[1,2,3]
    obj2.c.push(4);
    obj2.a = 5;
    console.log(obj2.c); //[1,2,3,4]
    console.log(obj1.c); //[1,2,3,4]
    console.log(obj1.a); //1;
    

    【2】使用属性描述符,如code 12所示。

    /*-----------code 12----------*/
    function simpleClone2(orig){
        var copy = Object.create(Object.getPrototypeOf(orig));//create创建的是一个空对象,如果直接用orig的话,copy将是继承orig,得到的结果和复制目标不一致。
        Object.getOwnPropertyNames(orig).forEach(function(propKey){
            var desc = Object.getOwnPropertyDescriptor(orig,propKey);//得到属性值,prokey是属性名
            Object.defineProperty(copy,propKey,desc);
        });
        return copy;
    }
    
    var obj1={a:1,b:2,c:[1,2,3]};
    var obj2=simpleClone1(obj1);
    console.log(obj1.c); //[1,2,3]
    console.log(obj2.c); //[1,2,3]
    obj2.c.push(4);
    console.log(obj2.c); //[1,2,3,4]
    console.log(obj1.c); //[1,2,3,4]
    

    【3】Object.assign(decObj,srcObj);assign方法不能赋值属性的get和set方法,此时只能用getOwnPropertyNames和defineProperty方法进行复制。如code 13所示。

    /*-----------code 13----------*/
    var obj1={a:1,b:2,c:[1,2,3]};
    var obj2=Object.assign({},obj1);
    console.log(obj1.c); //[1,2,3]
    console.log(obj2.c); //[1,2,3]
    obj2.c.push(4);
    obj2.a = 5;
    console.log(obj2.c); //[1,2,3,4]
    console.log(obj1.c); //[1,2,3,4]
    console.log(obj1.a); //1;
    

    深拷贝的方法

    【1】自定义拷贝函数,需要注意,在JS中基本数据类型除了Object类型都是值拷贝,不是引用拷贝。如code 14所示。

    /*-----------code 14----------*/
    function deepClone1(obj,cloneObj){
        if(typeof obj != 'object'){
            return false;
        }
        var cloneObj = cloneObj || {};
        for(var i in obj){
            if(typeof obj[i] === 'object'){
                cloneObj[i] = (obj[i] instanceof Array) ? [] : {};
                arguments.callee(obj[i],cloneObj[i]);//递归函数,相当于deepClone1(obj[i],cloneObj[i])
            }else{
                cloneObj[i] = obj[i]; 
            }  
        }
        return cloneObj;
    }
    
    var obj1={a:1,b:2,c:[1,2,3]};
    var obj2=deepClone1(obj1);
    console.log(obj1.c); //[1,2,3]
    console.log(obj2.c); //[1,2,3]
    obj2.c.push(4);
    console.log(obj2.c); //[1,2,3,4]
    console.log(obj1.c); //[1,2,3]
    

    【2】Json,只能正确处理的对象只有Number、String、Boolean、Array、扁平对象,即那些能够被json直接表示的数据结构,但是复制结果和原对象只有值是一致的。如code 15所示。

    /*-----------code 15----------*/
    function jsonClone(obj){
        return JSON.parse(JSON.stringify(obj));
    }
    
    var obj1={a:1,b:2,c:[1,2,3]};
    var obj2=jsonClone(obj1);
    console.log(obj1.c); //[1,2,3]
    console.log(obj2.c); //[1,2,3]
    obj2.c.push(4);
    console.log(obj2.c); //[1,2,3,4]
    console.log(obj1.c); //[1,2,3]
    
    var a = new String('aaaa');
    var b = jsonClone(a);
    console.log(a);
    console.log(b);
    

    控制对象状态

    属性描述符控制的是对象属性的状态,下面这些方法是用来控制对象整体的状态:

    【1】 Object.preventExtensions(myObject):禁止对象扩展,不能再添加新的属性。

    【2】Object.seal(myObject):禁止对象扩展,同时禁止对对象属性进行配置。

    【3】Object.freeze(myObject):禁止对象扩展,同时禁止对对象属性进行配置,同时禁止对象属性进行修改。

    数组对象的遍历

    前面在for...in循环中介绍到,for...in循环对同时遍历数组中的值和数组对象属性,也介绍了该如何只访问对象的属性,下面将一下怎样只遍历数组存在的数据集合,而非属性。

    最简单的方式就是标准for循环,如code 16所示。

    /*-----------code 16----------*/
    var myArray = [1, 2, 3];
    for (var i = 0; i < myArray.length; i++) {
    console.log( myArray[i] );
    }
    // 1 2 3
    

    或者是ES6引入的for...of循环,如code 17所示。

    /*-----------code 17----------*/
    var myArray = [ 1, 2, 3 ];
    for (var v of myArray) {
    console.log( v );
    }
    // 1 2 3
    

    同时ES5还提供了一些数组遍历方法,包括forEach、every、some方法,这三个函数都是顺序遍历数组中的每个值执行回调函数,调用格式为functtion(回调函数,this指向),不同的是forEach方法对数组中所有数都执行回调函数不管回调函数是否return false,every方法当遇到某个数使得回调函数返回false时,停止执行,不再对该数后面的数调用回调函数。some方法则正好相反,它在遇到某个值使回调函数返回true时停止执行。

    上面几种遍历方法都是顺序依次访问数据中的每个对象,那么是不是可以自定义数组的遍历顺序呢?

    其实数组对象有一个名为Symbol.iterator方法,它就是数组的一个迭代器,通过重复调用Symbol.iterator方法里的next方法,就可以不断向前访问数组,如code 17所示。上面几种循环遍历背后执行的就是类似code 18的过程。

    /*-----------code 18----------*/
    var myArray = [ 1, 2, 3 ];
    var it = myArray[Symbol.iterator]();
    it.next(); // { value:1, done:false }
    it.next(); // { value:2, done:false }
    it.next(); // { value:3, done:false }
    it.next(); // { done:true } //done等于true的时候,遍历结束
    

    因此只要重写数组对象的Symbol.iterator方法,我们就可以自定义数组的遍历顺序,可用用defineProperty方法或是字面量的格式进行重写,如code 19所示。

    /*-----------code 19----------*/
    var myObject = {
    a: 2,
    b: 3
    };
    Object.defineProperty( myObject, Symbol.iterator, {
    enumerable: false,
    writable: false,
    configurable: true,
    value: function() {
    var o = this;
    var idx = 0;
    var ks = Object.keys( o );
    return {
    next: function() {
    return {
    value: o[ks[idx++]],
    done: (idx > ks.length)
    };
    }
    };
    }
    } );
    
    //或是
    var myObject = {
    a: 2,
    b: 3,
    [Symbol.iterator]: function() {
    return {
    next: function() {
    var o = this;
    var idx = 0;
    var ks = Object.keys( o );
    return {
    next: function() {
    return {
    value: o[ks[idx++]],
    done: (idx > ks.length)
    };
    }
    };
    }
    };
    
    // iterate `myObject` manually
    var it = myObject[Symbol.iterator]();
    it.next(); // { value:2, done:false }
    it.next(); // { value:3, done:false }
    it.next(); // { value:undefined, done:true }
    // iterate `myObject` with `for..of`
    for (var v of myObject) {
    console.log( v );
    }
    // 2
    // 3
    

    参考资料:

    [1] You don't know js -- this & Prototypes

    [2] 深入理解javascript对象系列第一篇——初识对象

    [3] 深入理解javascript对象系列第二篇——属性操作

    [4] 深入理解javascript对象系列第三篇——神秘的属性描述符

    [5] 对象拷贝

  • 相关阅读:
    C++同步串口通信
    python描述符详解
    python属性访问
    python简单计时器实现
    python时间模块详解(time模块)
    python魔法方法大全
    python类与对象各个算数运算魔法方法总结
    python里的魔法方法1(构造与析构)
    Python 函数修饰符(装饰器)的使用
    python类与对象的内置函数大全(BIF)
  • 原文地址:https://www.cnblogs.com/ammyben/p/8454268.html
Copyright © 2020-2023  润新知