语法
- 声明(文字)形式
let myObj={
key:value
};
- 构造形式
let myObj=new Object();
myObj.key=value;
类型
基本类型
- string
- number
- boolean
- null
- undefined
- object
null本身是基本类型,但由于在JavaScript中,对象在底层表示为二进制,前三位为0则为对象,但null全为0,所有typeof会返回object
内置对象(都是对象的子类型)
- String
- Number
- Boolean
- Object
- Function
- Array
- Date
- RegExp
- Error
let str = "i am a string";
console.log(str);
console.log(str instanceof String);
let strObj=new String("I am a string");
console.log(typeof strObj);
console.log(strObj instanceof String);
console.log(Object.prototype.toString.call(strObj));
string本身并不是对象,但执行操作时(获取长度)会自动转换为对象String
let str="i am a string";
console.log(str.length);
console.log(str.charAt(3));
内容
对象的内容即属性
属性的访问方式
- 属性访问, ( . ) 操作符要求属性名满足标识符的命名规范
- 键访问, [''] 可以接受任何Unicode字符串作为属性名
let myobj={
a:2
};
console.log(myobj.a);
console.log(myobj['a']);
由于['']接受字符串,可动态构造这个字符串
let myobj={
a:2
};
let idx,
something=true;
if (something){
idx='a';
}
console.log(myobj[idx]);
在对象中,属性名永远是字符串,即使是其他值也会转换为字符串(数组中确实是数字)
let myobj={};
myobj[true]='foo';
myobj[3]='bar';
myobj[myobj]='baz';
console.log(myobj['true'],myobj['3'],myobj['[object Object]']);
可计算属性名,在文字形式中通过[]包裹一个表达式来当做属性名
let prefix='foo';
let name='javascript';
let myobj={};
myobj[prefix+name]='true';
console.log(myobj['foojavascript']);
let myobj2={
[prefix+'bar']:"hello",
[prefix+'baz']:"world"
};
console.log(myobj2['foobar'],myobj2['foobaz']);
属性与方法
在JavaScript函数和方法是可以互换的,函数永远不会属于一个对象,只有对函数的引用
function foo() {
console.log("foo");
}
let someFoo=foo;
let myObj={
someFoo:foo
};
console.log(foo,someFoo,myObj.someFoo);
即使在对象内部声明一个函数,这个函数也不会属于这个对象,只是一个引用
let myobj={
foo:function foo() {
console.log('foo');
}
};
let someFoo=myobj.foo;
console.log(someFoo,myobj.foo);
数组
数组也支持[]访问,不过期望的是整数,数组也是对象,可以添加属性
let myarray=['foo',42,'bar'];
console.log(myarray.length,myarray[0],myarray[2]);
myarray.baz="baz";
console.log(myarray.length,myarray.baz);
如果向数组添加一个属性,但属性看起来是数字,就是变为数值小标,并改变数组
let myarray=['foo',42,'bar'];
myarray['3']='baz';
console.log(myarray.length,myarray[3]);
复制对象
function anotherfun() {}
let anotherobj={
c:true
};
let anotherarr=[];
let myobj={
a:2,
b:anotherobj,
c:anotherarr,
d:anotherfun
};
anotherarr.push(anotherobj,myobj);
属性描述符
- value
- writable
- enumerable
- configurable
let myobj={
a:2
};
console.log(Object.getOwnPropertyDescriptor(myobj,'a'));
创建普通属性时,属性描述符会使用默认值,也可通过Object.defineProperty()添加或修改属性
let myobj={};
Object.defineProperty(myobj,'a',{
value:2,
writable:true,
configurable:true,
enumerable:true
});
console.log(myobj.a);
- writable,决定是否可以修改属性值(静默失败,严格模式下,会报错)
let myobj={};
Object.defineProperty(myobj,'a',{
value:2,
writable:false,
configurable:true,
enumerable:true
});
myobj.a=3;
console.log(myobj.a);
- configurable,只要属性是可配置的就可以使用defineProperty()方法来修改属性描述符,把configurable设为false是单向操作无法撤销,但writable属性可以由true改为false,但无法false-->true
let myobj={
a:2
};
myobj.a=3;
console.log(myobj.a);
Object.defineProperty(myobj,'a',{
value:4,
writable:true,
configurable:false,
enumerable:true
});
console.log(myobj.a);
myobj.a=5;
console.log(myobj.a);
Object.defineProperty(myobj,'a',{
value:6,
writable:true,
configurable:true,
enumerable:true
});
同时,还会禁止删除这个属性(静默失败,严格模式报错)
let myobj={
a:2
};
console.log(myobj.a);
delete myobj.a;
console.log(myobj.a);
Object.defineProperty(myobj,'a',{
value:2,
writable:true,
configurable:false,
enumerable:true
});
console.log(myobj.a);
delete myobj.a;
console.log(myobj.a);
- enumerable,控制属性是否出现在对象的枚举属性中(如for..in)
不变性
对象或属性不可改变,所有方法都是浅不变,只会影响目标对象和直接属性,不会改变引用对象
- 对象常量,结合writable:false和configurable:false创建一个常量属性(不可修改,重定义,删除)
let myobj={};
Object.defineProperty(myobj,'a',{
value:42,
writable:false,
configurable:false
});
- 禁止扩展,禁止对象添加新属性和保留已有属性(严格模式下会报错)
let myobj={
a:2
};
Object.preventExtensions(myobj);
myobj.b=3;
console.log(myobj.b);
- 密封,Object.seal(),对现有对象调用Object.preventExtensions()并把所有属性标记为configurable:false
- 冻结,Object.freeze(),对现有对象调用Object.seal(),并将所有属性标记为writable:false
[[Get]]
属性访问实现时的细节
let myobj={
a:2
};
console.log(myobj.a);
对象默认的内置[[Get]]操作会首先在对象中查找是否有相同名称的属性,找打则返回,反之,则遍历可能存在的[[Prototype]]链,如果仍没找到则返回undefined
let myobj={
a:2
};
console.log(myobj.b);
[[Put]]
属性存在时的行为
- 属性是否是访问描述符?如果是并存在setter就调用setter
- 属性的数据描述符中writable是否为false,是就静默失败,严格模式抛出TypeError异常
- 如果都不是则将该值设为属性的值
Getter和Setter
getter是一个隐藏函数会在获取属性时调用,setter会在设置值时调用,当给属性定义getter和setter时,这个属性会被定义为访问描述符,JavaScript会忽略他们的value和writable,取而关心set和get(还有configurable和enumerable)特性
let myobj={
get a(){
return 2;
}
};
Object.defineProperty(myobj,'b',{
get:function () {
return this.a*2;
},
enumerable:true
});
console.log(myobj.a,myobj.b);
定义setter,会覆盖单个属性默认的[[Put]]操作
let myobj={
get a(){
return this._a_;
},
set a(val){
this._a_=val*2;
}
};
myobj.a=2;
console.log(myobj.a,myobj._a_);
存在性
let myobj={
a:undefined
};
console.log(myobj.a,myobj.b);
这种情况下无法确定属性是否存在
通过in操作符检查属性是否在对象及其[[Prototype]]原型链中,hasOwnPrototy(...)只检查属性是否在对象中,对于一些对象没有Object.prototype的委托,即没有原型(Object.create(null)创建的对象),不能调用hasOwnPrototy(...).此时可显示绑定,Object.prototype.hasOwnPrototy(obj,'a')
let myobj={
a:2
};
console.log('a'in myobj,'b' in myobj,myobj.hasOwnProperty('a'),myobj.hasOwnProperty('b'));
in操作符实际检查的是某个属性名是否存在,尤其是数组
list = [1,4,3];
console.log(4 in list,2 in list);
枚举
属性存在且可访问,但不会在for..in循环中,尽管能用in操作符来判断是否存在
let myobj={};
Object.defineProperty(myobj,'a',{
enumerable:true,
value:2
});
Object.defineProperty(myobj,'b',{
enumerable:false,
value:3
});
console.log(myobj.b);
console.log('b' in myobj,myobj.hasOwnProperty('b'));
for (let k in myobj){
console.log(k,myobj[k]);
}
另一种区分属性是否可枚举
- propertyIsEnumerable(...)会检查属性名是否直接(不是原型链中)存在对象中并满足enumerable:true
- Object.keys(...)返回一个数组,包含所有可枚举属性
- Object.getOwnPropertyNames(...)返回一个数组包含所有属性,无论是否可枚举
- in和hasOwnProperty区别在于是否查找[[Prototype]]链,Object.keys(...),Object.getOwnPropertyNames(...)都直会直接查找对象包含的属性
let myobj={};
Object.defineProperty(myobj,'a',{
enumerable:true,
value:2
});
Object.defineProperty(myobj,'b',{
enumerable:false,
value:3
});
console.log(myobj.propertyIsEnumerable('a'),myobj.propertyIsEnumerable('b'));
console.log(Object.keys(myobj),Object.getOwnPropertyNames(myobj));
遍历
for...in可以遍历对象可枚举属性列表(包括[[Prototype]]链)
forEach(...),every(...),some(...),辅助迭代器可以接受一个回调函数并把它引用到数组的每个元素上
forEach(...)会遍历数组的所有值并忽略回调函数的返回值
every(...)会一直运行到回调函数返回false
some(...)会一直运行到回调函数返回true
遍历对象属性的顺序是不确定,一定不要相信观察到的顺序,这是不可靠的
遍历值for...of,对象需要实现迭代器
let myarray=[1,2,3];
for (let v of myarray){
console.log(v);
}
for...of会先对被访问对象请求一个迭代器对象,然后调用迭代器对象的next()方法遍历所有值,
Symbol.iterator用来获取对象的@@iterator内部属性,引用类似iterator的特殊属性时要使用符号名,而不是符号包含的值,@@iterator本身并不是迭代器对象,而是一个返回迭代器对象的函数,
next(...)返回形式为{value:..,done:...},value是当前遍历的值,done是一个bool表示是否还有可遍历值
let myarray=[1,2,3];
let it=myarray[Symbol.iterator]();
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
给对象定义@@iterator
let myobj={
a:2,
b:3
};
Object.defineProperty(myobj,Symbol.iterator,{
enumerable:false,
writable:false,
configurable:true,
value:function () {
let o=this;
let idx=0;
let ks=Object.keys(o);
return {
next:function () {
return {
value:o[ks[idx++]],
done:(idx>ks.length)
};
}
};
}
});
let it=myobj[Symbol.iterator]();
console.log(it.next());
console.log(it.next());
console.log(it.next());
for (let v of myobj){
console.log(v);
}
定义无限迭代器
let randoms={
[Symbol.iterator]:function () {
return {
next:function () {
return {value:Math.random()};
}
};
}
};
let randoms_pool=[];
for (let n of randoms){
randoms_pool.push(n);
if (randoms_pool.length ===100)break;
}