要说对象,我们先来讲讲JavaScript的类型
1.内置类型
- string
- number
- boolean
- null
- undefined
- object
- symbol(ES6新增)
2.对象
接下来我们主要讲下JavaScript主要类型中的对象,其实JavaScript有和主要类型相关的内置对象
2.1 内置对象的类别
- String
- Number
- Boolean
- Object
- Function
- Array
- Date
- RegExp
- Error
这些内置对象在JavaScript上其实是一些内置函数,这些内置函数可以当做构造函数,可以由new关键字构造对应子类型的新对象
2.2判断值的类型
1.typeof
typeof运算符返回的是一个字符串
typeof Symbol(); // 'symbol' 有效
typeof ''; // 'string' 有效
typeof 1; // 'number' 有效
typeof true; // 'boolean' 有效
typeof undefined; // 'undefined' 有效
这些是可以使用typeof判断的内置类型,你一定很好奇null呢?typeof的null得到的是object
typeof null; //object 无效
这是由于第一代JavaScript引擎中的JavaScript值表示为32位的字符。最低的3位作为一种标识,表示值是对象、整数、浮点数或者布尔值。
对象的标示是000。而为了表现null值,引擎使用了机器语言NULL指针,该字符的所有位都是0。而typeof就是检测值的标志位,这就是为什么它会被认为是一个对象的原因。(摘自:深入理解JavaScript 第九章)
内置对象typeof的判断
typeof new Function(); // 'function' 有效
typeof [] ; //'object' 无效
typeof new Date(); //'object' 无效
typeof new RegExp(); //'object' 无效
typeof new Error(); // 'object' 无效
2.instanceof
instanceof 是用来判断A是否为B的实例,表达式为:A instanceof B,如果A是B的实例,则返回true,否则返回false。instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性,但它不能检测null 和 undefined
[] instanceof Array; //true
{} instanceof Object;//true
new Date() instanceof Date;//true
new RegExp() instanceof RegExp;//true
null instanceof null;// Right-hand side of 'instanceof' is not an object
undefined instanceof undefined;// Right-hand side of 'instanceof' is not an object
3.constructor
constructor 是 Object 类型的原型属性,它能够返回当前对象的构造器(类型函数)。利用该属性,可以检测复合型数据的类型,如对象、数组和函数等。不过 constructor 是不稳定的,这个主要体现在把类的原型进行重写,在重写的过程中很有可能出现把之前的constructor给覆盖了,这样检测出来的结果就是不准确的。
{}.constructor; // ƒ Object() { [native code] }
4.Object.prototype.toString.call()
Object.prototype.toString.call() 是最准确最常用的方式。toString 是 Object 类型的原型方法,它能够返回当前对象的字符串表示。利用该属性,可以检测复合型数据的类型,如对象、数组、函数、正则表达式、错误对象、宿主对象、自定义类型对象等;也可以对值类型数据进行检测。
Object.prototype.toString.call('') ; // '[object String]'
Object.prototype.toString.call(1) ; // '[object Number]'
Object.prototype.toString.call(true) ; // '[object Boolean]'
Object.prototype.toString.call(undefined) ; // '[object Undefined]'
Object.prototype.toString.call(null) ; // '[object Null]'
Object.prototype.toString.call(new Function()) ; // '[object Function]'
Object.prototype.toString.call(new Date()) ; // '[object Date]'
Object.prototype.toString.call([]) ; // '[object Array]'
Object.prototype.toString.call(new RegExp()) ; // '[object RegExp]'
Object.prototype.toString.call(new Error()) ; // '[object Error]'
3 关于对象内容的访问方式
var obj={
a:1
};
如果你想访问obj对象中a,如何访问呢?毫无疑问你会想到 obj.a,如果我问除了这种还有其他方式么?相信也有不少伙伴想到 obj[“a”] ;这两种访问方式分别是属性访问和键访问
- 属性访问:要求属性名满足标识符的命名规范
- 键访问:可以接受任意UTF-8/Unicode字符串作为属性名,比如属性名带有 ‘-’ 特殊字符、或者属性名是变量、或者属性名是计算后的值,例如
// 含有特殊字符
var obj={
'my-obj':1
}
console.log(obj['my-obj']);
// 属性名是变量
var obj={
a:1,
b:2
}
function fun(attrName){
console.log(obj[attrName]);
}
fun('a'); // 1
fun('b'); // 2
// 属性名是计算后的值
var obj={
1:'a',
2:'b'
}
function fun(){
for(let i=0;i<2;i++){
console.log(obj[i+1]);
}
}
fun(); // a b
3.1 对象中的属性描述符
属性描述符是ES5新增的,使用**Object.getOwnPropertyDescript(obj,attr)**获取对象obj的attr属性描述符;使用 Object.defineProperty(obj,attr,{…}) 定义对象属性描述符
var obj={a:1};
Object.getOwnPropertyDescript(obj,'a');
//{
// value:2, //值
// writable:true, //可写可改
// enumerable:true, //可枚举(遍历)
// configurable:true //可配置(修改属性描述符)
//}
1. writable
决定是否可以修改属性的值,如果设置其为false,那么你在修改属性值会静默失败,如果在严格模式时会报错(TypeError)
var obj={};
Object.defineProperty(obj,'a',{
value:1,
writable:false,
configurable:true,
enumberable:true
});
obj.a=2;
obj.a; //1
"use strict";
var obj={};
Object.defineProperty(obj,'a',{
value:1,
writable:false,
configurable:true,
enumberable:true
});
obj.a=2;
obj.a; //TypeError: Cannot assign to read only property 'a' of object '#<Object>'
2. enumerable
控制属性是否会出现在对象的属性枚举中(for…in循环会遍历到可枚举的属性)
3. configurable
控制属性是否可以使用defineProperty(…)方法来修改属性描述符。
注意:
- configurable设置为false是不可逆操作,即设置为false后则不可修改为ture了,设置了false后,再修改configurable会报错(不论是否在严格模式下);
- 若设置了configurable:false后,writable:true可以修改为false,但是修改为false后也不可修改为ture;enumerable没有影响(大家可以多在console中实践)
- 设置了configurable:false后,delete对象中的属性也会静默失败,即delete属性后,属性依然可以被访问
var obj={};
Object.defineProperty(obj,'a',{
value:1,
writable:false,
configurable:false,
enumberable:true
});
Object.defineProperty(obj,'a',{
value:1,
writable:false,
configurable:true,//TypeError: Cannot redefine property: a
enumberable:true
});
Object.defineProperty(obj,'a',{
value:1,
writable:true,//TypeError: Cannot redefine property: a
configurable:false,
enumberable:true
});
4 对象的API
4.1判断对象中是否存在属性
这个命题一出来可能很多小伙伴说我直接把访问这个属性不就知道这个对象中是否存在这个属性了?其实这个方法不能很可靠的得到你想要答案,这是为什么呢?因为当你访问obj.a得到 undefined 时你怎么知道时本身不存在a属性还是因为这个属性就被定义成undefined呢?所以就出来一个API—— 对象.hasOwnProperty(‘属性’)
hasOwnProperty vs in
- hasOwnProperty:只获取当前对象是否存在所查找的属性,不会去原型链上寻找
- in:会查找原型链中是否存在,存在返回true;否则返回false
var obj={
a:1;
};
Object.prototype.b=2;
"a" in obj; //true
"b" in obj; //true
obj.hasOwnProperty("a"); //true
obj.hasOwnProperty("b"); //false
注意:in操作符判断的是对象是否含有某个属性名,而不是某个属性值
3 in [1,2,3]; //fasle
//[1,2,3] 的属性名分别是0 1 2
4.2 遍历
1. for… in
var obj={
a:1,
b:2,
};
Object.prototype.c=3;
for (let k in obj){
console.log(k+":"+obj[k]);
}
// a:1
// b:2
// c:3
你会发现for…in会遍历到原型链上的值,这可能不是你想要的,那要怎么解决呢?还记得前边我们说过的 hasOwnProperty 么?我们可以结合下达到你想要的效果
var obj={
a:1,
b:2,
};
Object.prototype.c=3;
for (let k in obj){
if(obj.hasOwnProperty(k)){
console.log(k+":"+obj[k]);
}
}
// a:1
// b:2
2. Object.keys()
遍历对象,返回对象本身(不遍历原型链)所有可枚举属性名组成的数组
var obj={
a:1,
b:2,
};
Object.prototype.c=3;
Object.defineProperty(obj,'d',{
value:4,
enumerable:false
});
Object.keys(obj); // ["a", "b"]
3. Object.getOwnPropertyNames()
遍历对象,返回对象本身(不遍历原型链)所有属性名组成的数组
var obj={
a:1,
b:2,
};
Object.prototype.c=3;
Object.defineProperty(obj,'d',{
value:4,
enumerable:false
});
Object.getOwnPropertyNames(obj); // ["a", "b", "d"]
4. 仿照数组构造@@iterator,使用for…of遍历(寻找内置或者自定义的@@iterator对象并调用他的next()方法遍历数据值)
var obj={
a:1,
b:2
};
Object.prototype.c=3;
Object.defineProperty(obj,'d',{
value:4,
enumerable:false
});
// 给对象增加@@iterator遍历器
Object.defineProperty(obj,Symbol.iterator,{
enumerable:false,
writable:false,
configurable:true,
value:function(){
var _this=this;
var index=0;
var keysArr=Object.keys(_this);
return{
next:function(){
return{
value:_this[keysArr[index++]],
done:(index>keysArr.length)
}
}
}
}
});
// 使用for...of遍历
for(let val of obj){
console.log(val);
}
// 1
// 2
// 手动遍历
var iterator=obj[Symbol.iterator]();
iterator.next(); // {value: 1, done: false}
iterator.next(); // {value: 2, done: false}
iterator.next(); // {value: undefined, done: true}
4.3 控制对象/属性值不被改动
1. 结合属性描述符控制属性不可变
var obj={};
Object.defineProperty(obj,"MY_CONST",{
value:1,
writable:false,
configurable:false
})
obj.MY_CONST=2;
obj.MY_CONST; // 1
2. Object.preventExtensions()
不允许增加新的属性,但是可以修改已存在属性的属性值;非严格模式下静默失败,严格模式下报错
var obj={a:1};
Object.preventExtensions(obj);//不允许增加新的属性,但是可以修改属性值
obj.b=2;
obj; // {a: 1}
"use strict"
var obj={a:1};
Object.preventExtensions(obj);//不允许增加新的属性,但是可以修改属性值
obj.b=2; // Cannot add property b, object is not extensible
3. Object.seal()
结合Object.preventExtensions()+configurable:false;所以不能增加新属性、不能重新配置、不能delete现有属性,但是可以修改现有属性的值
4. Object.freeze()
结合Object.seal()+writable:false;所以不能增加新属性、不能重新配置、不能delete现有属性,也不能修改现有属性值
总结:
以上方法均值控制目标对象的不可变性,如果它引用了其他对象,其他对象的内容不受影响,依然可变;如果你想被引用的对象也不可变,那么久在对它本身调用Object.freeze的基础上遍历它引用的所有对象,在这些对象上也调用Object.freeze
4.4 ES6对象我用到的一些API
1. Object.assign(target,source1,source2…)
一般用于对象的合并,将源对象所有的可枚举属性,复制到目标对象中,并返回目标对象
- 只是复制对象的引用地址,如果只有一个参数,则返回目标对象就是本身
var obj={a:1};
var obj1=Object.assign(obj);
console.log(obj1===obj); //true
``
- 如果让null/undefined作为目标对象(第一个参数)会报错,如果不是作为目标对象,会被直接跳过
```javascript
var obj={a:1};
var obj1=Object.assign(null); // Cannot convert undefined or null to object
var obj={a:1};
Object.assign(obj,null,undefined,{1:"a"});
obj; // {1: "a", a: 1}
- 如果参数不是对象会先转为对象在再合并,如果不能转为对象且不是目标对象(首参),会直接跳过
var obj={a:1};
Object.assign(obj,1,{1:"a"});
obj; // {1: "a", a: 1}
Object.assign(obj,[1]); // {0: 1, a: 1}
- 若是属性名重复,则会覆盖;
var obj={a:1};
Object.assign(obj,[1],[2],{a:3}); // {0: 2, a: 3}
作用:
- 1.克隆对象(浅克隆)
function clone(origin){
return Object.assign({},origin)
}
- 2.合并多个对象
function merge(target,...source){
return Object.assign(target,...source)
}
- 3.为属性指定默认值
const DEFAULTES={
logLevel:0,
outputFormat:'html'
};
function processContent(options){
options=Object.assign({},DEFAULTES,options);
}
2. Object.getOwnPropertyDescriptors()
ES5中,该方法返回某个对象属性的描述对象。ES6中返回指定对象所有自身属性(非继承属性)的描述对象
const obj = {
foo: 123,
get bar() { return 'abc' }
};
Object.getOwnPropertyDescriptors(obj)
// { foo:
// { value: 123,
// writable: true,
// enumerable: true,
// configurable: true },
// bar:
// { get: [Function: get bar],
// set: undefined,
// enumerable: true,
// configurable: true } }
上述代码中,Object.getOwnPropertyDescriptors()方法返回一个对象,所有原对象的属性名都是该对象的属性名,对应的属性值就是该属性的描述对象。 该方法的引入主要为了解决Object.assign()
无法正确拷贝get属性和set属性的问题
const source={
set foo(value){
console.log(value);
}
}
const target1={};
Object.assign(target1,source);
Object.getOwnPropertyDescriptor(target,'foo')
// { value: undefined,
// writable: true,
// enumerable: true,
// configurable: true }
上诉代码中,source对象的foo属性的值时一个复制函数,Object.assign方法将这个属性拷贝给target1对象,结果该属性的值变成了undefined。这是因为Object.assign()
方法总是拷贝一个属性的值,而不会拷贝他背后的复制方法或取值方法
这时,Object.getOwnPropertyDescriptors()
方法配合Object.defineProperties()
方法,就可以正确拷贝
cosnt shallowMerge=(target,source)=>Object.defineProperties(
traget,Object.getOwnPropertyDescriptors(source)
);
Object.getOwnPropertyDescriptors()
方法的另一用处——配合object.create()
方法,将对象属性克隆到一个新对象。
这是浅拷贝
const clone=Object.create(Object.getProtptypeOf(obj));
//或者
const shallowClone=(obj)=>Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
);
上述代码会克隆对象obj
另外,Object.getOwnPropertyDwscriptors()
方法可以实现一个对象继承另一个对象,以前继承另一个对象,常常写成下面这样
const obj={
__protp__:prot,
foo:123
};
ES6规定,proto__只有浏览器要部署,其他环境不用部署,如果去除__proto,上述代码就要修改为这样:
const obj=Object.create(proto);
obj.foo=123;
//或者
const obj=Object.assign(
Object.create(prot),
{
foo:123
}
)
有了Object.getOwnPropertyDescriptors()
,我们就有了另一种写法
const obj=Object.create(
prot,
Object.getOwnPropertyDescriptors({
foo:123
})
)
Object.getOwnPropertyDescriptors()
也可以用来实现Mixin(混入)模式
let mix=(object)=>({
with:(...mixins)=>mixins.reduce(
(c,mixin)=>Object.create(
c,Object.getOwnPropertyDescriptors(mixin)
),object)
});
//mutiple mixins example
let a={a:'a'};
let b={b:'b'};
let c={c:'c'};
let d=mix(c).with(a,b);
d.c//'c'
d.b//'b'
上述代码返回一个新对象d,代表了对象a和b被混入了对象c的操作