原始类型(6⃣️个)
null、undefined、number、string、boolean、symbol。
- string类型永远不可变
- 原始类型存储的都是值,是没有函数可以调用的。(看上去可以调用都是强转)
typeof null
会输出object
,但是这只是 JS 存在的一个悠久 Bug。- 在条件判断时,除了
undefined
,null
,false
,NaN
,''
,0
,-0
,其他所有值都转为true
,包括所有对象。
typeof
有undefined、number、string、boolean、symbol、object、function这几个可能。
闭包
缺点:内存泄漏。
循环用闭包的bug,以及三种解决方案 :
(注意,for循环里面用let定义就不会出现用var定义的bug问题啦)
https://juejin.im/book/5bdc715fe51d454e755f75ef/section/5bed40d951882545f73004f6
for循环如果用let定义i。当前的
i
只在本轮循环有效。所以每一次循环的i
其实都是一个新的变量。let
声明的变量只在它所在的代码块有效。
以下两个case都是闭包,都返回local:
var scope = 'global';
function check() {
var scope = 'local';
function f(){
return scope;
}
return f();
}
check();
var scope = 'global';
function check() {
var scope = 'local';
function f(){
return scope;
}
return f;
}
check()();
深拷贝
方法一:
JSON.parse(JSON.stringify(object))
有局限性:
- 会忽略
undefined
、symbol
、函数 - 不能解决循环引用的对象
方法二:
如果所需拷贝的对象含有内置类型并且不包含函数,可以使用 MessageChannel
(异步)
MessageChannel https://zhuanlan.zhihu.com/p/37589777
方法三:
手写:
function deepClone(obj) {
function isObject(o) {
return (typeof o === 'object' || typeof o === 'function') && o !== null
}
if (!isObject(obj)) {
throw new Error('非对象')
}
let isArray = Array.isArray(obj)
let newObj = isArray ? [...obj] : { ...obj }
Reflect.ownKeys(newObj).forEach(key => {
newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
})
return newObj
}
变量提升
函数会被提升,并且优先于变量提升。
函数提升会把整个函数挪到作用域顶部,变量提升只会把声明挪到作用域顶部。
var
存在提升,我们能在声明之前使用。let
、const
因为暂时性死区的原因,不能在声明前使用。
var a = 1
function test(){
console.log(a)
let a
}
test() // 会报错(暂时性死区)
原型
关于内建函数没有原型属性:https://stackoverflow.com/questions/14169384/why-do-built-in-functions-not-have-a-prototype-property
箭头函数, 异步函数, 迭代器, ... 都没有prototype。
继承
组合继承
(child原型是father的实例)
缺点就是在继承父类函数的时候调用了父类构造函数,导致子类的原型上多了不需要的父类属性,存在内存上的浪费。
寄生组合继承
Child.prototype = Object.create(Parent.prototype, { constructor: { // 创建一个新的属性叫“constructor,指向Child构造函数 value: Child, enumerable: false, writable: true, configurable: true } })
对象的属性描述符
对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。(对应数据属性和访问器属性)
- 数据描述符是一个拥有可写或不可写值的属性。
- 存取描述符是由一对 getter-setter 函数功能来描述的属性。
描述符必须是两种形式之一,不能同时是两者。
模块化
优点:
- 解决命名冲突,避免全局污染
- 提供复用性
- 提高代码可维护性
实现方案:
- 立即执行函数
- AMD/CMD(略)
- commonjs (exports = module.exports ) 支持动态载入、同步导入、值拷贝(若更新要重新导)
- ES Module 不支持动态载入、异步导入、实时绑定
- TS Module TODO
arguments
函数的局部变量。
arguments: 一个"类数组"的对象,包含了传入当前函数的所有实参.
arguments.callee : 指向当前正在执行的函数.
arguments.caller : 指向调用当前正在执行的函数的函数,请使用Function.caller代替
arguments.length: 传入当前函数的实参个数,也可以是Function.length。
当函数作为入参传递的时候,可以直接用其参数作为key做诸如记忆函数的事情。
转换为数组的方法
Array.prototype.slice.apply(xxx)
[...document.querySelectorAll('div')] // Symbol.iterator
并发/并行
- 并发(concurrency):任务 A 和任务 B,在一段时间内通过任务间的切换完成了这两个任务,这种情况就可以称之为并发。
- 并行(parallelism):假设 CPU 中存在两个核心,同时完成多个任务的情况就可以称之为并行。
浏览器的各种高度
https://www.cnblogs.com/lvdabao/articles/3651779.html
https://www.cnblogs.com/wuyuchao/p/6134663.html(内有兼容性获取的写法)
lodash
get如果取不到值的时候,默认是返回undefined
命令行常用
commander、chalk、inquirer、glob、esm、minimist
线程/进程
把这些概念拿到浏览器中来说,当你打开一个 Tab 页时,其实就是创建了一个进程,一个进程中可以有多个线程,比如渲染线程、JS 引擎线程、HTTP 请求线程等等。当你发起一个请求时,其实就是创建了一个线程,当请求结束后,该线程可能就会被销毁。
垃圾回收
- 新生代算法(存活时间短的对象)from/to
- 老生代算法(存活时间长的对象)
- 标记清除算法
- 标记压缩算法
数学计算
为什么 0.1 + 0.2 != 0.3? js是64位运算。
在输入内容的时候,二进制被转换为了十进制,十进制又被转换为了字符串,在这个转换的过程中发生了取近似值的过程,所以打印出来的其实是一个近似值。
console.log(0.100000000000000002) // 0.1
另外还有个坑是toFiexed
https://stackoverflow.com/questions/21091727/javascript-tofixed-function
https://www.594cto.com/content/1f4b68a052df40f98231a349efa93915
解决方法是:1. 转字符串处理; 2. Math.round 3. bigNumber
defer/async
https://www.growingwiththeweb.com/2014/02/async-vs-defer-attributes.html
如果想首屏渲染的越快,就不应该在首屏就加载 JS 文件,这也是都建议将 script
标签放在 body
标签底部的原因。或者也可以加上defer/async。