类型
值和类型
js中的变量是没有类型的,只有值才有。
undefined 和 undeclared 是不同的,前者是已经申明但没赋值,后者是未定义。
var a
a // undefined
b // ReferenceError: b is not defined 报错
var a
typeof a // undefined
typeof b // undefined 不会报错,typeof有安全机制
写polyfill有用
数组
delete 数组单元后,数组长度不会改变
数组下标可以传字符串,数字字符串会被转为数字类型(不建议使用)
字符串
字符串成员函数不会改变其原始值,会返回一个新的字符串。
let s = 'abc'
ss = s.toUpperCase()
ss === s // false
ss // ABC
// s.push('d') // 不能使用
可以使用数组形式取值,但不能使用数组形式修改对应的下标。
let s = 'abc'
s[1] // b
s.chatAt(1) // b
s[1] = d
s // abc
字符串可以借用数组的不可变更函数
let s = 'abc'
// s.join
// s.map
Array.prototype.join.call(a, '-')
Array.prototype.map.call(a, function (v) {
return v.toUpperCase() + '.'
}).join('')
对于无法使用数组可变更成员函数的,可以将字符串转数组,处理完后再转回来。
Array.prototype.reverse(s)
let ss = s.split('').reverse().join('')
ss
数字
let a = 33.22
// 小数位数
a.toFixed(3) // 33.220
// 有效位数
a.toPrecision(3) // 33.2
// 有效但不建议写法
22..toFixed(2) // 22.00
// 无效写法
22.toFixed(2) // SyntaxError
22 .toFixed(2) // 22.00
es6
2 8 16 进制
推荐字母小写
误差
// Number.EPSILON
0.1 + 0.2
function isNumberEqual(n1, n2) {
return Math.abs(n1 - n2) < Number.EPSILON
}
let a = 0.1, 0.2
let b = 0.3
isNumberEqual(a, b) // true
最大值最小值,安全值
Number.MAX_VALUE
Number.MIN_VALUE // 无限接近0的值
Number.MAX_SAFE_INTEGER
Number.MIN_SAFE_INTEGER
整数检测
Number.isInteger(22) // true
Number.isInteger(22.22) // true
Number.isInteger(22.2) // false
安全整数
Number.isSafeInteger(Math.pow(2, 53)) //false
Number.isSafeInteger(Math.pow(2, 53) - 1) //true
32位有符号整数,53改为31
Math.pow(-2, 31)
Math.pow(2, 31) - 1
特殊数值
undefined 可当作变量赋值
null 不可赋值
void 运算符
返回 undefined用
function getData() {
if(!api.ready) {
return void setTimeout(getData, 300);
}
return res
}
if (getData) {
// coding...
}
等价于
if(!api.ready) {
setTimeout(getData, 300);
return
}
NaN
a = 6/ 'a'
typeof a // number
a === NaN // false NaN不等于自身
b = 'a'
isNaN(a) // 是否为非数字的值
// true
isNaN(b)
// true
Number.isNaN(a) // 判断的是数字的操作,操作中值里面带有非数字的部分则为false
// true
Number.isNaN(b)
// false
无穷数
有穷数可以去无穷数,反之不行
计算移除结果为 Infinity 或者 -Infinity
var a = 1 / 0 // Infinity
Infinity/Infinity // NaN
1/Infinity // Infinity
零值
var a = 0 / -3 //-0
var b = 0 * -3 // -0
a.toString() // '0'
a + '' // '0'
String(a) // '0'
JSON.stringify(-0) // '0'
+'-0' // -0
Number('-0') // -0
JSON.parse('-0') // -0
a == 0 // true
a === 0 // true
-0 === 0 // true
0 > -0 // false
0 < -0 // false
1/a === -Infinity // true
特殊等式
var a = 2 / 'foo'
var b = -3 * 0
Object.is(a, NaN)
Object.is(b, -0)
能使用==、===就不要使用Object.is()。性能高些。
值和引用
标量基本类型值是值复制
复合值是引用复制
不能修改对方的引用,可以修改共同值
var a = [1,2,3]
var b = a
a[3] = 4
a // [1,2,3,4]
b // [1,2,3,4]
b = [5,6,7]
a // [1,2,3,4]
b // [5,6,7]
function fn(a) {
a.push(5)
console.log(a) // [1,2,3,4,5]
a = [1,2,3]
console.log(a) // [1,2,3]
}
fn(a)
a // [1,2,3,4,5]
修改引用
var a = [1,2,3,4]
function fn(a) {
a.push(5)
console.log(a) // [1,2,3,4,5]
a.length = 0 // 清空数组
a.push(1,2,3)
console.log(a) // [1,2,3]
}
fn(a)
a // [1,2,3]
fn(a.slice()) // 传递浅副本
封装标量值到复合值,从而修改变量值
var obj = {
a:1
}
function fn(wrapper) {
wrapper.a = 2
}
fn(obj)
obj.a // 2
原生函数
内部属性[[class]],内部的分类,基本类型值被称为被各自的封装对象自动包装
Object.prototype.toString.call(/abc/i)
// '[object RegExp]'
Object.prototype.toString.call([1,2,3])
// '[object Array]'
Object.prototype.toString.call({a:1})
// '[object Object]'
Object.prototype.toString.call(123)
// '[object Number]'
Object.prototype.toString.call('123')
// '[object String]'
Object.prototype.toString.call(true)
// '[object Boolean]'
Object.prototype.toString.call(null)
// '[object Null]'
Object.prototype.toString.call(undefined)
// '[object Undefined]'
封装对象包装
基本类型无.length
,.toString
属性和方法,需要通过封装对象才能访问
var a = 'abc'
a.length // 3
a.toUpperCase() // 'ABC'
不要提前自己封装,让引擎自己去操作,引擎会自己优化,自己去优化反而效率更低
自行封装对象基本类型值
var a = Object('abc')
typeof a // 'object'
a instanceof String // true
拆封
a.valueOf() // abc
原生函数作为构造函数
var a = new Array(3) // [empty*3]
var b = [undefined, undefined, undefined] // [undefined, undefined, undefined]
var c = [] // [empty*3]
var d = [,,,] // [empty*3]
var e = Array.apply(null, {length: 3}) // [undefined, undefined, undefined]
c.length = 3
a.join('-') // '--'
b.join('-') // '--'
a.map(function(v, i){return i}) // [0,1,2]
b.map(function(v, i){return i}) // [empty*3]
强制类型转换
- 返回基本类型
- 动态类型语言的进行时
- 将对象强制转化为string是通过ToPrimitive抽象完成的
ToString
let ary = [1,2,3]
ary.toString() // 1,2,3
JSON.stringify 字符串化,非强制类型转换
在对象中遇到 undefined, function, symbol 时会自动忽略,数组中则转为null
不安全的JSON:
类型:undefined,function,symbol和包含循环引用(对象之间互相引用,形成无限循环)
返回值:undefined
处理不安全JSON:
定义toJSON方法来返回一个安全的JSON值
var o = {}
var a = {
b:1,
c:o,
d: function(){},
e: [1,2,3]
}
o.f = a // 创建循环引用
// JSON.stringify(a)
a.toJSON = function() {
return {
b: this.b,
e: this.e.slice(1)
}
}
JSON.stringify(a)
ToNumber
Object.create() 返回的对象,没有 valueof() 和 toString() 函数,无法进行强制类型转换
对象转换:
- 转换为基本类型值
- 非基本类型值,则强制转换
强制转换
- 抽象操作ToPrimitive,内部操作DefaultValue
- 检查是否有 valueOf 函数
- 检查是否有 toString 函数
- 产生 TypeError 错误
console.log(+true) // 1
console.log(+false) // 0
console.log(+undefined) // NaN
console.log(+null) // 0
ToBoolean
假值对象
let a = new Boolean(false)
let b = new Number(0)
let c = new String('')
d = a && b && c // d String{''}
e = new Boolean(a && b && c) // true
document.all的历史以及被设置为假值以此废弃旧的代码判断,不再执行ie兼容程序。
字符串和数字互转
let a = '3.14'
let b = +a // 3.14
let c = 1 + - + + - + + 1 // 2 负负得正
// 转换日期 时间戳
let d = new Date([2022, 10,1])
+d // 不建议
let t = +new Date() // 不建议
Date.now() // 建议
let tt = new Date().getTime() // 建议
~
~:将对象转换为数字类型并且取反。返回2的补码。
~42 // -(42+1) -43
-1:哨位值
抽象渗漏:保留了底层的细节
~~
一般用来取代正数Math.floor()的处理
只适合32位
对负数处理与Math.floor()不同
~~12.1 // 12
~~-12.1 // -12
Math.floor(12.1) // 12
Math.floor(-12.1) // -13
// 按位操作速度快于函数
12.1 | 0 // 12
-12.1 | 0 // -12
// 同上可取代,parseInt(12.1, 10),parseInt(-12.1, 10)
解析数字字符串
转换和解析的不同
- 解析从左到右,遇到非数字字符就停止。
- 转换不允许出现非数字字符,否则会失败返回NaN.
let a = '22'
let b = '22px'
Number(a) // 22
parseInt(a) // 22
Number(b) // NaN
parseInt(b) // 22
parseInt的历史问题
es5前,下面的场景会出现问题:
let h = "08"
let s = '09'
let time = parseInt(h) + ':' + parseInt(s) // 0:0
parseInt(string, radix)
,string
的第一个字符会被默认为radix
的值,0开头即为8进制。
所以es5前需要parseInt('08', 10)
这样的写法。
es5后默认radix
为10,radix
的有效值为 0-9,a-i(区分大小写) 19最高
parseInt一般用来解析数字字符串,对于非数字字符串
对于非数字字符串,parseInt会先将其强制转换为字符串。
parseInt(1/0, 19) // 18
// =>
parseInt('Infinity', 19) // 解析了i,以19为基数时值为18.
!!
建议用Boolean或!!来让代码更清晰易读
// 强制转换JSON里面的数据
let a = [
1,
function() {}
]
JSON.stringify(a) // [1, null]
JSON.stringify(a, function(k, v) {
if(typeof v === 'function') {
return !!v
} else {
return v
}
}) // '[1, true]'
隐式强制类型转换的作用是减少冗余,让代码更简洁
var a = [1]
var b = [2,3]
a + b // '12,3'
规则:
- 如果其中一个是对象,则调用ToPrimitive抽象操作,调用[[DefaultValue]],转为数字的上下文。
- 如果无法使用valueOf取得基本类型值,则转而调用toString()。
- 以上操作通过则最后执行拼接操作。
所以以上执行数组相加,会先执行valueOf,由于无法获取基本类型值,所以将数组转为调用toString(),最后是字符串拼接。
也就是会呈现出先执行数字运算,后执行字符串运算的效果。
[] + {} // '' + '[object object]' = '[object object]'
{} + [] // 0
a + ‘’ 和 String(a)的不同
let a = {
valueOf: function() {
return 1
},
toString: function() {
return 2
}
}
a + '' // 1
String(a) // 2
a + ''
先调用valueOf
String(a)
直接调用toString
let a = [1]
let b = [2]
a - b // -1
布尔值
查看传入几个布尔值
隐式强制转换
function fn() {
var sum = 0
for(let i = 0; i < arguments.length; i++) {
if (arguments[i]) {
sum += arguments[i]
}
}
return sum === 1
}
fn(true, false, false) // true
显式强制转换
function fn() {
var sum = 0
for(let i = 0; i < arguments.length; i++) {
sum += Number(!!arguments[i])
}
return sum === 1
}
fn(true, false, false) // true
隐式类型转换为布尔值
- if
- while
- for 第二个条件
- ?:
- || &&
|| &&
选择器运算符
let a = 11
let b = 'abc'
let c = null
a || b // 11
a && b // 'abc'
c || b // 'abc'
c && b // null
function fn (a, b) {
a = a || 'hello'
b = b || 'world'
}
fn() // 'hello world'
fn('this is j', '') // 'this is j world'
function foo() {console.log(d)}
let d = 1
d && foo() // 1 短路
有和没有!!的差别
let a = 11
let b = null
let c = 'abc'
if(a && (b || c)) {
console.log('ok') // ok
}
这里(b || c) -> 'abc'
然后a && 'abc' -> 'abc'
最后'abc' => true
使用!!
let a = 11
let b = null
let c = 'abc'
if(!!a && (!!b || !!c)) {
console.log('ok') // ok
}
避免隐式转换
symbol属于特殊情况,暂时跳过
抽象相等
== 和 ===
性能不需要在意
区别:==允许在比较中隐式强制类型转换。===不允许。
数字和字符串比较,转为数字进行比较
var a = 11
var b = '11'
a == b // true
a === b // false
规则:
- type(x)是数字,type(y)是字符串,则x == ToNumber(y)
- type(x)是字符串,type(y)是数字,则ToNumber(x) == y
字符串和布尔值比较, 布尔值转为数字,布尔值优先级最高。
let a = '12'
let b = true
a == b // false
规则:
- type(x)是布尔类型,则ToNumber(x) == y
- type(y)是布尔类型,则x == ToNumber(y)
建议不要使用 == true, == false 操作。
undefined null
undefined == null
对象和非对象之间的相等比较
var a = [11]
var b = 11
a == b // true
var a = 'abc'
var b = Object(a)
a == b // true
a === b // false
var a = null
var b = undefined
var c = NaN
var x = Object(a)
a == x // false
var y = Object(b)
b == y // false
var z = Object(c)
c == z // false
null,undefined不能够被拆封,所以调用Object.create(null)返回对象
NaN被拆封为NaN,NaN == NaN => false
[] == ![] // true
如果在浏览器上输入[]则会是false,因为[]会先执行toString,转为’‘,最后才转为false。
根据类型判断,右边是布尔值。所以根据布尔值和其他类型规则判断,左边会转换为布尔值来和右边比较。
布尔值强制类型转换,即ToBoolean规则规定:undefined,null,false,’‘,+0,-0,NaN 是假值,其他为真值。
先执行![], 执行ToBoolean规则操作,[]为true,![]为false。
接着执行左边的[], []会调用内部的ToPrimiter去调用valueOf,valueOf因为[]是对象无法转为数字,所以交给toString去执行.
[]的toSting是’‘字符串,’'的强制类型转换为布尔值便是 false。
2 == [2] // true
'' == [null] // true
强制类型转换中,数组valueOf返回数组本身,所以数组最后会被字符串化。
0 == '\n' // true
'', ' ', '\n'
会被ToNumber转换为0
比较 没有严格比较
先调用ToPrimitive,是否是数字,否则则转为字符串,字符串按照字母顺序比较。
var a = [11]
var b = ['22']
a < b // true
var a = ['22']
var b = ['012']
a < b // false 字符串则按照字母顺序来比较
var a = [1,2]
var b = [0,3,4]
a < b // false
var a = {b:1}
var b = {b:1}
a < b // false,全部转为[object object]
a == b // false
a > b // false
a <= b // true 被处理为 b < a,然后结果反转。<= 实质是 !(a > b),处理为!(b < a)
a >= b // true
语法(这部分分多篇文章和例子独立出来)
异步和性能
将来与现在
分块的程序
事件循环
并行线程
并发
任务
语句顺序
回调
条件
顺序的大脑
信任问题
省点回调
promise
then 鸭子类型
promise 问题
- 调用过早
- 调用过晚
- 未调用
- 调用次数过少、过多
- 未能传递参数
- 吞掉异常
无法使用try-catch捕获异常 - 信任问题
链式流
错误处理
- 绝望的陷阱
- 未捕获的错误
- 成功的坑
promise模式
- Promise.all([])
- Promise.race()
- 变体
- 并发迭代
promiseApi 概述
- new Promise
- Promise.resolve
- then…catch…
- promise.all()
- promise.race()
promise缺点
- 顺序错误处理
- 单一值(不算缺点
- 单一决议
- 惯性
- 无法取消
- 性能
生成器
打破完整运行
- 输入和输出
- 多个迭代器
生成器生成值
- 生产者与迭代器
- iterable
- 生成器迭代器
异步迭代生成器
生成器+Promise
- 支持promise的Generator Runner
- 生成器中的promise并发
生成器的委托
- 为什么
- 消息委托
- 异步委托
- 递归委托
生成器并发
形实转换程序
之前的生成器
程序性能
性能测试与调优
高级异步模式
备注:
- 委托和promise及生成器需要反复阅读和练习查看效果,这里面的设计思想非常重要。
- 三本书前两本技术含量比较高,第三部基本是全面两本的复习和部分重复叙述(水文字凑数),建议大概快速翻看一下即可。生成器部分有增加了更详细的描述,不熟悉的话可以再次看下。