《JavaScript 悟道》 笔记
2 数值
Math.round
用于四舍五入小数, 返回一个整数. (单词round
用作动词, 表示 绕过;拐过;使成圆形;把…四舍五入)Math.ceil
返回一个比传入参数大的整数. (单词ceil
, 作为动词, 表示 在…装天花板;在…装壁板;在(木船)上装船底隔板)Math.floor
返回的一个恰比传入参数小的整数Math.trunc
返回的则是恰比传入参数更接近0
的整数
Math.floor
和 Math.trunc
都是将数转换为整数的函数。不同的是,Math.floor
返回的 是一个恰比传入参数小的整数,而 Math.trunc
返回的则是恰比传入参数更接近 0
的整数。
Math.round(3.3) // 3
Math.round(-3.3) // -3
Math.round(3.7) // 4
Math.round(-3.7) // -4
Math.ceil(3.4) // 4
Math.ceil(-3.4) // -3
Math.floor(2.5) // 2
Math.floor(-2.5) // -3
Math.trunc(2.5) // 2
Math.trunc(-2.5) // -2
6 布尔类型
JavaScript 无法精确表示绝大多数小数,0.1
就是其中之一, 比如 0.1 + 0.2
返回是 0.30000000000000004
, 所以除非你肯定值的大小在安全整数范围内,否则不要通过 ===
的条件判断来结束循环。即使在安全整数范围内,也还是推荐使用 >=
。
任意数值与 NaN
进行任意比较都会返回 false
。
7 数组
7.3 栈与队列
pop
方法返回数组中的最后一个元素,并将其从数组中移除push
方法则将传入的值附加到数组的末尾shift
方法移除并返回的是数组中的第零个元素unshift
方法将元素插入数组的开头
就性能来说, shift
和 unshift
比 pop
和 push
差很多, 这种差异在数组很大的时候尤为明显。当我们将 shift
和 push
一起使用的时候,数组就会像一个队列——先进先出。
7.6 遍历
every
用于测试数组中所有元素是否通过了测试(return truthy
), 接受一个回调函数(签名如下).
- 如果回调函数返回 falsy 值(未通过测试), 则停止遍历, 并且
every
函数返回false
. - 如果回调函数返回 truthy 值(通过测试), 则继续遍历, 如果全部遍历都返回 truthy, 则
every
函数返回true
.
Array.prototype.every = function(element, index, array) {
if(break) {
return false;
}
if(continue) {
return true;
}
}
some
方法与 every
类似。用于测试数组中至少有一个通过了测试(return truthy
). 区别是 some
的回调函数返回 truthy 停止遍历(表示有一个通过了测试), 返回 falsy 继续遍历(表示未通过测试).
find
方法与 some
类似,只不过返回值不同, some
(包括 every
)返回值都是 true
或者false
. 而find
的遍历函数返回truthy的时候,直接返回当前处理的元素。
findIndex
方法与 find
类似,只不过返回的不是一个元素,而是相应元素的序号。
7.7 排序
sort
排序方法是原地生效的, 会修改原数组. 也就是说 sort
方法无法对冻结数组进行排序, 而且对共享型数组(shared array)进行排序操作也不安全。
sort
的回调函数在比较大小的时候,如果认为第一个参数应当排在第二个参数前面,则自定义比较函数需要返回一个负数; 如果第二个参数应当排在前面,则需要返回正数;如果自定义比较函数返回0,则表示比较函数无法判断孰大孰小。
sort
方法还有一个比较严重的问题,那就是缺乏稳定性。在比较两个相等值的时候(比较函数会返回 0),如果排序方法将这两个值排在它们原来的位置上,则可以说该排序方法是稳定的。然而 JavaScript 的 sort
方法不具备这种稳定性。尽管这种稳定性对于排序字符串或者数值数组的时候并不那么重要,但当我们需要对一个存储对象或者数组的数组进行排序的时候,就另当别论了。想象一下,你正在对姓名进行排序:先判断姓的顺序,若姓相同再判断名的顺序。如果 sort
方法稳定,我们就可以这么做:先对姓排一遍序,结束后再对名排一遍序。可惜的是 JavaScript 并不具备该稳定性,第二遍排序会把第一遍已经排好的姓打乱。
7.9 数组方法的纯度
不会修改数组本身的方法
- concat
- every
- filter
- find
- findIndex
- forEach
- indexOf
- join
- lastIndexOf
- map
- reduce
- reduceRight
- slice
- some
会修改数组本身的方法
- fill
- pop
- push
- shift
- splice
- unshift
- reverse
- sort
8 对象
8.3 继承
Object.create(prototype)
可以将一个已有的对象继承到一个新对象中。该已有对象将作为新对象的原型。
如果我们访问在某一个对象上不存在的属性,JavaScript 会在返回 undefined
之前尝试在该对象的原型上寻找对应属性,那么该属性就会被返回,好像它就在我们正在访问的对象上一样。
当我们对一个对象的内容赋值的时候,被修改的只是原型链顶端的对象(包括delete
),原型链中间的属性并不会被修改:
const parent = {age: 10};
const child = Object.create(parent);
child.age += 1;
console.log(child.age); // 11
delete child.age;
console.log(child.age); // 10
JavaScript 对象有自有属性(own property)和来自原型链的继承属性(inherited property). 大多数对象继承了 hasOwnProperty(string)
方法可以获取自有属性,然而这个方法其实有可靠性风险, 可以被用户代码覆盖.
JavaScript 的继承模式还有个问题,那就是意外继承。举例来说,你可能想将对象作为哈希表(字典)来用,但是它又继承了 toString
和constructor
这些属性,以及一些其他有可能继承的属性。这很可能与你自己想赋予的各种属性混淆。可以使用 Object.create(null)
创建一个“纯”对象,该对象没有任何继承属性。
8.4 键名
Object.keys(object)
函数会将传入对象的所有自有属性(不包括继承属性)的键名作为字符串放入一个数组中并返回。键名在数组中的顺序是按属性的插入时间来排列的。
8.5 冻结
Object.freeze(object)
可以将对象冻结,使其成为不可变(immutable)对象。
Object.freeze(object)
和 const
表达式做的是完全不同的事。 Object.freeze
作用于值,而 const
则作用于变量。如果将可变对象赋值给一个 const
变量(常量),你还是可以改变这个对象里的内容,但是无法为这个变量赋另一个值。如果将不可变对象赋值给一个普通的变量,那么你将不能改变变量里的内容,但是可以为变量赋另一个值。
8.6 莫使冻结共原型
如果对象原型中的一个属性是不可变的,那么该对象就无法拥有同名的自有属性了。修改从冻结原型继承过来的属性会触发异常。此外,这种形式的继承会降低插入新属性的性能。每当我们插入新属性的时候,系统会在整条原型链中搜索是否存在该不可变属性。
经过测试发现, 修改冻结原型集成过来的属性不会触发异常, 只是修改不会成功, 比如以下代码:
const parent = Object.freeze({ age: 10 });
const child = Object.create(parent);
child.age += 1; // 这个地方并不会抛异常
console.log(child.age); // 10
9 字符串
我们可以通过 String.fromCharCode([...])
函数来创建字符串。字符串中的元素可以通过 charCodeAt
访问.
startsWith
、 endsWith
和 contains
三个方法是对 indexOf
和 lastIndexOf
这两个方法的包装。