与某些语言把原始类型存储在栈中,把引用类型存储在堆中不一样,JavaScript使用一个变量对象来追踪变量的生存周期:原始值直接保存在这个对象内,引用类型的指针(指向这个引用类型在内存中的地址)被保存在这个对象内。数组
因为上面的缘由,当把一个引用类型赋值给一个变量时,只是把它的指针赋值给了这个变量。框架
解除对一个对象的引用的最好的方法是将对象变量赋值为null
,这样垃圾收集机制就能更好的处理无用的垃圾了。例如:函数
let pointer = {}; // 建立一个引用了对象的变量 pointer, 称为对象变量
// do something with pointer
pointer = null; // 将对象变量赋值为 null, 它引用的对象会被垃圾收集,那块内存就能空出来
复制代码
上面的代码用一个pointer
变量引用了一个对象,在将pointer
变量设置为 null
以后,垃圾收集器就能更好的处理那个已经没有了引用的对象。ui
对于全部的引用类型,使用字面量形式建立的对象并无调用构造函数,可是JavaScript引擎在背后作的工做和调用了构造函数时同样。例如对于一个普通对象:spa
// 使用对象字面量的方式建立一个对象
let person = {
name: '王大锤',
age: 30
};
/* 等价于下面使用构造函数建立的对象 */
// 使用构造函数建立一个对象
let person = new Object();
person.name = '王大锤';
person.age = 30;
复制代码
又例如对于一个数组:指针
let arr = [1, 2, 3]; // 使用字面量形式建立一个数组
/* 等价于下面使用构造函数的形式建立数组 */
let arr = new Array(1, 2, 3);
复制代码
使用Array.isArray
方法鉴定一个变量是否是数组,使用instanceof
操做符也能够判断一个变量的值是否是数组的实例,可是若是变量在同一个网页的不一样框架之间传递,因为每一个框架都有本身的环境,因此后者可能没法获得准确的结果,可是前者始终能获得正确的结果。code
let arr = [1, 2, 3];
console.log(Array.isArray(arr), arr instanceof Array); // true true
复制代码
对原始类型的值使用instanceof
操做符判断其对应的类型,总会返回false
,这是由于原始类型虽然有打包操做,可是在使用instanceof
进行判断时,打包操做就已经结束了,此时打包出来的临时对象已经被销毁,因此结果为false
对象
let name = 'Jack Ma';
let age = 40;
let flag = false;
console.log(name instanceof String); // false
console.log(age instanceof Number); // false
console.log(flag instanceof Boolean); // false
复制代码
函数存在一个被称为[[call]]
的内部属性,内部属性没法经过代码访问,这里使用双中括号来标注此类属性名。[[call]]
属性是函数的独有属性,并且typeof
操做符对具备[[call]]
属性的对象返回function
,这就是使用typeof
判断函数类型的原理。排序
函数声明能够被提高是由于引擎提早知道了函数的名字;函数表达式是使用匿名函数定义的,变量名只是引用了这个匿名函数,虽然能够经过变量对函数进行调用,但变量名并非函数的名字,因此没法进行提高。ip
sort
方法在排序的时候会将对象转换成字符串而后再比较,因此在不指定比较函数的时候不能对纯数字数组进行准确排序。
函数也有length
属性,表示函数指望的参数个数,也就是函数声明的形参个数。
函数和方法的区别:其实这两个名称指的都是函数,只是当一个函数是一个对象的属性时,相对于这个对象,函数就被称为了方法。
当属性***第一次***被添加给对象时,JavaScript会调用对象名为[[put]]
的内部方法来建立这个属性,并赋值。
当属性被添加给对象以后,再改变属性的值时,不会再调用再调用[[put]]
方法了,这时会调用[[set]]
这个内部属性。
let obj = {}; //定义一个对象,这个对象没有任何用户本身建立的属性
obj.name = 'Jack M'; // 调用了 [[put]] 方法,由于 obj 原本没有 name 属性,这里给它添加了 name 属性
obj.name = 'Mask'; // 调用了 [[get]] 方法,由于这时已经存在了name属性,这里只是从新给属性赋值
复制代码
若是想删除对象的某个属性,应使用delete
操做符。注意:直接将属性设置为null
是没法删除这个属性的,这样只是给属性赋了一个新值为null
:
// 建立一个带有 name 属性的对象
let obj = {
name: 'Yuri'
};
// 将 name 属性值设为 null,没法删除这个属性,只是给它赋值为 null, 即 obj.name === null
obj.name = null;
console.log(obj.name === null); // true
// 使用 delete 关键词能够真正删除属性
delete obj.name;
console.log(obj.name); // undefined
console.log('name' in obj); // false
复制代码
想遍历对象的属性时,能够用两种方法:
for ... in ...
:迭代对象的可枚举的***属性名***,可枚举属性是指[[Enumerable]]
值为 true
的属性Object.keys(object)
:这个方法返回对象的全部可枚举属性的属性名组成的数组。// 建立一个带有两个自有属性的对象
let obj = {
name: 'Yuri',
age: 40,
speak: 'You will obey ...'
};
for(let propertyName in obj){ // 每次都会将 对象的属性名赋值给 propertyName
console.log(propertyName);
}
// name
// age
// speak
let allProperties = Object.keys(obj); // 得到 obj 对象的全部可枚举属性的属性名
console.log(allProperties); // ["name", "age", "speak"]
复制代码
注意:这两个方法取得的可枚举属性是有差异的,for in
方法会遍历对象的原型链,而Object.keys()
方法只会涉及到对象自己的属性,不会访问原型链。
对象的大部分自带的属性的[[Enumerable]]
的值都是false
,即不可遍历,用实例对象的propertyIsEnumerable
方法能够判断一个属性是否是可枚举的:
console.log(obj.propertyIsEnumerable('name')); // true
console.log(obj.propertyIsEnumerable('age')); // true
console.log(obj.propertyIsEnumerable('speak')); // true
复制代码
使用构造函数建立对象时,若是不须要传递参数时,能够不加小括号:
// 定义一个构造函数
function Person(name){
/* xxx */
}
let p1 = new Person();
let p2 = new Person; // 不加小括号
console.log(p1 instanceof Person, p2 instanceof Person); // true true
复制代码