对象可以通过两种形式定义:声明形式和构造形式
var myObj={key:value};
let myObj = new Object();
myObj.key=value;
类型:
js中有六种主要类型
- string
- number
- boolean
- null
- undefined
- object
注意: 简单基本类型(string,boolean,number,null和undefined)本身并不是对象.null有时会被当做一种类型,但是这是语言本身的一个bug,即对null执行
typeof null 会返回字符串"object".
内置对象:
javaScript中还有一些对象子类型,通常被称为内置对象 - String
- Number
- Boolean
- Object
- Function
- Array
- Date
- RegExp
- Error
内置函数可以当做构造函数来使用,从而可以构造一个对应类型的新对象。
var strPrimitive = "I am a string";
typeof strPrimitive; //"string"
strPrimitive instanceof String ;//false;
var strObject = new String("I am a stirng");
typeof strObject; //"object"
strObject instanceof String; // true
Object.prototype.toString.call(strObject);//"[obejct String]"
原始值"I am a string"并不是一个对象,他只是一个字面量,并且是一个不可变的值.
思考下面的代码
var strPrimitive="I am a string";
console.log(strPrimitive.length);//13
console.log(strPrimitive.charAt(3));//"m"
strPrimitive.wrapClass="system auto create and auto delete";
console.log(strPrimitive.wrapClass);//undefined;
使用以上两种方法,我们都可以直接在字符串字面量上访问属性或者方法,之所以可以这样做是因为引擎自动把字面量转化为String对象,所以可以访问属性和方法.
包装类: String , Number , Boolean;
null和undefined没有对应的构造形式.他们只有文字形式
内容
对象的内容是由一些存储在特定命名位置的值(任意类型)组成的;
我们称之为属性;
存储在对象容器内部的是这些属性的名称,它们就像指针(引用)一样,
指向这些值真正的存储位置.
var myObject={a:2};昂为
myObject.a=2;//2
myObject["a"];//2
如果要访问myObject中a位置上的值,我们需要使用.操作符或[]操作符。
.a语法通常被称为"属性访问",[“a”]语法通常被称为"键访问".实际上它们访问的是同一个位置,并且会返回相同的值2,所以这两个术语可以互换的.
.操作符要求属性名满足标识符的命名规范,
[]操作符可以接受任意UTF-8/Unicode字符串作为属性名.
var myObject={};
myObject[true]="foo";
muObject[3] ="bar";
myObject[myObject]="baz";
myObject["true"];//"foo"
myObject["3"] ;//"bar"
myObject["[object Object]"];//"baz"
可计算属性名
var prefix="foo";
var myObject={
[prefix +"bar"]:"hello",
[]prefix+"baz":"world"
};
myObject["foobar"];//"hello"
myObject["foobaz"];//world
属性与方法
从技术角度来说,函数永远不会"属于"一个对象,所以把对象内部引用称为"方法"似乎不妥
确实,有些函数具有thi引用,有时候这些this确实会指向调用位置的对象引用.但是这种用法从本质上来说并没有把一个函数函数变成一个"方法",
因为this是在运行时根据调用位置动态绑定的,所以函数和对象的属性就是属性访问。所以函数和对象的关系最多也只能说是间接关系
每次访问对象的属性就是属性访问.如果属性访问返回的是一个函数,
那么它也并不是一个"方法".属性访问返回的函数和其他函数没有任何区别
(除了可能发生的隐式绑定)
function foo(){
console.log("foo");
}
var someFoo=foo;//对foo变量引用
var myObject={someFoo:foo};
foo;// function foo(){...}
someFoo; // function foo(){.. }
myObject.someFoo; // function foo(){...}
someFoo和myObject.someFoo只是对于同一个函数的不同引用,并不能
说明这个函数是特别的或者"属于"某个对象.如果定义时在内部有一个this引用,那么这两个函数引用的唯一区别就是myObject.someFoo中的this会被隐式绑定到一个对象.无论哪种方法引用形式都不能称之为"方法".
数组
数组也支持[]访问形式,不过就像我们之前提到过的值存储的,数组有一套更加结构化存储机制.数组期望的数值下表.也就是值存储的位置是整数(索引);
var myArray=["foo",42,"bar"];
myArray.length;//3
myArray[0]; //"foo"
myArray[2]://"bar"
myArray.notIndex="hello";
myArray.length;//3
myArray["6"]="test";
myArray.length//7;
数组的长度会呼吸的,不小于数组的最大索引(有一个范围);
稀疏数组参考《javaScript权威指南》,
《你不知道的JS》里没有讲;
复制对象
对于JSON安全(也就是说可以被序列化为一个JSON字符串并且可以根据这个字符串解析出一个结构和值完全一样的对象)的对象来说,有一种巧妙的复制方法
var newObj = JSON.parse(JSON.stringify(someObj));
ES6定义了Object.assign(targetObject ,originalObject/…/)方法来实现浅复制;
var targetObj={} , originalObj={a:1,arr:[1,2,3]}
Object.assign(targetObj,originalObj);
targetObj.arr==originalObj.arr //true;
//
var targetObj={}, originalObj={a:1,arr:[1,2,3],fun:function(){console.log("what clone fun?")}};
function clone(targetObj,originalObj){
for( let prop in originalObj){
if(!originalObj.hasOwnProperty(prop)) continue;
if(typeof originalObj[prop] !="object"){
targetObj[prop]=originalObj[prop];
}else{
targetObj[prop] = new Object();//简单暴力
clone(targetObj[prop],originalObj[prop]);
}
}
}
clone(targetObj,originalObj);
targetObj.arr ==original.arr//false;
属性描述符
var myObject={ a:2 };
Object.getOwnProertyDescriptor(myObject,"a");
//{
// value:2,
//writable:true,
//enumerable:true,
//configurable:true
//}
writable:决定是否可以修改属性的值
enumerable: 是否可以枚举
Configurable:是否可配置,只要属性是可配置,就可以使用definedProperty()方法
来修改属性描述符:
configurable:false 时 ,可以把writable的状态由true改为false,但是无法由false改为true;
除了无法修改, 还会禁止删除这个属性
- 对象常量:
writable:false 和configurable:false就可以创建一个真正的常量属性(不可修改,重定义或删除) - 禁止扩展
Object.preventExtensions(); - 密封:
Object.seal() 相当于Object.preventExtensions() ,并把所有属性标记为configurable:false; - 冻结
object.freeze() , 对象完全动不了;不能添加属性,不能删除属性,不能修改属性的值;
[[Get]]
属性访问在在实现时有一个微妙却非常重要的细节
var myObject={a:2};
myObject.a=2;
myOject.a是一次属性访问,但是这条语句并不仅仅是在myObject
中查找名为a的属性;
在语言规范中,myObject.a在myObject上实际上实现了[[get]]操作.
对象默认的内置[[Get]]操作首先在对象中查找是否有名称相同的属性
如果找到就会返回这个属性的值.
[[Put]]
[[Put]]被触发时,更复杂;
如果已经存在这个属性,[[Put]]算法大致如下
1. 属性是否是访问描述符?如果是并且存在setter就调用setter.
2. 属性的数据描述符中writable是否是false?如果是,在非严格模式失败,
在严格模式下抛出TypeError异常
3. 如果都不是,将该值设置为属性的值.