1.对象概述
把一组数据结构和处理它们的方法组成对象(object),把相同行为的对象归纳为类(class),通过类的封装(encapsulation)隐藏内部细节,通过继承(inheritance)实现类的特化(specialization)/泛化(generalization),通过多态(polymorphism)实现基于对象类型的动态分派(dynamic dispatch)。
对象包含一系列无序的属性,每个属性(可动态添加或删除)都有一个字符串key和对应的value,每个对象都还有个原型。
javascript中对象的每一个属性都有很多属性标签:writable、enumerable、configurable、value、set/get。
每一个对象还有一个原型[[proto]]。
[[proto]] [[class]]这些标签是描述属性特征的 在js中是无法直接访问的 因为这些特性都是内部值 ECMAScript的规范是把他们放在两个方括号中的
var obj={};
obj.x=1;
obj.y=2;
function foo(){}
foo.prototype.z=3;
obj.__proto__=new foo(); //这个地方是把obj对象的原型指向foo.prototype ;
obj.x ===1; //true;
obj.y===2; //true;
obj.y===3 ; //false ;
var obj=new foo();
obj.x ===1; //false
obj.y===2; //false
proto:指向foo()的prototype(函数的对象属性)。
访问obj.z 查找对象属性,没有z,再在对象的原型链上继续查找一直到末端。
对象的[class]标签表示它属于那个种类。
对象的[extensible]标签表示它是否允许增加新的属性。
2.创建对象、原型链
对象的创建:
1.字面量:var obj1 = {x:1,y:2};可嵌套
2.new构造器的方式: prototype是原型是对象属性
function foo(){}
foo.prototype.z = 3;
var obj = new foo();// obj的prototype指向foo.prototype
obj.y = 2;
obj.x = =1;
obj.z = 3;// obj中没有z这个值,会向上找原型,原型中有就返回
// 最上层的原型是Object.prototype --->null 找到null还没有就会返回undefined
typeof obj.toString; // 'function'
'z' in obj; //true
obj.hasOwnProperty('z'); // false 说明z不再obj上,而在原型上
// 会继承原型链上的属性,但是不会去修改他们
3.Object.create创建对象,create时系统内置函数
var obj = Object.create({x:1});// 会让对象的原型指向创建的x
obj.x//1
typeof obj.toString;// 'function'
obj.hasOwnProperty('x');// false
var obj = Object.create(null);
obj.toString //undeined
3.属性操作
读写对象属性、属性异常、删除属性、检测属性、枚举属性。
枚举简单的说也是一种数据类型,只不过是这种数据类型只包含自定义的特定数据,它是一组有共同特性的数据的集合
数据属性:
1.configurable:设置属性是否能被删除,属性的特性是否能被设置,能否修改属性的值等;
2.enumerable:设置属性是否可枚举
3.writable:设置属性的值能否被修改
4.value:设置属性的值
如果想通过自定义这些特性来设置对象属性,可以通过Object.defineProperty,如下:
var person = {name:"张三"};
Object.defineProperty(person,name,{configurable:true,enumerable:true,writable:true,value:"李四”});
在上面所设置的特性当中,configurable如果为false表示属性特性设置在之后不能被改动(除了writable,writable在configurable为false时仍然可以设置成false),而且configurable一旦为false后就不能再设置成true了,
而enumberable如果为false则表示该属性不可枚举,用for in循环输出对象属性时该属性不会被输出;
writable很明显,为false时则表示不可修改该属性的值(不过当configurable为true时可以通过修改value的值直接改写或者将writable修改为true)
Object.defineProperty(person,name,{configurable:false});
delete person.name; //delete操作不成功
Object.defineProperty(person,name,{enumerable:false}); //操作不成功
访问器属性
configurable:设置属性是否能被删除,属性的特性是否能被设置,能否修改属性的值等;
enumerable:设置属性是否可枚举
get / set :在读写属性时会自动调用的函数
比如:
var book = {
_year:2004,
edition:1
};
Object.defineProperty(book,"year",{
get:function(){
return this._year;
},
set:function(newValue){
this._year = newValue;
this.edition += newValue - 2004;
}
});
book.year = 2005; //当要设置year属性时,会自动调用set方法
alert(book.edition);
如果要的不止是配置单单一个属性,而是要同时配置多个,可以通过Object.defineProperties,如下:
var person = {name:"张三"};
Object.defineProperty(person,{
name:{value:"李四”,configurable:false},
sex:{value:"man",writable:false}
});
在配置完属性后或者通过Object.defineProperty更改过属性后想读者该属性,可以通过Object.getOwnPropertyDescriptor读取
Object.getOwnPropertyDescriptor(book,"edition"); //读取book对象中的edition属性
属性读写:
var obj = {x:1,y:2};
obj.x; //1
obj["y"]; //2
属性读写异常:
var obj = {x:1};
obj.y; //undefined
属性删除:
var person = {age:28,title:'fe'};
delete person.age; //true
delete person['title']; //true
person.age; //undefined
delete person.age; //true
delete Object.prototype; //false
//getOwnPropertyDescriptor 获取对象属性所有标签。
var descriptor = Object.getOwnPropertyDescriptor(Object,'prototype');
//configurabe: 方法,检查获取到的object下的对象是否可配置。
descriptor.configurable; //false
//var定义的全局变量或者局部变量不会被删除
var globalVal = 1;
delete globalVal; //false
(function(){
var localVal = 1;
return delete localVal;
}()) //false
//全局函数或者局部函数也不可以被删除
function fd(){};
delete fd; //false
(function(){
function fd(){};
return delete fd;
}()); //flase
//隐式全局变量
onNo = 1;
window.ohNo; //1
delete ohNo; //true
属性检测:
var cat = new Object;
cat.legs = 4;
cat.name = "Kitty";
'legs' in cat; //true
'abc' in cat; //false
'toString' in cat; //true,inherited property!!!
//Object.hasOwnProperty('property')只检测本对象上是否有该属性,有则返回true
cat.hasOwnProperty('legs'); //true
cat.hasOwnProperty('toString'); //false
//对象是否是可枚举的
cat.propertyIsEnumerable('legs'); //true
cat.propertyIsEnumerable('toString'); //false
//定义枚举为false
Object.defineProperty(cat,'price',{enumerable:false,value:1000});
cat.propertyEnumerable('price'); //false
cat.hasOwnProperty('price'); //true
//属性枚举(Object的属性是不可枚举,默认为false,其他对象默认是true)
var o = {x:1,y:2,z:3};
'toString' in o; //true
o.propertyIsEnumberable('toString'); //false
var key;
for(key in o){
console.log(key); //x,y,z
}
var obj = Object.create(o);
obj.a = 4;
var key;
for(key in obj){
console.log(key); //a,x,y,z
}
//Object.hasOwnProperty('property')只检测本对象上是否有该属性,有则返回true[只处理对象上的属性,不去向原型链请求属性]
var obj = Object.create(o);
obj.a = 4;
var key;
for(key in obj){
if(obj.hasOwnProperty(key)){
console.log(key); //a
}
}
4.get/set方法
JavaScript中对象的property有三个属性:
1.writable。该property是否可写。
2.enumerable。当使用for/in语句时,该property是否会被枚举。
3.configurable。该property的属性是否可以修改,property是否可以删除。
var man = {
name:"Bosn",
weibo:"@Bosn",
get age(){
return new Date().getFullYear()-1988;
},
set age(val){
console.log('Age can\'t be set to ' + val);
}
}
console.log(man.age); //27
man.age = 100; //Age can\'t be set to 100
console.log(man.age); //still 27
// get/set与原型链
function foo(){}
Object.defineProperty(foo.prototype,'z',{
get: function(){return:1;}
});
var obj = new foo();
obj.z;// 1
obj.z = 10;
obj.z;// still 1
// 当obj中没有z属性,并向原型链上查找的时候有对应的get/set方法时,再赋值的时候会走原型上的相应的get/set方法,而不会直接赋值给obj
// 想要赋值给obj.z
Object.defineProperty(obj,'z',{
value: 100,
configurable: true;
});
obj.z;// 100
delete obj.z;
obj.z;// back to 1
var o = {};
Object.defineProperty(o,'x',{value:1}); //writable=false,configurable=false
var obj = Object.create(o);
obj.x; //1
obj.x = 200;
obj.x; //1
Object.defineProperty(obj,'x',{writable:true,configurable:true,value:100});
obj.x; //100
obj.x = 500;
obj.x; //500
附注:
val = +val;
实际上就是利用一元+操作符的特性(会尝试转换为数字),来隐式将一个变量转换为数字类型(number)。
例如:如果val是"123",+“123”; // 输出123,类型是number,而不再是string
5.属性标签
writable、enumerable和configurable默认值:
1)在使用Object.defineProperty、Object.defineProperties 或 Object.create 函数的情况下添加数据属性,writable、enumerable和configurable默认值为false。
2)使用对象直接量创建的属性,writable、enumerable和configurable特性默认为true。
defineProperties应用场景:
1.Node.js编程,需要对属性的访问做控制,需要更健壮的get/set方法提供属性存取的控制。
2.前端安全,通过Object.defineProperties禁止部分敏感属性的访问。来防止前端页面环境被插件、ISP等因素篡改。
3.一些较为复杂的业务逻辑或较为底层的库,get/set/权限控制在一些情况下会让代码更加健壮。
configurable 是否能delete删除属性修改属性标签; writable 是否能修改属性值;enumerable 是否可以枚举,使用for in。
// 查看属性标签
Object.getOwnPropertyDescriptor({pro:true},'pro');
// Object{value:true,writable:true,enumerable:true,configurable:true}
Object.getOwnPropertyDescriptor({pro:true},'a'};
//undefined
var person = {};
Object.defineProperty(person,'name',{
configurable:false,
writable:false,
enumerable:true,
value:'Bosn Ma'
});
person.name; //Bson Ma
person.name = 1;
person.name; //Bson Ma
delete person.name; //false
Object.defineProperties(person,{
title:{value:"fe",enumerable:true},
corp:{value:'BABA',enumerable:true},
salary:{value:50000,enumerable:true,writable:true}
});
Object.getOwnPropertyDescriptor(person,'salary');
//Object{value:50000,writable:true,enumerable:true,configurable:false}
Object.getOwnPropertyDescriptor(person,'corp');
//Object{value:'BABA',writable:false,enumerable:true,configurable:false}
6.对象标签、对象序列化
对象标签:[[proto]]、[[class]]、[[extensible]]
原型标签:[[proto]]
class标签:[[class]]
首先我们先了解Object.prototype.toString的用法:
toString 是 Object 原型对象上的方法,
使用 call 来调用该方法会返回调用者的类型字符串,格式为 [object,xxx],xxx 是调用者的数据类型,包括:String、Number、Boolean、Undefined、Null、Function、Date、Array、RegExp、Error、HTMLDocument 等,基本上,所有的数据类型都可以通过这个方法获取到。
Object.prototype.toString()返回[object class], 我们可以通过它获取对象的类型信息。
要获取一个对象的真实的内置类型,我们需要通过获取[[Class]]的属性值,在es5之前,该属性值只能通由Object.prototype.toString来访问,因此,通过Object.prototype.toString.call(arr)改变tostring方法的this指向,从而获得对象的内置类型。
console.log(Object.prototype.toString.call("jerry"));//[object String]
console.log(Object.prototype.toString.call(12));//[object Number]
console.log(Object.prototype.toString.call(true));//[object Boolean]
console.log(Object.prototype.toString.call(undefined));//[object Undefined]
console.log(Object.prototype.toString.call(null));//[object Null]
console.log(Object.prototype.toString.call({name: "jerry"}));//[object Object]
console.log(Object.prototype.toString.call(function(){}));//[object Function]
console.log(Object.prototype.toString.call([]));//[object Array]
console.log(Object.prototype.toString.call(new Date));//[object Date]
console.log(Object.prototype.toString.call(/\d/));//[object RegExp]
function Person(){};
console.log(Object.prototype.toString.call(new Person));//[object Object]
那为什么不直接用obj.toString()呢?
console.log("jerry".toString());//jerry
console.log((1).toString());//1
console.log([1,2].toString());//1,2
console.log(new Date().toString());//Wed Dec 21 2016 20:35:48 GMT+0800 (中国标准时间)
console.log(function(){}.toString());//function (){}
console.log(null.toString());//error
console.log(undefined.toString());//error
同样是检测对象obj调用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方法。
//表示对象是哪一个类型
var toString = Object.prototype.toString;
function getType(o){return toString.call(o).slice(8,-1);};
toString.call(null); //"[object null]"
getType(null); //"Null"
getType(undefined); //"Undefined"
getType(1); //"Number"
getType(new Number(1)); // "Number"
typeof new Number(1); // "Object"
getType(true); //"Boolean"
getType(new Boolean(true)); //"Boolean"
extensible标签:[[extensible]]
var obj = {x:1,y:2};
Object.isExtensible(对象); //可判断对象是否可以添加;
Object.preventExtensible(对象); //可将对象改为不能添加,不改变原有属性标签
Object.defineProperty(obj,"x",{value:2}); //虽然禁止了对象扩展,但是删除或者修改属性的值以及标签
Object.seal(obj); //设置对象属性标签不可被配置,设置confinurable为false
Object.ifSealed(obj); //判断属性是否可以被修改
Object.freeze(obj); //冻结,设置属性不可写,也就是将对象writable、enumrable、configurable设置为false,阻止对象的所有修改
Object.isFrozen(obj); //判断对象是否被freeze
var obj = {x:1,y:2};
Object.isExtensible(obj); // true
Object.preventExtensions(obj);
Object.isExtensible(obj); //false
obj.z = 1;
obj.z; //undefined
Object.getOwnPropertyDescriptor(obj,'x');
// Object{value:1,writable:true,enumerable:true,configurable:true}
Object.seal(obj);
Object.getOwnPropertyDescriptor(obj,'x');
// Object{value:1,writable:true,enumerable:true,configurable:false}
Object.isSealed(obj); //true
Object.freeze(obj);
Object.getOwnPropertyDescriptor(obj,'x');
// Object{value:1,writable:false,enumerable:true,configurable:false}
Object.isFrozen(obj); //false
序列化:
在向后台发送请求数据时,用JSON.stringify将对象转化为字符串,用JSON.parse将字符串解析为对象,但是,在用JSON.stringify将对象转化为字符串时如果对象属性的值为undefined,那么它将不会出现在转化后的字符串里。
var obj = {x:1,y:true,z:[1,2,3],nullVal:null};
JSON.stringify(obj); //"{"x":1,"y":true,"z":[1,2,3],"nullVal":null}"
obj = {val:undefined,a:NaN,b:Infinity,c:new Date()};
JSON.stringify(obj); //"{"a":null,"b":null,"c":"2015-01-20T14:15:43.910Z"}"
obj = JSON.parse('{"x":1}');
obj.x; //1
如果我们想自定义序列化,可以在对象里自定义toJSON方法,同理,我们在将对象toString或者valueOf时也可以自定义toString方法和valueOf方法。
其他对象方法:toString()、valueOf()
var obj = {x:1, y:2};
obj.toString = function(){return this.x + this.y;};
obj.valueOf = function(){return this.x + this.y + 100}
"result" + obj; // 这里是result103,而不是result3
JavaScript中的二元+操作符,若操作数为对象,则尝试转换为基本类型。优先级是先找valueOf,再找toString。
注意到,若valueOf/toString返回的不是基本类型,而是对象,则会被忽略。
var obj = {x:1, y:2};
obj.toString = function(){return this.x + this.y;};
obj.valueOf = function(){return {x : 1}}; // 不可用的valueOf
"result" + obj; // "result3", 因为valueOf无效,使用toString作为结果返回
若valueOf/toString均不可用,则报TypeError异常。
var obj = {x:1, y:2};
obj.toString = function(){return {};}; // 不可用的toString
obj.valueOf = function(){return {x : 1}}; // 不可用的valueOf
"result" + obj; // Uncaught TypeError: Cannot convert object to primitive value
对象转化为基本类型的过程:
在后台首先会自动调用valueOf方法尝试将对象转化为基本类型,如果valueOf不存在或者无法转化为基本类型则会调用toString方法继续尝试,如果两种都无法将对象转化为基本类型则会报错。
本节课程总结:
对象的结构
创建对象
属性操作
getter/setter
属性标签
对象标签
序列化
对象方法