0%

回流reflow和重绘repaint

回流

第一次确定节点的大小和位置,称为布局

后续对节点的大小、位置修改并重新计算称为回流

引起回流的情况

  • DOM结构发生变化(添加或删除节点)
  • 改变布局(修改width、height、padding、font-size等)
  • 修改窗口大小
  • 调用getComputedStyle方法获取尺寸、位置星系、

避免回流的方法

修改样式时尽量一次性修改,比如通过cssText修改,或者通过添加class修改,而不是对css的各种属性分次修改

避免频繁的操作DOM,可以使用虚拟DOM,或在父元素中要操作的DOM完成,再一次性更新到DOM中

composite合成

绘制的过程中,浏览器会将布局后的元素绘制到多个图层中

默认情况下,标准流中的内容被绘制在同一个图层,对于某些特殊的属性,会创建一个新的图层,这些图层可以利用GPU加速绘制

可以形成新的合成层的属性:3D transforms、video、canvas、iframe、opacity、position:fixed、will-change、animation或transition设置了opacity、transform

分层确实可以提高性能,但以内存管理为代价,因此不能作为Web性能优化的一部分过渡使用

重绘

第一次渲染内容称为绘制

后续重新渲染称为重绘

引起重绘的情况

修改背景颜色、文字颜色、边框颜色、样式等

注:回流一定引起重绘、因此回流是一件很消耗性能的事情,所以开发中要避免回流

网页解析过程:

image-20230322214252101

image-20230322215014488

HTML解析过程

默认情况下服务器给浏览器返回index.html文件,因此解析HTML是所有步骤的开始,解析HTML过程会构建Dom Tree

image-20230322215643759

生成CSS规则

在解析过程中,如果遇到CSS的link元素,会有浏览器下载对应的CSS文件(不会影响DOM)的解析

对CSS下载完成后,会对CSS文件进行解析,解析出规则树,即CSSOM

image-20230322220941332

构建Render Tree

当DOM Tree和CSSOM Tree有了过后就可以两者结合构建Render Tree了

image-20230322221500301

注:link元素不会阻塞DOM Tree的构建,但会阻塞Render Tree的构建

​ Render Tree和Dom Tree并不是一一对应的关系,对于display为none的元素,不会出现在Render Tree中

布局和绘制

渲染树会显示每个节点的样式,但不会给出每个节点的尺寸和位置,布局就是确定渲染树中每个节点的宽度、高度和位置信息

绘制就是将布局阶段计算的每个frame转为屏幕上实际的像素点,包括将元素的可见部分进行绘制,比如文本、颜色、边框、阴影、替换元素

script元素和页面解析的关系

在HTML解析过程中,遇到script元素是不能继续构建DOM Tree的;会首先下载JavaScript代码,并执行脚本,等脚本执行结束后,继续解析HTML,构建DOM Tree

原因: JavaScript的一个作用是操作DOM,并修改DOM,如果等DOM Tree构建完成后执行JavaScript,会引起严重的回流和重绘,影像性能

但是: 目前的Vue/React开发模式,脚本比HTML页面更重,等到JavaScript解析会造成页面阻塞,在脚本执行完成钱,用户在界面上看不到任何东西

script解析优化:defer、async

defer: 浏览器不需要等到脚本下载,继续解析HTML,构建DOM Tree

​ 不会阻塞DOm Tree的构建,等到DOM Tree构建完成后,在DOMContentLoaded事件之前执行代码

​ 带多个defer的脚本按顺序执行

​ 从某种角度来说,defer可以提高性能,并且推荐放到head元素中

​ 注:defer仅适用于外部脚本,对于script默认内容会忽略

async: 脚本完全独立

​ 浏览器不会因async阻塞

​ async不能保证不同脚本之间的顺序,独立下载、运行

​ async不能保证在DOMcontentLoaded之前或之后运行

defer通常用于文档解析后会操作DOM的JavaScript代码,并且对多个script文件有顺序要求的

async通常用于独立的脚本,对其他脚本,DOM没有依赖的

回流和重绘(见浏览器优化)

jQuery是一个快速、小型且功能丰富的JavaScript

优点:丰富的功能(DOM操作、过滤器、时间、动画、Ajax等)、编写更少可读的代码提高开发人员的工作效率、跨浏览器支持(IE9+)

缺点:jQuery代码库一直在增长(jQuery1.5超过200KB)、不支持组件化开发、jQuery更适合组件化开发,当涉及到复杂的项目时,能力有限

jQuery使用

jQuery监听文档加载

1
2
3
4
$( document ).ready( handler )  :  //deprecated 
$( "document" ).ready( handler ) : //deprecated
$().ready( handler ) ://deprecated
$( handler ) ://推荐用这种写法,其它可以使用但是不推荐

jQuery与其他库变量名冲突(jQuery别名$的冲突)

1
jQuery.noConflict()

jQuery对象(类数组对象——可通过索引访问)

  • 如果传入假值:返回一个空的集合。
  • 如果传入选择器:返回在在documnet中所匹配到元素的集合。
  • 如果传入元素:返回包含该元素的集合。
  • 如果传入HTML字符串,返回包含新创建元素的集合。
  • 如果传入回调函数:返回的是包含document元素集合, 并且当文档加载完成会回调该函数。
  • 因为函数也是对象,所以该函数还包含了很多已封装好的方法。如:jQuery.noConflict、jQuery.ready等
1
2
3
4
5
6
const obj = {
"name":"oww"
}
const obj1 = $() // 创建空对象
const obj2 = $(obj) // 通过j对象创建jQuery对象
const obj3 = $('li') // 通过选择器创建jQuery对象

jQuery对象与Element对象的区别:jQuery会包含所选择到的对象,可以通过$(element)和obj3[index]之间相互转换,转换后具有不同的函数

jQuery选择器

常规选择器:通用选择器(*)、属性/后代/基本(id、class、元素)/兄弟/交集/伪元素(伪类不行)/可见选择器(:visible,:hidden)、jQuery扩展选择器(:eq(),:odd,:even:first:last)—eq是用索引、odd奇数、even偶数

jQuery过滤器

  • eq(index): 从匹配元素的集合中,取索引处的元素, eq全称(equal 等于),返回jQuery对象。
  • first() : 从匹配元素的集合中,取第一个元素,返回jQuery对象。
  • last(): 从匹配元素的集合中,取最后一个元素,返回jQuery对象。
  • not(selector): 从匹配元素的集合中,删除匹配的元素,返回jQuery对象。
  • filter(selector): 从匹配元素的集合中,过滤出匹配的元素,返回jQuery对象。
  • .find(selector): 从匹配元素集合中,找到匹配的后代元素,返回jQuery对象。
  • is(selector|element| . ): 根据选择器、元素等检查当前匹配到元素的集合。集合中至少有一个与给定参数匹配则返回true。  8.odd() :将匹配到元素的集合减少为集合中的奇数,从零开始编号,返回jQuery对象。
  • even():将匹配到元素的集合减少到集合中的偶数,从零开始编号,返回jQuery对象。
  • 支持链式调用
  • on添加监听
1
2
3
4
5
6
7
const $li = $('li')
$li.click((event)=>{
console.log("1", event)
})
$li.on('click',(event)=>{
console.log("2", event)
})

jQuery函数

jQuery对文本的操作

.text()、.text(text)

1
2
3
4
5
6
7
8
9
10
11
<div class="active list div">
<li><p>1</pp></li>
<li value="2">2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
<li><span>9</span></li>
</div>
1
2
3
4
const $li = $('li')
console.log(typeof $li.text()) // string 会把其本身和子元素的text拼接为1个string
console.log($li.text()) // 123456789
$li.text("aaa") // 更改所有选择元素的text

.html、.html(htmlString)

1
2
3
const $li = $('li')
console.log($li.html()) // <p>1</p> 获取选中第一个元素的html
$li.html("<span>10<span>") //给所有选中元素设置innerHtml

.val()、.val(value)

1
2
3
const $li = $('li')
console.log($li.odd().val()) // 2 获取选中第一个元素的value
$li.val(12)

jQuery对css的操作

.width()、.height()、.css(prppertyName) 获取选中第一个元素的指定样式属性值

.css(propertyName,value)、.css({propertyName1:value1,propertyName2,value2}) 为每一个匹配到的元素设置属性

jQuery对class的操作

.addClass(className)、.addClass(classNames)、.addClass(funcntion)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const $li = $('li')
$li.addClass(function(index){
if(index%2===0){
this.classList.add("active1")
}else{
this.classList.add("active2")
}

}) // 通过this手动给选中的元素添加类
$li.addClass((index)=>{
if(index%2===0){
return "active1"
}else{
return "active2"
}

}) // 通过返回类名,jQuery给元素添加类

.hasClass(className) 判断匹配到的元素是否分配了该类

1
2
3
4
const $div = $('div')
console.log($div.hasClass("active")) // true
console.log($div.hasClass("active list")) // true
console.log($div.hasClass("active div")) // false 该方法是判断所选中元素是否包含要查询的类的字符串

.removeClass()、.removeClass(className)、.removeClass(classNames)、.removeClass(function) 删除指定的类

.toggleClass()、.toggleClass(className[,state])、.toggleClass(classNames[,state]) 删除或添加指定的类

this的绑定

一. 整理this的绑定规则

  • 默认绑定:独立函数调用,函数没有被绑定到某个对象上进行调用

  • 隐式绑定:通过某个对象发起的函数调用,在调用对象内部有一个对函数的引用。

  • 显式绑定:明确this指向的对象,第一个参数相同并要求传入一个对象。

    • apply/call
    • bind
  • new绑定:

    • 创建一个全新对象
    • 新对象被执行prototype链接
    • 新对象绑定到函数调用的this
    • 如果函数没有返回其他对象,表达式会返回这个对象

默认绑定

默认绑定this一般情况下是window

在严格模式下this为undefined

es6后的class中函数默认为严格模式,即this为undefined

1
2
3
4
5
6
7
8
9
10
11
class Test{
constructor(){

}
foo(){
console.log(this)
}
}
const test = new Test()
const bar = test.foo
bar() // undefined

babel会将js设置为严格模式——在React框架下,隐式绑定都为undefined

1
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
1
2
3
4
function foo(){
console.log(this)
}
foo() // undefined

二. 说出apply、call、bind函数的用法和区别

​ 用法:

  • ​ apply

    ​ 第一个参数: 绑定this

    ​ 第二个参数: 传入额外的实参, 以数组的形式

  • ​ call

    ​ 第一个参数: 绑定this

    ​ 参数列表: 后续的参数以多参数的形式传递, 会作为实参

  • ​ bind(不希望obj对象身上有函数)

    1
    2
        var bar = foo.bind(obj)
    bar() // this -> obj

    区别:

  • call、apply和bind都可以改变函数的this指向

  • call、apply和bind第一个参数的是this要指向的对象

  • call、apply和bind都可以后续为函数传参,apply是将参数并成一个数组,call和bind是将参数依次列出

  • call、apply都是直接调用,bind生成的this指向改变函数需要手动调用。

​ 绑定优先级:

​ 默认绑定的优先级最低、显示绑定的优先级高于隐式绑定、new绑定的优先级高于隐式绑定、new绑定优先级高于bind

​ new绑定和call、apply不可以一起使用

​ 注:

​ 显示绑定传入null或undefined,显示绑定会忽略使用默认规则

1
2
3
4
foo.call(null)
foo.call(undefined)
var bar = foo.bind(null)
bar() // 都是windows

三. 说出箭头函数的各种用法和简写

  • 基本写法

    • ():函数的参数

    • {}:函数的执行体

      1
      2
      3
      4
      var foo3 = (name, age) => {
      console.log("箭头函数的函数体")
      console.log(name, age)
      }
  • 优化写法

    • 只有一个参数时, 可以省略()

      1
      2
      3
      names.forEach(item => {
      console.log(item)
      })
    • 只有一行代码时, 可以省略{}

      1
      names.forEach(item => console.log(item))
    • 只要一行代码时, 表达式的返回值会作为箭头函数默认返回值, 所以可以省略return

      1
      2
      var newNums = nums.filter(item => item % 2 === 0)
      var newNums = nums.filter(item => item % 2 === 0)
    • 如果箭头函数默认返回的是对象, 在省略{}的时候, 对象必须使用()包裹 () => ({name: “why”})

      1
      2
      3
      4
      var arrFn = () => ["abc", "cba"]
      var arrFn = () => {} // 注意: 这里是{}执行体
      var arrFn = () => ({ name: "why" })
      console.log(arrFn())

四. 完成this的面试题解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var name = "window";
var person = {
name: "person",
sayName: function () {
console.log(this.name);
}
};
function sayName() {
var sss = person.sayName;
sss(); // window
person.sayName(); // person
(person.sayName)(); // person
(b = person.sayName)(); // window
}
sayName();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
var name = 'window'
var person1 = {
name: 'person1',
foo1: function () {
console.log(this.name)
},
foo2: () => console.log(this.name),
foo3: function () {
return function () {
console.log(this.name)
}
},
foo4: function () {
return () => {
console.log(this.name)
}
}
}

var person2 = { name: 'person2' }

// person1.foo1(); // person1
// person1.foo1.call(person2); // person2

// person1.foo2(); // window
// person1.foo2.call(person2); // window

// person1.foo3()(); // window
// person1.foo3.call(person2)(); // window
// person1.foo3().call(person2); // person2

// person1.foo4()(); // person1
// person1.foo4.call(person2)(); // person2
// person1.foo4().call(person2); // person1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
var name = 'window'
function Person (name) {
this.name = name
this.foo1 = function () {
console.log(this.name)
},
this.foo2 = () => console.log(this.name),
this.foo3 = function () {
return function () {
console.log(this.name)
}
},
this.foo4 = function () {
return () => {
console.log(this.name)
}
}
}
var person1 = new Person('person1')
var person2 = new Person('person2')

// person1.foo1() // person1
// person1.foo1.call(person2) // person2

// person1.foo2() // person1
// person1.foo2.call(person2) // person1

// person1.foo3()() // window
// person1.foo3.call(person2)() // window
// person1.foo3().call(person2) // person2

// person1.foo4()() // person1
// person1.foo4.call(person2)() // person2
// person1.foo4().call(person2) // person1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var name = 'window'
function Person (name) {
this.name = name
this.obj = {
name: 'obj',
foo1: function () {
return function () {
console.log(this.name)
}
},
foo2: function () {
return () => {
console.log(this.name)
}
}
}
}
var person1 = new Person('person1')
var person2 = new Person('person2')

// person1.obj.foo1()() // window
// person1.obj.foo1.call(person2)() // window
// person1.obj.foo1().call(person2) // person2

// person1.obj.foo2()() // obj
// person1.obj.foo2.call(person2)() // person2
// person1.obj.foo2().call(person2) // obj

防抖与节流

防抖

当事件触发时,相应的函数并不会立即触发,而是会等待一定的时间

当事件密集触发时,函数的触发会被频繁的推迟

防抖的应用场景:

输入框中频繁的输入内容,搜索或提交信息

平凡的点击按钮,触发某个事件

用户缩放浏览器的resize事件

如:输入macbook时,输入m联想,连续输入macbook不联想,在输入完一段时间后联想

防抖的实现:

以input输入为例:

html代码:

1
2
<input type="text">
<button id="cancel">取消</button>

基本实现: