偶尔过一遍js知识
1.typeof
- 对于基本类型除了null都可以显示正确的类型
- 对于对象,除了函数都会显示object
- 对于null会显示object
可以使用Object.prototype.toString.call(xxx)获得正确类型
返回类似[Object Type]的字符串
2.类型转换
转Boolean
条件判断除了undefined,null,false,NaN,’’,0,-0 其他所有值都转为true
对象转基本类型:首先会调用valueOf然后调用toString。并且这两个方法可以重写。
Symbol.toPrimitive 也可以重写,该方法在转基本类型时调用优先级最高。
1 | let num = { |
四则运算符:加法运算时,其中一方时字符串类型就会把另一个也转化为字符串类型。
其他运算只要其中一方是数字,那么另一方就转为数字。
1
2[1,2] + [2,1] // '1,22,1'
// 加法会触发三种类型转换:将值转换为原始值,转换为数字,转换为字符串
3.原型
prototype
基本上所有的函数都有这个属性,除了Function.prototype.bind()创建的对象
声明一个函数时,这个属性就被自动创建了
这个属性的值是一个对象(也就是原型),只有一个属性constructor
constructor对应构造函数
1
2function Fn() {}
Fn.prototype // {constructor: ƒ}constructor是一个共有且不可枚举的属性。一旦我们改变了函数的prototype,那么这个对象就没有这个属性了。
1
2Fn.prototype = {a: 1}
Fn.prototype // {a: 1}constructor属性的作用
- 让实例对象知道是什么函数构造了它
- 如果想给某类库中的构造函数增加一些自定义的方法,就可以通过xx.constructor.method来扩展
proto
这是每个对象都有的隐式原型,指向创建该对象的构造函数原型
因为在js中没有类的概念,为了实现类似继承的方式,通过 proto 将对象的原型联系起来组成原型链,得以让对象可以访问到不属于自己的属性。
实例对象 proto 的 产生: 使用new操作符时,生成的实例对象拥有 proto __属性
1
2function Fn() {}
// 这个函数是Function的实例对象 function是个语法糖 内部调用了 new Function
总结
- Object是所有对象的爸爸,所有对象都可以通过 proto 找到它
- Function是所有函数的爸爸,同上
- Function.prototype和Object.prototype是两个特殊的对象,他们由引擎创建
- 除了以上两个特殊对象,其他对象都是通过构造器new出来的
- 函数的prototype是一个对象也就是原型
- 对象的 proto 指向原型, proto __将对象和原型链接起来组成原型链
4.New
- new的过程
- 新生成一个对象
- 链接到原型
- 绑定this
- 返回新对象
1 | // 调用new时发生的四件事,试着实现一个new |
new 运算符优先级
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function Fn() {
return this
}
Fn.getName = function () {
console.log('1')
}
Fn.prototype.getName = function () {
console.log('2')
}
new Fn.getName() // 1
new Fn().getName() // 2
-------------执行顺序-------------
new (Fn.getName())
(new Fn()).getName()
5.Instanceof
可以正确判断对象的类型,内部机制是通过判断对象的原型链中是不是能找到类型的prototype.
6.this
- 有对象调用: 指向调用它的对象
- 没有对象调用: 指向window
- new出来的实例: 指向实例
- call apply bind 改变指向传入的对象
箭头函数没有this,this指向取决于箭头函数外第一个不是箭头函数的函数
7.闭包
函数A返回一个函数B,并且B中使用了函数A的变量,函数B就被称为闭包
8.深拷贝浅拷贝
一个变量赋值一个对象,那么两者的值引用同一个,其中一方改变,另一个相应改变。实际业务重要解决这个问题
1.浅拷贝 assign
1 | let a = { |
扩展运算符 …
1 | let a = { |
浅拷贝只能解决第一层问题,如果接下去的值里还有对象,就需要深拷贝
2.深拷贝 JSON.parse(JSON.stringify(object))
局限
- 忽略undefined
- 不能序列化函数
- 不能解决循环引用的对象
如果有以上三种情况 可以使用lodash
的深拷贝函数
9.防抖和继承
防抖多次执行变为最后一次执行,节流每隔一段时间执行,不频繁执行
10.继承
ES5 Object.create()
1 | function Super() {} |
浏览器小结
1.Event loop
js执行过程中产生执行环境,这些执行环境会被顺序的加入到执行栈中。如果遇到异步代码,会被挂起并加入到Task队列中。一旦执行栈为空,Event Loop 就会从Task队列中拿出需要执行的代码并放入执行栈中执行
不同的任务源会被分配到不同的Task队列中,任务源可以分为
微任务(microtask) ES6中称为jobs 包括:
- process.nextTick
- promise
- Object.observe
- MutationObserver
宏任务(macrotask)ES6中称为task
- script
- setTimeout
- setInterval
- setImmediate
- I/O
- UI rendering
Event loop顺序
- 执行同步代码,这属于宏任务
- 执行栈为空,查询是否有微任务需要执行
- 执行所有微任务
- 必要的话渲染UI
- 然后开始下一轮Event loop,执行红任务中的异步代码
2.渲染机制
- 处理html并构建dom树
- 处理css构建cssdom树
- 将dom和cssdom合并成一个渲染树
- 根据渲染树的布局,计算每个节点的位置
- 调用GPU绘制, 合成图层,显示在屏幕上
3.重绘(Repaint)和回流(Reflow)
重绘是当前节点需要更改外观而不会影响布局,比如改变color就称为重绘
回流是布局或者几何属性需要改变
回流必定发生重绘,重绘不一定引发回流。回流所需的成本比重绘高的多,改变深层次的节点很有可能导致父节点一些了回流。
导致性能问题的几个点
- 改变 window 大小
- 改变字体
- 添加或删除样式
- 文字改变
- 定位或者浮动
- 盒模型