zoukankan      html  css  js  c++  java
  • 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] 对象拷贝

  • 相关阅读:
    商贸通帐套隐藏方法
    固定资产打开提示:上年度数据未结转!
    ZOJ 2432 Greatest Common Increasing Subsequence
    POJ 1080 Human Gene Functions
    POJ 1088 滑雪
    POJ 1141 Brackets Sequence
    POJ 1050 To the Max
    HDOJ 1029 Ignatius and the Princess IV
    POJ 2247 Humble Numbers
    HDOJ 1181 变形课
  • 原文地址:https://www.cnblogs.com/ammyben/p/8454268.html
Copyright © 2011-2022 走看看