js的的基本类型:
简单基本类型:string,number,boolean,null,undefined,symbol(es6新增,符号,具有唯一性)
复杂基本类型:object(包含Array,function)
对象定义的2种形式:声明(文字)形式和构造形式
//声明形式
var obj1={
key:value;
}
//构造形式
var obj2=new Object();
obj2.key=value;
生成的对象是一样的,文字声明中你可以添加多个键值对,构造形式中必须逐个添加
内置对象:String,Number,Boolean,Object,Function,Array,Date,RegExp,Error
这些内置对象实际是一些内置函数,可以当做构造函数使用(new String()),从而构造一个对应子类型的新对象
var a="tom";
var b=new String('jerry')
typeof a;//"string"
typeof b;//"object"
console.log(a.length);
console.log(a.charAt(0));
原始值a是一个字符串,是一个不可变的值,如果在这个字面量上执行一些操作,比如获取长度,访问其中某个字符,需要将他转换为String对象,幸好,在必要时js会自动进行转换
null,undefined没有对应的构造函数,只有文字形式
对象包含一系列属性和方法,在引擎内部,存储在对象容器内部的是这些属性的名称,他们就像指针(引用)一样,指向这些值真正的存储位置
标识符命名规范:
下划线(_)、美元符($)、字母,数字(不能是首字符)组成,区分大小写,不能是js关键字(for if typeof)
属性访问方式:
. 操作符(属性访问),属性访问要求满足标识符命名规范
[ ] 操作符(键访问),键访问可以结构任意utf-8/unicode字符串作为属性名,例如访问user-name这种属性
var obj={
a:1,
"user-name":'tom'
}
obj.user-name;//NaN
obj["user-name"];//"tom"
属性名永远是字符串,如果使用string之外的其他值,会被自动转化为字符串
函数永远不会属于某个对象,所有把对象内部引用的函数称为方法
复制对象:
function anotherFunction(){
/**/
}
var anotherObject = {
c: true
}
var anotherArray = []
var myObject = {
a: 2,
b: anotherObject,//引用,不是复本
c: anotherArray,//引用
d: anotherFunction //引用
}
anotherArray.push(anotherObject, myObject)
对于浅拷贝来说:复制出的新对象中的a的值会复制旧对象中a的值,也就是2,但是新对象中b,c,d三个属性只是三个引用,他们和旧对象中b,c,d中引用的对象是一样的。
对于深复制来说,除了复制myObject以外还会复制anotherObject和anotherArray,这是问题就来了,anotherArray引用了anotherObject和myObject,所有又要复制myObject,这样由于循环引用导致死循环
对于JSON安全(可以被序列化为一个JSON字符串并且可以根据这个字符串解析出一个结构和值完全一样的对象)的对象来说,有一种巧妙的复制方法:
var newOBJ=JSON.parse(JSON.stringify(someObj))
这种方法需要保证json是安全的,所有只适用部分情况
相比深复制,浅复制非常易懂并且问题少的多,es6定义了Object.assign()方法实现浅复制,此方法的第一个参数是目标对象,之后还跟着一个或多个源对象,他会遍历一个或多个源对象的所有课枚举的自有键并把它们复制(使用=操作符赋值)到目标对象,最后返回目标对象,就像这样:
var newObj = Object.assign({},myObject);
newObj.a;//2
newObj.b === anotherObject;//true
newObj.c === anotherArray;//true
newObj.d === anotherFunction;//true
ps:Object.assign()使用=操作符赋值,所以源对象的属性的一些特性(比如writable)不会被复制到目标对象
属性描述符(数据描述符)
var obj = { a: 2 }
Object.getOwnPropertyDescriptor(obj, 'a');
//{
value: 2,
writable: true,
enumerable: true,
configurable: true
}
var obj2 = {}
Object.defineProperty(obj2,'b',{
value: 4,
writable: true,
enumerable: true,
configurable: true
})
obj2.b;//4
对象属性对应的属性描述符包含数据值,writable(可写),enumerable(可枚举),configurable(可配置)
writeable:决定是否可以修改属性的值,如果为false,obj.a=3//2,属性值修改失败了,严格模式会报TypeError错
configurable:只要属性是可配置的,就可以使用defineProperty()方法来修改属性描述符,
如果为false使用defineProperty()会产生一个TypeError错误,由true修改成false是单向操作,不可撤销。
如果此属性是false,writable还可以有true修改为false,但是无法由false修改为true。
如果此属性是false,还会禁止删除这个属性,delete obj.a//静默失败,obj.a//2
enumerable:控制属性是否出现在对象的属性枚举中,比如for...in循环,如果把此属性设置成false,这个属性不会出现在枚举中
用户定义的所有属性默认都会是true。
不变性
1.对象常量
结合writable:false.configurable:false可以创建一个真正的常量属性(不可修改、重定义、删除)
var obj = {}
Object.defineProperty(obj, "FAVORIFE_NUMBER",{
value: 1,
writable: false,
configurable: false
})
2.禁止扩展
如果想禁止一个对象添加新属性并且保留已有属性,可以使用Object.preventExtensions()
var obj = { a: 1 }
Object.preventExtensions(obj)
obj.b = 2
obj.b//undefined
非严格模式下,创建属性b会静默时报。严格模式下会报TypeError
3.密封
Object.seal()创建一个“密封”的对象,这个方法实际上会在一个现有的对象上调用Object.preventExtensions()并把所有属性标记为configurable:false,所有密封后不能添加新属性,不能重新配置和删除属性(虽然可以修改属性的值)
4.冻结
Object.freeze()会创建一个冻结的对象,这个方法会在现有方法上调用Object.seal(),并把所有数据访问属性标记为writable:false,这样就无法修改他们的值,这个是对象上级别最高的不可变性,他会禁止对于对象本身及其任意直接属性的修改(这个对象引用的其他对象是不受影响的)
[[Get]]
obj.a是一次属性访问,不仅仅是在obj中查找名字为a的属性,实际上是实现了[[Get]]操作(有点像函数调用[[Get]]()),对象默认的内置[[Get]]操作首先在对象中查找是否有名称相同的属性,如果找到就会返回这个属性的值。然而如果没找到,按照[[Get]]算法的定义会执行另外一种非常重要的行为,遍历其原型链,原型链上也没找到返回undefined。
var obj = { a: undefined }
obj.a //undefined
obj.b //undefined
b;//Uncaught ReferenceError: b is not defined
访问obj的a,b属性返回值都是undefined,看着没什么区别,但是底层的[[Get]]操作对obj.b进行了更复杂的操作
[[Put]]
[[Put]]被触发时,实际的行为取决于许多因素,包括对象中是否已经存在这个属性(这个是最重要的因素)
如果已经存在这个属性,[[Put]]算法大致会检查一下内容:
1.属性是否是访问描述符?如果是并且存在setter就调用setter
2.属性的数据描述符中writable是否是false,如果是,在非严格模式下静默失败,严格模式抛出TypeError
3.如果都不是,该值设置为属性的值
Getter和Setter
对象默认的[[Put]]和[[Get]]分别控制属性值的设置和获取
ES5中可以使用getter和setter部分改写默认操作,但是只能应用在单个属性上,无法应用在整个对象上。getter是一个隐藏函数,会在获取属性值时调用。setter也是一个隐藏函数,会在设置属性值时调用。
当你给一个属性定义getter、setter或者两者都有时,这个属性会被定义为“访问描述符”(和“数据描述符”相对)
对于访问描述符来说,js会忽略他们的value和writable特性,取而代之的是set和get(还有configurable和enumerable)
var obj = {
get a(){
return 2;
}
}
Object.defineProperty(obj, 'b', {
get:function(){
return this.a*2
},
enumerable: true
})
obj.a = 3//由于定义了getter,所以对a的值进行设置时set操作会忽略复制操作,不会抛出错误
obj.a//2
obj.b//4
两种定义方式都会在对象中创建一个不包含值的属性,对于这个属性的访问会自动调用一个隐藏函数,他的返回值会被当做属性访问的返回值。
setter会覆盖单个属性默认的[[Put]](也被称为赋值)操作
存在性
属性值返回undefined,有可能是不存在也有可能是存的值就是undefined,如何区分
var obj = { a:undefined }
('a' in obj)//true
('b' in obj)//false
obj.hasOwnProperty('a')//true
obj.hasOwnProperty('b')//false
in操作符会检查属性是否在对象及其原型链中。
obj.hasOwnProperty()只会检查属性是否在obj对象中。
枚举
for...in 会遍历可枚举的属性,查找原型链
obj.propertyIdEnumerable('a'),会检查给定的属性名是否直接存在于对象中并且满足enumerable:true。不查找原型链
Object.keys(obj),会返回一个数组,包含obj所有可枚举的属性。不查找原型链
Object.getOwnPropertyNames(obj),返回一个数组,包含obj所有属性,无论他们是否可枚举。不查找原型链
遍历
ES5中增加了一些数组的辅助迭代器,包含foreach(),every(),some(),每个迭代器都可接受一个回调函数并把它应用到数组的每个元素上,唯一的区别是他们对于回调函数的返回值的处理方式不同
foreach会遍历数组中的所有返回值并忽略回调函数的返回值
every()会一直运行直到回调函数返回false
some()会一直运行直到回调函数返回true
every和some中的特殊返回值和普通for循环中break语句类似,他们会提前终止遍历。
for(var i of arr),for...of具有迭代器的都可以使用此遍历