- 首发于简书
Chapter1 原始类型和引用类型
- 什么是类型?如何鉴别类型? - 本章的主要内容。
JS有两种基本数据类型:原始类型(
string,number,boolean,null,undefined
)和引用类型(内建类型:Array,Date,Error,Function,Object,RegExp
)。原始类型保存为简单数据值,引用类型则保存为对象,本质是指向内存位置的引用。尽管原始类型拥有方法,但它们不是对象。JS使它们看起来像对象一样,以此来提供语言上的一致性体验。
鉴别原始类型:
typeof
操作符可用于鉴别string,number,boolean,undefined
类型,如console.log(typeof "helloworld");
返回结果为string
。 运行console.log(typeof null);
的结果是object
,这已经被设计和维护JavaScript的TC39委员会认定是一个错误。判断一个值是否为空类型的最佳方法是直接与null比较,即console.log(value === null);
注意三等号在进行比较时不会将变量强制转换为另一种类型,双等号会。
鉴别引用类型:对函数使用
typeof
操作符,返回值是function
,而对非函数的引用类型,返回值均为object
。可使用instanceof
操作符鉴定引用类型,如console.log(function1 instanceof Function);
返回值为true
。
string
类型有很多方法,可以帮助你更好地使用它们number
和boolean
类型也有方法,null
和undefined
没有。- 最好在不使用对象时将其引用解除 解除引用的最佳手段是将对象变量置为
null
,如object1 = null;
。在那些使用几百万对象的巨型程序中,对象引用解除尤为重要。 - 在需要动态访问属性时,使用中括号访问JavaScript对象的属性特别有用 除了语法不同,在性能或其他方面点号和中括号都大致相同,唯一区别在于中括号允许你在属性名字上使用特殊字符。
- 在JS中,除非需要通过一个或多个字符串动态构造正则表达式,否则都建议使用字面形式而不是构造函数 总之,除了函数,对内建类型没什么正确或错误的实例化方法。你可以选择更舒服的那种。
Array.isArray()
是鉴别数组的最佳方法 把一个数组从一个框架传到另一个框架,instanceof
操作符就无法识别它。无论该值来自哪里,Array.isArray()
对来自任何上下文的数组都返回true
,如console.log(Array.isArray(array1))
。- JS提供了3种原始封装类型:
String,Number,Boolean
,它们属于特殊引用类型 JS会在背后创建这些对象,但这些临时对象在使用它们的语句结束后就立刻被销毁,也就是说临时对象仅在值被读取时创建。手动创建原始封装类型实际会创建出一个object
,这意味着typeof
操作符无法鉴别出你实际保存的数据的类型。而一个对象在条件判断语句中总被认为是true
。除非有特殊情况,否则应避免手动创建原始封装类型。
Chapter2 函数
- 讨论在JS中定义和执行函数的各种方法 - 本章的主要内容。
定义函数:有两种定义函数的字面形式 - 函数声明(如
function add(num1,num2){return num1 + num2;}
)和函数表达式(如var add = function(num1,num2){return num1 + num2;};
)。JS会对函数声明进行提升,因为JS引擎提前知道了函数名称。而函数表达式仅能通过变量引用,因此无法提升。只要始终在使用函数前定义它,函数声明或表达式就可随意使用。
执行函数:函数就是对象,基本上只要是可以使用对象的地方,你就可以使用函数,如作为其他函数的参数,或作为其他函数的返回值等等。JS函数的另一个特殊之处是可以给函数传递任意数目的参数却不造成错误。函数参数实际上保存在一个类似数组的叫做
arguments
的对象中。函数还有length
属性,表明了函数的期望参数个数。
arguments
对象自动存在于函数中arguments
可以自由增长来包含任意个数的值,这些值可通过数字索引来引用(如arguments[i]
),它的length
属性表明有多少个值。arguments
不是一个数组的实例,Array.isArray(arguments)
永远返回false
。在某些情况下,使用arguments
比命名参数更有效,如不知道函数会接受多少个参数的情况。- JS根据实际传入的参数决定调用函数的哪个版本 可以用
arguments
对象获取函数传入的参数个数,并决定怎么处理,这是模仿函数重载。实际使用中,检查命名参数是否未定义比使用arguments
更常见。如果还想检查不同数据类型,可以使用typeof
和instanceof
操作符。 - JS所有的函数作用域内都有一个
this
对象代表调用该函数的对象 当函数作为对象的方法被调用时,默认this
的值等于那个对象。在全局作用域中,this
代表全局对象(浏览器的window
)。 - 修改
this
值有三种函数方法call()
- 第一个参数指定了函数执行时this
的值,其余所有参数均是传入函数的参数;;apply()
- 它只接受两个参数,分别是this
的值和一个数组或类似数组的对象,内含传递给函数的参数;bind()
- 第一个参数是传递给新函数的this
值,其余所有参数均是永久设置在新函数中的命名参数; - JS函数和其他对象最大的区别在于它们有一个特殊的内部属性
[[call]]
typeof
操作符会在对象内查找该属性,如果找到,返回function
。
Chapter3 理解对象
- 定义属性与删除属性,属性探测与属性枚举,数据属性与访问器属性的特征,禁止修改对象的三种方式 - 本章的主要内容
当一个属性第一次被添加给对象,JS在对象上调用
[[Put]]
方法,该方法会在对象上创建一个新节点来保存属性。调用[[Put]]
方法的结果是在对象上创建了一个自有属性。注意,你声明的所有属性默认是可配置和可遍历的。当一个已有属性被赋予新值,调用[[Set]]
方法,该方法将属性当前值置换为新值。delete
操作符针对单个对象属性调用名为[[Delete]]
的内部方法。
in操作符不会评估属性的值,会同时检查自有属性和原型属性是否存在。检测自有属性可用Object.hasOwnProperty()
方法。可枚举属性的内部特征[[Enumerable]]
被设置为true
,反之为false,比如对象的大部分原生方法的该特征就被置为了false
。for in
枚举自有属性和原型属性,Object.keys()
只获取对象的自有属性。
属性有数据属性和访问器属性两种类型。数据属性包含一个值,访问器属性不包含值而是定义了一个当属性被读取或写入时调用的函数(
getter
或setter
)。以上两种属性的通用特征是[[Enumerable]]
和[[Configurable]]
,分别表示可遍历和可配置。数据属性的独有特征是[[Value]]
和[[Writable]]
,访问器属性的独有特征是[[Get]]
和[[Set]]
。获取属性特征可使用Object.getOwnPropertyDescriptor()
方法。可使用Object.defineProperty()
定义单个属性,Object.defineProperties()
可定义多个属性。
对象也有内部特征,
[[Extensible]]
表示该对象本身是否可以被修改。注意,你创建的所有对象默认是可修改的。禁止修改对象有三种方式,一是Object.preventExtensions()
,在某个对象上使用该方法后就无法给对象添加新属性。二是Object.seal()
,使用后对象的所有属性均不可配置,只能读写属性。三是Object.freeze()
,使用后只能读取对象的数据属性,无法写入和配置属性。
Chapter4 构造函数和原型对象
- 理解构造函数,原型对象和对象实例间的关系 - 本章的主要内容
构造函数也是函数,唯一的区别是应该首字母大写。使用构造函数的好处是所有用同一构造函数创建的对象都具有同样的属性和方法。可以用构造函数属性(
.constructor
)来检查一个对象的类型,但还是建议使用instanceof
来检查对象类型,因为构造函数属性可以被覆盖。构造函数允许你给对象配置同样的属性,但它并没有消除代码冗余。
几乎所有的函数都有一个名为
prototype
的属性,该属性是一个原型对象,用来创建新的对象实例。鉴别原型属性的方法:name in object && !object.hasOwnProperty(name)
。注意,使用对象字面形式改写原型对象改变了构造函数属性(.constructor
),为避免这一点,需要在改写原型对象时手动重置其constructor
属性。构造函数和对象实例通过原型对象相连。
当在一个对象上使用
Object.seal()
或Object.freeze()
,虽然无法添加和改写被冻结对象的自由属性,但仍然可以通过在原型对象上添加属性来扩展这些对象实例。原因是[[Prototype]]
属性是对象实例的自有属性,属性本身被冻结,但其指向的值(原型对象)并没有冻结。当然,内建对象的原型对象也可以改变。