JS基础学习——对象
2018-02-19 20:36
393 查看
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] 对象拷贝
相关文章推荐
- 2016.4.18(js—对象基础学习笔记)
- js基础学习之-js全局对象
- JS 学习笔记--JS中的事件对象基础
- node.js基础学习--IO-对象映射(fs,Buffer)
- js面向对象学习笔记之七(函数 与 基础控制结构)
- JS基础学习第六天:JavaScript对象入门(构造函数和实例对象)
- 【js基础】js事件对象学习笔记
- js基础学习之-js对象的属性
- js基础学习之-js包装对象
- JS基础学习第七天:对象原型及原型式的继承
- JS基础学习第十天:BOM对象及BOM操作
- Node.js学习--基础知识(8)--全局对象和全局变量
- Three.js基础学习之场景对象
- 【学习】js学习笔记:对象的一些基础知识
- 学习zepto.js(对象方法)[6]
- Js_面向对象基础
- 17 JS基础之--对象的组成(属性和方法)
- 对于js中网络接口websocket,二进制数组arraybuffer,视图对象dataview学习记录。
- js1:对象的学习,构造函数,继承构造函数【使用教材:JavaScript深度剖析第2版】
- js基础学习之--关于 Cookie 的增删改查的封装函数