JavaScript总结

272 阅读19分钟

一、数据类型

1. 数据类型

基本数据类型:number,string,boolean,undefined,null,symbol

复杂数据类型:object。对象又可以分成三个子类型:狭义的对象(object),数组(array),函数(function)

区别:

  1. 基本类型储存的是值,对象储存的是地址。
  2. 对象赋值时赋值的是地址,改变新值是,原始值同时变化
// Symbol:定义独一无二的值
let s1 = Symbol('a')
let s2 = Symbol('a')
s1 === s2   // false

2. 数据类型的判断

2.1 typeof 运算符(判断基本类型)

  typeof 123 // "number"

  数值:返回 "number"
  字符串:返回 "string"
  布尔值:返回 "boolean"
  symbol: 返回 "symbol"
  undefined:返回 "undefined"
  null:返回 "object"
  object:返回 "object"
  function:返回 "function"

2.2 instanceof 运算符(判断复杂类型)

沿着原型链查找,来判断对象是不是某个构造函数的实例。

  var o = {};
  var a = [];

  o instanceof Array // false
  a instanceof Array // true

2.3 Object.prototype.toString.call(item)

返回值的构造函数。

  Object.prototype.toString.call(123);// "[object Number]"

  数值:返回[object Number]。
  字符串:返回[object String]。
  布尔值:返回[object Boolean]。
  undefined:返回[object Undefined]。
  null:返回[object Null]。
  数组:返回[object Array]。
  arguments 对象:返回[object Arguments]。
  函数:返回[object Function]。
  Error 对象:返回[object Error]。
  Date 对象:返回[object Date]。
  RegExp 对象:返回[object RegExp]。
  其他对象:返回[object Object]。

3. 数据类型转换

3.1 强制转换

// Number() & parseInt()
Number(324) // 324
Number('324') // 324
Number('324abc') // NaN
Number('') // 0
Number(true) // 1
Number(false) // 0
Number(undefined) // NaN
Number(null) // 0
Number({a: 1}) // NaN
Number([1, 2, 3]) // NaN
Number([5]) // 5
parseInt('123a') // 123
parseInt('a123') // NaN
parseInt([1,2,3]) // 1

// String()
String(123) // "123"
String('abc') // "abc"
String(true) // "true"
String(undefined) // "undefined"
String(null) // "null"
String({a: 1}) // "[object Object]"
String([1, 2, 3]) // "1,2,3"

// Boolean
Boolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean(NaN) // false
Boolean('') // false

3.2 自动转换

1 不同类型的数据互相运算。

123 + 'abc' // "123abc"

2 非布尔值类型的数据求布尔值。

if ('abc') {
  console.log('hello')
}

3 对非数值类型的值使用一元运算符。

+ {foo: 'bar'} // NaN
- [1, 2, 3] // NaN

4. 函数的参数

4.1 rest:多余变量数组,真数组

function args(a,b,...rest){
    console.log(rest)
}
args(1,2,3,4) // [3, 4]

4.2 arguments:函数运行时的所有参数,类数组

function args(a,b){
    console.log(arguments)
}
args(1,2) // Arguments(2) [1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]

类数组

  • 如果一个对象的所有键名都是正整数或零,并且有length属性,那么这个对象就很像数组,语法上称为“类似数组的对象”。数组专有的方法(比如slice和forEach),不能在类数组上直接使用。
  • 数组的slice方法可以将“类似数组的对象”变成真正的数组。
  var arr = Array.prototype.slice.call(arrayLike)

5. 闭包和立即执行函数

5.1 闭包

概念:A函数内有B函数,B函数可以访问到A的变量。

作用:让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。

  function f1() {
    var n = 999
    function f2() {
      console.log(n)
    }
    return f2
  }

  var result = f1()
  result() // 999

5.2 立即调用的函数表达式(IIFE)

作用:不必为函数命名,避免了污染全局变量。

(function(){ /* code */ }());
// 或者
(function(){ /* code */ })();

6. 箭头函数和普通函数区别

(1)箭头函数不能用于构造函数

普通函数可以用于构造函数,以此创建对象实例;但是箭头函数并不能用作构造函数。

(2)箭头函数中this的指向不同

在普通函数中,this总是指向调用它的对象或者,如果用作构造函数,它指向创建的对象实例;在箭头函数中,this的指向最近一层的非剪头函数的this。

(3)箭头函数不具有arguments对象

每一个普通函数调用后都具有一个arguments对象,用来存储实际传递的参数;但是箭头函数并没有此对象。

二、标准库

1. Number 数字

1.1 0.1 + 0.2 != 0.3

JS 采用 IEEE 754 双精度版本(64位)。计算机通过二进制来存储东西, 很多十进制小数用二进制表示都是无限循环的, JS 采用的浮点数标准却会裁剪掉我们的数字,就会出现精度丢失的问题。

1.2 进制转换

// 其他 to 十进制
Number.parseInt(x, radix)
// 十进制 to 其他
number.toString(radix)

2. Array 数组

api 作用
splice(start,length,item1,...),slice(start,end) 取数
join(str) 数组分割为字符串
concat(list) 数组合并
sort() 排序
reverse() 翻转数组
push(i), pop(), unshift(i), shift() 添加、删除数据
some(), every(), filter() 过滤
forEach, map(), reduce() 高级函数
indexOf 查找位置
let a = [1, 2, 3]
a.splice(1, 1, 4, 5) // [2]
a // [1, 4, 5, 3]
["a", "b", "c"].slice(1,2)  // ["b"]

["a", "b", "c"].join('-')   // "a-b-c"
["a", "b", "c"].concat("d")   // ["a", "b", "c", "d"]

[3,1,2].sort((a,b) => a-b)  // [1, 2, 3]
["a", "b", "c"].reverse()   // ["c", "b", "a"]

push(), pop(), unshift(), shift()

[3, 10, 18, 20].some(val => val>18)   // true
[3, 10, 18, 20].every(val => val>18)    // false
[3, 10, 18, 20].filter((val,index,arr) => val>18)   // [20]

[1,2,3].forEach(val => val+1)   // undefined
[1,2,3].map((val,index,arr) => val+1)  // [2, 3, 4]

arr.reduce(callback,[initialValue])
[1, 2, 3].reduce(function(prev, cur, index, arr) {
    console.log(prev, cur, index, arr);
    return prev + cur;
},1)  // 7

[1,2,3].indexOf(2)  // 1

3. String 字符串

api 作用
slice(start,end), substr(start,length), substring(start,end) 取数
split(str) 字符串分割为数组
concat(str) 数组合并
charAt(i) 取出第i个字符
indexOf(s) 查找位置
match("e") ,search("h") 查找字符
replace(old, new) 替换字符
toLowerCase(), toUpperCase() 改变大小写
"abc".slice(1,2)  // "b"
"hello".substr(2,3) // "llo"
"hellow".substring(2,3) // "l"
"abc".split("") // ["a", "b", "c"]

"hello".concat("world")   // "helloworld"

"hello".charAt(1)   // "e"
"hello".indexOf('h')  // 0

"hello".match("e")  // ["e", index: 1, input: "hello", groups: undefined]   String.match(regexp)
"hello".search("h") //0   String.search(regexp)

"hello".replace("o","ooo")  // "hellooo"

4. Object 对象

Object.keys({a:1,b:2})  // ["a", "b"]
Object.values({a:1,b:2})  // [1, 2]

三、面向对象编程

1. new 和 构造函数

1.1 构造函数,就是专门用来生成实例对象的函数。

  var Vehicle = function () {
    this.price = 1000;
  }
  • 函数体内部使用了this关键字,代表了所要生成的对象实例。
  • 生成对象的时候,必须使用new命令。

1.2 new 命令将执行

  • 创建一个空对象,作为将要返回的对象实例。
  • 将这个空对象的原型,指向构造函数的prototype属性。
  • 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象)。
  • 开始执行构造函数内部的代码。

1.3 注意事项

  • 如果构造函数内部有return语句,而且return后面跟着一个对象,new命令会返回return语句指定的对象;否则,就会不管return语句,返回this对象。
  • 如果对普通函数(内部没有this关键字的函数)使用new命令,则会返回一个空对象。

1.4 Object.create() 创建实例对象

  var person1 = {
    name: '张三',
    age: 38,
    greeting: function() {
      console.log('Hi! I\'m ' + this.name);
    }
  };

  var person2 = Object.create(person1);

  person2.name // 张三
  person2.greeting() // Hi! I'm 张三

2. 原型链

所有的JS对象都有一个prototype属性,指向它的原型对象(prototype)。当试图访问一个对象的属性时,如果没有在该对象上找到,它还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。这个链子,就叫做「原型链」。

所有对象都可以通过 __proto__ 找到Object,所有函数都可以通过 __proto__ 找到Function

Object.prototype 的原型是null。null 没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是null。

    arr.__proto__ = Array.prototype
    Array.__proto__ = Function.prototype
    Array.prototype.__proto__ === Object.prototype
    Function.__proto__ === Object.prototype
    Object.prototype.__proto__ === null

3. prototype 和 constructor

原型 prototype 对象有一个 constructor 属性,默认指向 prototype 对象所在的构造函数。

实例对象有一个__proto__属性,指向该实例对象对应的原型对象

constructor 属性的作用是,可以得知某个实例对象,到底是哪一个构造函数产生的。

4. 继承

//  ES5
    function Animal(name) {
        this.name = name
    }
    Animal.prototype.run = function() {
        return 'I am running!'
    }
    //继承
    function Dog(name, color){
        this.color = color
        Animal.call(this, name) // 继承属性
    }
    Dog.prototype = new Animal() // 继承方法
    Dog.prototype.constructor = Dog

    var awu = new Dog("awu", "yellow")

//  ES6
    class Animal{
        constructor(name){
            this.name = name
        }
        run(){
            return 'I am running!'
        }
    }
    class Dog extends Animal{
        constructor(name, color){
            super(name)
            this.color = color
        }
    }

    var awu = new Dog("awu", "yellow")

5. call,apply,bind

可以改变this的指向。this 是你想指定的上下文,他可以是任何一个 JavaScript 对象。

  • call:需要把参数按顺序传递进去。
fun.call(thisArg, arg1, arg2, ...)
  • apply:是把参数放在数组里。
func.apply(thisArg, [argsArray])
  • bind:以创建它时传入 bind()方法的第一个参数作为 this,传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。
function.bind(thisArg[, arg1[, arg2[, ...]]])

手写 call、apply、bind

Function.prototype.myCall = function (context, ...args) {
  // this值,把其转为object才能添加fn属性
  console.log(...args)
  var context = context ? Object(context) : window

  context.fn = this
  result = context.fn(...args)
  // 删掉添加的属性防止污染
  delete context.fn

  return result
}

Function.prototype.myApply = function (context, args) {
  // this值,把其转为object才能添加fn属性
  var context = context ? Object(context) : window

  context.fn = this
  result = args ? context.fn(...args) : context.fn()
  // 删掉添加的属性防止污染
  delete context.fn

  return result
}

Function.prototype.bind = function (context, ...args) {
  // 调用bind的函数
  var thatFunc = this, thatThis = context
  var Args = [...args]
  if (typeof thatFunc !== 'function') {
    throw new TypeError('need function')
  }
  return function () {
    // 原有参数+新传入参数
    var funcArgs = Args.concat([...arguments])
    return thatFunc.apply(thatThis, funcArgs)
  }
}

6. 深拷贝和浅拷贝

-- 和原数据是否指向同一对象 第一层数据为基本数据类型 原数据中包含子对象
赋值 改变会使原数据一同改变 改变会使原数据一同改变
浅拷贝 改变会使原数据一同改变 改变会使原数据一同改变
深拷贝 改变会使原数据一同改变 改变会使原数据一同改变

6.1 浅拷贝(shallow copy):只复制指向某个对象的指针,而不复制对象本身,新旧对象共享一块内存

    function shallowCopy(src) {
        var dst = {}
        for (var prop in src) {
            if (src.hasOwnProperty(prop)) {
                dst[prop] = src[prop]
            }
        }
        return dst
    }

6.2. 深拷贝(deep copy):复制并创建一个一摸一样的对象,不共享内存,修改新对象,旧对象保持不变。

// JSON.parse(JSON.stringify(obj))
- 忽略了symbol,function,undefined
- 不能解决循环引用

// 递归
function deepClone (source) {
  if (source instanceof Object) {
    let dist
    if (source instanceof Array) {
      dist = new Array()
    } else if (source instanceof Function) {
      dist = function () {
        return source.apply(this, arguments)
      }
    } else {
      dist = new Object()
    }
    for (let key in source) {
      dist[key] = deepClone(source[key])
    }
    return dist
  }
  return source
}

let a = {'a':111}
let b = deepClone(a)
b.a = 222
a // {'a':111}

7. 柯里化

让所有函数只接收一个参数

function curryIt(fn) {
    let rest = [].slice.call(arguments, 1)
    return function(arg){
        let argu = [].slice.call(arguments)
        if(argu.length + rest.length === fn.length){
            return fn(...rest,...argu)
        }else{
            return curryIt(fn, ...rest, ...argu)
        }
    }
}

function sum(a,b){return a+b}
curryIt(sum)(1)(2) // 3

8. this

四、异步操作

1. 相关概念

同步任务和异步任务

  1. 同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。

  2. 异步任务:不进入主线程、而进入"任务队列"的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

并发和并行

  1. 并发:单核,通过任务切换同时完成AB
  2. 并行:多核,两个核心同时完成AB

2. EventLoop事件循环

(1)首先执行同步代码,将setTimeout回调函数加入任务队列。

(2)当执行完所有同步代码后,执行栈为空,查询是否有异步代码需要执行。

(3)执行所有微任务。

(4)当执行完所有微任务后,如有必要会渲染页面。

(5)然后开始下一轮 Event Loop,执行宏任务中的异步代码,也就是 setTimeout 中的回调函数。

同步任务 > 微任务 > 宏任务

  • 微任务:promise, process.nextTick, MutationObserver
  • 宏任务:整体代码, setTimeout, setInterval, requestAnimationFrame, setImmediate。

3. 异步操作的模式

3.1 回调函数

回调地狱

假设多个请求存在依赖性,可能就会写出如下代码:

  • 代码难以阅读
  • 耦合性高,修改时牵一发动全身
ajax(url, () => {
    // 处理逻辑
    ajax(url1, () => {
        // 处理逻辑
        ajax(url2, () => {
            // 处理逻辑
        })
    })
})

3.2 事件监听

3.3 发布/订阅

class EventHub {
  constructor() {
    this.cache = {}
  }
  on(eventName, fn) {
    if (!this.cache[eventName]) {
      this.cache[eventName] = []
    }
    this.cache[eventName].push(fn)
  }
  emit(eventName, param = undefined) {
    if (this.cache[eventName]) {
      this.cache[eventName].forEach(fn => fn(param))
    }
  }
  off(eventName, fn) {
    let index = this.cache[eventName].indexOf(fn)
    if (index > -1) {
      this.cache[eventName].splice(index, 1);
    }
  }
}

3.4 Generator

Generator 函数是一个状态机,封装了多个内部状态。执行Generator函数会返回一个遍历器对象。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

// 基本用法
function* helloWorldGenerator() {
    yield 'hello'
    return 'ending'
    yield 'world
}

var hw = helloWorldGenerator()
hw.next()   // {value: "hello", done: false}
hw.next()   // {value: "ending", done: true}
hw.next()   // {value: undefined, done: true}

// Generator.prototype.throw()
var g = function* () {
  try {
    yield;
  } catch (e) {
    console.log('内部捕获', e);
  }
};

var i = g();
i.next();

try {
  i.throw('a');
  i.throw('b');
} catch (e) {
  console.log('外部捕获', e);
}
// 内部捕获 a
// 外部捕获 b

// Generator.prototype.throw()
function* helloWorldGenerator() {
    yield 'hello'
    yield 'world
}
var hw = helloWorldGenerator()
hw.next()   // {value: "hello", done: false}
hw.next('ending')   // {value: "ending", done: true}
hw.next()   // {value: undefined, done: true}

3.5 Promise

Promise 异步操作有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。除了异步操作的结果,任何其他操作都无法改变这个状态。Promise 对象只有从 pending 变为 fulfilled 和从 pending 变为 rejected 的状态改变。只要处于 fulfilled 和 rejected ,状态就不会再变了。

const promise = new Promise(function(resolve, reject) {
    if (/* 异步操作成功 */){
        resolve(value)
    } else {
        reject(error)
    }
})
promise.then(function(value) {
    // success
}.catch(function(error) {
    // faild
}).finally(function() {
    // 不管Promise对象最后状态如何,都会执行的操作
})

// Promise.all([p1, p2, p3])
1.只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled。p1、p2、p3返回值组成一个数组,传递给回调函数。
2. 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected。第一个被reject的实例返回值传给回调函数。

// Promise.race([p1, p2, p3])
只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。

// Promise.resolve()
将现有对象转为Promise对象

// Promise.reject()
返回一个新的Promise实例,该实例状态为rejected

手写Promise

const PENDING = "pending"
const RESOLVE = "resolve"
const REJECT = "reject"

function MyPromise(fn) {
  const that = this
  that.status = PENDING // MyPromise 内部状态
  that.value = null // 传入 resolve 和 reject 的值
  that.resolveCallbacks = [] // 保存 then 中resolve的回调函数
  that.rejectCallbacks = [] // 保存 then 中reject的回调函数

  // resolve 函数 Promise内部调用 resolve 函数 例:new MyPromise((resolve,reject)=>{resolve(1)})
  function resolve(val) {
    if (that.status === PENDING) {
      that.status = resolve
      that.value = val
      that.resolveCallbacks.forEach(cb => cb(that.value))
    }
  }
  // reject 函数 Promise内部调用的 reject 函数 例:new MyPromise((resolve,reject)=>{reject(1)})
  function reject(val) {
    if (that.status === PENDING) {
      that.status = REJECT
      that.value = val
      that.rejectCallbacks.forEach(cb => cb(that.value))
    }
  }
  // 调用传入 MyPromise 内的方法 例:new MyPromise((resolve,reject)=>{})   fn=(resolve,reject)=>{}
  try {
    fn(resolve, reject)
  } catch (error) {
    reject(error)
  }
}
// 在原型上添加then方法
MyPromise.prototype.then = function (onFulfilled, onRejected) {
  const that = this
  // 判断传入的是否为函数
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
  onRejected = typeof onRejected === 'function' ? onRejected : r => {
    throw r
  }

  //如果 Promise 内部存在异步代码,调用then方法时,此时 promise 内部还是 PENDING 状态,将 then 里面的函数添加进回调数组,当异步处理完成后调用 MyPromise 内部的 resolve 或者 reject 函数
  if (that.status === PENDING) {
    that.resolveCallbacks.push(onFulfilled)
    that.rejectCallbacks.push(onRejected)
  }

  // 当 Promise 内部的状态已经为 resolve,则调用 then 里面的函数并传递值
  if (that.status === RESOLVE) {
    onFulfilled(that.value)
  }

  // 当 Promise 内部状态为 reject,则调用then里的回调函数并传递值
  if (that.status === REJECT) {
    onRejected(that.value)
  }
}

// 自己实现的Promise
new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(1)
  }, 0)
}).then(res => {
  console.log(res)
}, err => {

})

3.6 async

async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

function resolveAfter2Seconds(x) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(x)
    }, 2000)
  })
}
async function f1() {
  var x = await resolveAfter2Seconds(10)
  console.log(x) // 相当于promise的then,参数传了10
}
f1()  // 10

async function f3() {
  try {
    var z = await Promise.reject(30)
  } catch (e) {
    console.log(e)
  }
}
f3()  // 30

async function test() {
  // 以下代码没有依赖性的话,最好使用 Promise.all 的方式
  await fetch(url)
  await fetch(url1)
  await fetch(url2)
}

五、DOM 和 事件模型

1. DOM增删改查

// 插入dom
let element = document.createElement(tagName[, options])
var child = node.appendChild(child)
var child = parentNode.insertBefore(newNode, referenceNode)

// 删除dom
element.remove()

// 查找dom
document.querySelector()

Node.textContent()

Element.getAttribute()
Element.setAttribute()
Element.classList

2. 事件相关API

target.addEventListener(type, listener[, useCapture])
target.removeEventListener(type, listener[, useCapture]);
target.dispatchEvent(event)

useCapture:capture, once, passive(用不调用preventDefault)

3. 事件模型

事件模型分为3个阶段:捕获阶段-目标阶段-冒泡阶段

  • 第一阶段:从window对象传导到目标节点(上层传到底层),称为“捕获阶段”。
  • 第二阶段:在目标节点上触发,称为“目标阶段”。
  • 第三阶段:从目标节点传导回window对象(从底层传回上层),称为“冒泡阶段”。(默认)
<div style="padding:50px;border:1px solid blue">
  <button>你好</button>
</div>
<script>
  let btn = document.querySelector("button")
  let div = document.querySelector("div")

  //true - 事件在捕获阶段执行
  //false - 默认。事件在冒泡阶段执行

  div.addEventListener("click",()=>{console.log(1)},true)
  btn.addEventListener("click",()=>{console.log(2)},true)
  div.addEventListener("click",()=>{console.log(3)},false)
  btn.addEventListener("click",()=>{console.log(4)},false)

  //点击button: 1,2,4,3

</script>

点击button:

  • 捕获阶段:事件从div向p传播时,触发div的1事件;
  • 目标阶段:事件从div到达p时,触发p的2,4事件;
  • 冒泡阶段:事件从p传回div时,再次触发div的3事件。

4. 事件委托

事件在冒泡过程中会上传到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方式称为事件代理。

<ul>
  <li>列表1</li>
  <li>列表2</li>
  <li>列表3</li>
</ul>
<script>
var ul = document.querySelector('ul')

//currentTarget指向事件绑定的元素,而target则是指向事件触发的元素。

ul.addEventListener('click', function (event) {
  if (event.target.tagName.toLowerCase() === 'li') {
    console.log(event.target.textContent)
  }
})
</script>

5. mouseover和mouseenter

  • mouseover:当鼠标移入元素或其子元素都会触发事件,所以有一个重复触发,冒泡的过程。对应的移除事件是mouseout
  • mouseenter:当鼠标移除元素本身(不包含元素的子元素)会触发事件,也就是不会冒泡,对应的移除事件是mouseleave

6. js拖拽功能的实现

  • dragstart:开始拖放
  • darg:正在拖放
  • dragenter:被拖放元素进入某元素
  • dragleave:被拖放元素移出目标元素
  • dragend:整个拖放操作结束

六、浏览器模型

1. script标签

没有 defer 或 async,浏览器会立即加载并执行指定的脚本,“立即”指的是在渲染该 script 标签之下的文档元素之前,也就是说不等待后续载入的文档元素,读到就加载并执行。

有 async,加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步)。

有 defer,加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成。

2. 渲染引擎处理网页

  1. 解析代码:HTML 代码解析为 DOM,CSS 代码解析为 CSSOM(CSS Object Model)。
  2. 对象合成:将 DOM 和 CSSOM 合成一棵渲染树(render tree)。
  3. 布局:计算出渲染树的布局(layout)。
  4. 绘制:将渲染树绘制到屏幕。

V8引擎

V8引擎将AST直接生成本地可执行代码,少了转化为字节码的过程,速度更快

3. 重绘与回流

(1)重绘:字体颜色、大小等外观改变。

(2)重排:元素增、删,尺寸、布局重新构建。

解决方法

1.不要一条一条的修改样式,尽量提前设置好 class,后续增加 class,进行批量修改。

2.批量操作dom

3.复杂动画使用absolute绝对定位,或者使用 transform 替代 top。

4.不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局。

3. BOM相关API

3.1 window

  • window.innerWidth:文档宽度
  • window.outerWidth:浏览器宽度
  • window.open():打开新窗口
  • window.scroll():滚动
  • window.scrollTo():滚动至
  • window.alert():警告框
  • window.confirm():确认框
  • window.prompt():输入
  • window.setTimeout
  • window.setInterval
  • window.clearInterval

3.2 Navigator

  • appCodeName:返回浏览器的代码名
  • appName:返回浏览器的名称
  • appVersion:返回浏览器的平台和版本信息
  • cookieEnabled:返回指明浏览器中是否启用 cookie 的布尔值
  • platform:返回运行浏览器的操作系统平台
  • userAgent:返回由客户机发送服务器的user-agent 头部的值

3.3 Screen

  • availHeight:返回屏幕的高度(不包括Windows任务栏)
  • availWidth:返回屏幕的宽度(不包括Windows任务栏)
  • colorDepth:返回目标设备或缓冲器上的调色板的比特深度
  • height:返回屏幕的总高度
  • pixelDepth:返回屏幕的颜色分辨率(每象素的位数)
  • width:返回屏幕的总宽度

3.4 history

  • history.go():前进或后退指定的页面数
  • history.back():后退一页
  • history.forward():前进一页

3.5 location

  • location.href:返回或设置当前文档的URL
  • location.reload():重载当前页面

setTimeout, setInterval

JS 是单线程执行的,如果前面的代码影响了性能,就会导致 setTimeout 不会按期执行。

requestAnimationFrame 代替

window.requestAnimationFrame 需要传入一个回调函数作为参数,并要求浏览器在下次重绘之前调用指定的回调函数,它有两个特点。

function setInterval(callback, interval) {
  let timer
  const now = Date.now
  let startTime = now()
  let endTime = startTime
  const loop = () => {
    timer = window.requestAnimationFrame(loop)
    endTime = now()
    if (endTime - startTime >= interval) {
      startTime = endTime = now()
      callback(timer)
    }
  }
  timer = window.requestAnimationFrame(loop)
  return timer
}

let a = 0
setInterval(timer => {
  console.log(1)
  a++
  if (a === 3) cancelAnimationFrame(timer)
}, 1000)

4. XMLHttpRequest 对象

 var request = new XMLHttpRequest()
 request.open('GET', '/a/b/c?name=ff', true)
 request.onreadystatechange = function () {
   if(request.readyState === 4 && request.status === 200) {
     console.log(request.responseText)
   }}
 request.send()

5. 同源限制

“同源”指的是“三个相同”。

  1. 协议相同
  2. 域名相同
  3. 端口相同

解决同源限制问题:

5.1 JSONP

// 前端
<script src="http://domain/api?param1=a&param2=b&callback=jsonp"></script>
<script>
    function jsonp(data) {
    	console.log(data)
	}
</script>

// 后端
let callbackName = query.callback
response.setHeader('Content-Type', 'application/javascript')
response.write(`
    ${callbackName}.call(undefined, 'success')
`)

5.2 postMessage

// 发窗口 index.html
<iframe src="iframe.html" id="iframe"></iframe>
<script>
  iframe.contentWindow.postMessage('send', 'iframe.html')
</script>

// 收窗口 iframe.html
window.addEventListener('message', event => {
  console.log(event.orgin)  // index.html
  console.log(event.data) // send
}, false)

5.3 CORS

1 简单请求

对于简单请求,浏览器直接发出 CORS 请求。具体来说,就是在头信息之中,增加一个Origin字段,表名请求来自哪个域名。

(1)请求方法是以下三种方法之一:HEAD,GET,POST

(2)Content-Type 的值仅限于下列三者之一:text/plain, multipart/form-data, application/x-www-form-urlencoded

2 非简单请求

非简单请求的 CORS 请求,会在正式通信之前,增加一次 HTTP 查询请求,称为“预检”请求(preflight)。浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些 HTTP 方法和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

// 前端
var xhr = new XMLHttpRequest()
xhr.withCredentials = true  // 跨域cookies

// 后端
Access-Control-Allow-Origin
Access-Control-Expose-Headers
Access-Control-Allow-Methods
Access-Control-Allow-Credentials

5.4 document.domain

该方式只能用于二级域名相同的情况下,比如 a.test.com 和 b.test.com 适用于该方式。

只需要给页面添加 document.domain = 'test.com' 表示二级域名都相同就可以实现跨域

6. 读写文件

// HTML 代码如下
// <input type="file" accept="image/*" multiple onchange="fileinfo(this.files)"/>

function fileinfo(files) {
  for (var i = 0; i < files.length; i++) {
    var f = files[i]
    console.log(
      f.name, // 文件名,不含路径
      f.size, // 文件大小,Blob 实例属性
      f.type, // 文件类型,Blob 实例属性
      f.lastModifiedDate // 文件的最后修改时间
    )
  }
}

// HTML 代码如下
// <input type=’file' onchange='readfile(this.files[0])'></input>

function readfile(f) {
  var reader = new FileReader()
  reader.readAsText(f)
  reader.onload = function () {
    console.log(reader.result)
  }
  reader.onerror = function(e) {
    console.log(e)
  }
}

7. 缓存

特性 cookie localStorage sessionStorage session indexDB
数据生命周期 一般由服务器生成,可以设置过期时间 除非被清理,否则一直存在 页面关闭就清理 保存在服务器中 除非被清理,否则一直存在
数据存储大小 4K 5M 5M 4k 无限
与服务端通信 每次都会携带在 header 中,对于请求性能影响 不参与 不参与 将 SessionID通过 Cookie 发给客户端 不参与

7.1 缓存位置

(1)Service Worker

Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。首先需要先注册 Service Worker,然后监听到 install 事件以后就可以缓存需要的文件,那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。

(2)Memory Cache

Memory Cache 也就是内存中的缓存,读取高效,可是缓存持续性很短,会随着进程的释放而释放。 一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。

(3)Disk Cache

Disk Cache 也就是存储在硬盘中的缓存,读取速度慢,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。

(4)Push Cache

Push Cache 是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。并且缓存时间也很短暂,只在会话(Session)中存在,一旦会话结束就被释放。

(5)网络请求

如果所有缓存都没有命中的话,那么只能发起请求来获取资源了。

7.2 缓存策略

(1)强缓存

强缓存表示在缓存期间不需要请求

(2)协商缓存

如果缓存过期,就验证资源是否更新。如未更新则返回304。

Last-Modified 和 If-Modified-Since 和 If-Unmodified-Since

Last-Modified 表示本地文件最后修改日期,If-Modified-Since 会将 Last-Modified 的值发送给服务器,询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来,否则返回 304 状态码。

ETag 和 If-Match 和 If-None-Match

ETag 类似于文件标识,If-None-Match 会将当前 ETag 发送给服务器,询问该资源 ETag 是否变动,有变动的话就将新的资源发送回来。并且 ETag 优先级比 Last-Modified 高。

(3)未设置策略

取响应头中的 Date 减去 Last-Modified 值的 10% 作为缓存时间。

8. 垃圾回收

(1)获取root(绝不会被清除的变量,如函数参数,全局变量)

(2)访问并标记他们的引用,然后在寻找被标记对象的引用

(3)清除未标记

六、性能优化

1. 图片

(1)选择合适的像素

(2)CSS代替图片

(3)小图使用base64

(4)雪碧图

2. DNS 预解析

<link rel="dns-prefetch" href="//xxx.com">

DNS解析

域名解析是把域名指向网站空间IP

DNS解析过程:

(1)浏览器缓存中查找

(2)系统缓存中查找(Hosts文件)

(3)路由器缓存中查找

(4)服务商缓存中查找

(5)根域名服务器查找

3. 节流和防抖

节流

一段时间执行一次之后,就不执行第二次

<input>
<script>
  let text = document.querySelector("input")
  function throttle(fn, interval = 1000) {
    let canRun = true;
    return function () {
        if (!canRun) return;
        canRun = false;
        setTimeout(() => {fn();canRun = true;}, interval);
    };
  }
  text.oninput = throttle(()=>{console.log(text.value)})
</script>

防抖

任务频繁触发的情况下,只有任务触发的间隔超过指定间隔的时候,任务才会执行

<input>
<script>
  let text = document.querySelector("input")
  function debounce(fn, interval = 1000) {
    let timeout = null;
    return function () {
        clearTimeout(timeout);
        timeout = setTimeout(() => {fn()}, interval);
    };
  }
  text.oninput = debounce(()=>{console.log(text.value)})
</script>

4. 预加载和懒加载

预加载

将所有所需的资源提前请求加载到本地,这样后面在需要用到时就直接从缓存取资源。

懒加载

在图片没有进入可视区域时,先不给的src赋值,这样浏览器就不会发送请求了,等到图片进入可视区域再给src赋值。

如果:offsetTop-scroolTop<clientHeight,则图片进入了可视区内,则被请求。

document.documentElement.clientHeight // 获取屏幕可视区域的高度
element.offsetTop // 获取元素相对于文档顶部的高度
document.documentElement.scrollTop  // 获取滚动条滚动的距离

5. CDN 加载

广泛采用各种缓存服务器,将这些缓存服务器分布到用户访问相对集中的地区或网络中,在用户访问网站时,利用全局负载技术将用户的访问指向距离最近的工作正常的缓存服务器上,由缓存服务器直接响应。

七、输入URL到显示网页,发生了什么

1. DNS解析

2. 子网掩码

通过子网掩码判断IP是否在子网络中,如果不在则必须通过网关转发数据

3. 发起TPC链接,三次握手

三次:为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。

(1)第一次握手:客户端发网络包,服务端收到了。S确定B能发,S能收。

(2)第二次握手:服务端发包,客户端收到了。B确定B能收、发,S能收、发。

(3)第三次握手: 客户端发包,服务端收到了。S确定B能收发、S能收、发。

4. 断开TPC链接,四次挥手

四次:而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即close,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送。

(1)第一次挥手:客户端发包FIN,请求关闭数据传输,自身形成等待结束连接的状态。

(2)第二次挥手:服务端知道客户端已经等待结束,但服务端此时还有任务要处理,向客户端发送一个ACK,表示知道要关闭。

(3)第三次挥手:服务器处理完任务,自身处于等待关闭连接的状态,告诉客户端应用程序关闭FIN。

(4)第四次挥手:当客户端收到服务器端的FIN时,回复一个ACK给服务器,客户端同时又为自己定义一个定时器,超出这个时间就默认服务器端已经接收到了自己的确认信息,此时客户端就关闭自身连接,服务器端一旦接收到客户端发来的确定通知就立刻关闭服务器端的连接。

5. 浏览器解析HTML,请求资源,渲染页面

(1)解析代码:HTML 代码解析为 DOM,CSS 代码解析为 CSSOM。

(2)对象合成:将 DOM 和 CSSOM 合成一棵渲染树(render tree)。

(3)布局:计算出渲染树的布局(layout)。

(4)绘制:将渲染树绘制到屏幕。

八、ES6 补充

1. 声明

(1)let

  • 代码块内有效
  • 不能重复声明
  • 不存在变量提升(暂时性死区)
  • 不能挂在全局

(2)const

  • 声明常量
  • 对于复杂类型,只能保证指针为固定的,指针指向的数据结构无法保证

2. 解构赋值和剩余运算符

let [a, b, c] = [d, e, f]

let { foo, bar } = { foo: 'aaa', bar: 'bbb' }

let a = [1, 2, 3], b = [4, 5, 6]
[...a, ...b] = [1, 2, 3, 4, 5, 6]

3. 内置对象

(1)数据类型

symbol

表示独一无二的值,最大的用法是用来定义对象的唯一属性名。

(2)数据结构

Set

类似于数组,但是成员的值都是唯一的,没有重复的值。

WeakSet

首先,WeakSet 的成员只能是对象,而不能是其他类型的值。

其次,WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存。

Map

类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。

WeakMap

首先,WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。

其次,WeakMap的键名所指向的对象,不计入垃圾回收机制。

(3)操作对象

Proxy

let handle = {
    get: function(target,key){
        console.log('setting' + key)
        return target[key]  // 不是target.key
    },
    set: function(target, key, value) {
        console.log('setting ' + key)
        target[key] = value
    }
}
let proxy = new Proxy(target, handler)
proxy.name // getting name
proxy.age = 25 // 实际执行 setting age

Reflect

(1)将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。

(2)修改某些Object方法的返回结果,让其变得更合理。

try {
  Object.defineProperty(target, property, attributes)
  // success
} catch (e) {
  // failure
}

if (Reflect.defineProperty(target, property, attributes)) {
  // success
} else {
  // failure
}

(3)让Object操作都变成函数行为。

Reflect.has(obj, name)  // name in obj
Reflect.deleteProperty(obj, name) // delete obj[name]

(4)Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。

(4)数据类型

ES6 字符串

api 作用
includes() 判断是否找到参数字符串。
startsWith() 判断参数字符串是否在原字符串的头部。
endsWith() 判断参数字符串是否在原字符串的尾部。
repeat(num) 将字符串重复指定次数返回
padStart() 返回新的字符串,表示用参数字符串从头部补全原字符串。
padEnd() 返回新的字符串,表示用参数字符串从头部补全原字符串。
trimStart() 消除字符串头部的空格
trimEnd() 消除尾部的空格

ES6 数字

api
Number.isFinite()
Number.isNaN()
Number.parseInt()
Number.parseFloat()
Number.isInteger()
Number.EPSILON
Number.isSafeInteger()

ES6 数组

api 作用
Array.from() 将类数组和可遍历(iterable)的对象转为真正的数组
Array.of() 将一组值转换为数组
find 用于找出第一个符合条件的数组成员,然后返回该成员
findIndex 返回第一个符合条件的数组成员的位置
copyWithin 将指定位置的成员复制到其他位置
fill 使用给定值,填充一个数组
flat() 将嵌套的数组“拉平”

4. 类和继承

(1)constructor

通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。

(2)类的实例

生成类的实例的写法,与 ES5 完全一样,也是使用new命令。

class Point {
  // ...
}
var point = new Point(2, 3)

(3)static关键字

如果在一个方法前,加上static关键字,就表示该方法不会被实例继承。

父类的静态方法,可以被子类继承。

(3)super 关键字

子类的构造函数必须执行一次super函数。

super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。

5. ES Module

(1)CommonJS 支持动态导入,也就是 require(${path}/xx.js);ES Module不支持

(2)CommonJS 是同步导入,因为用于服务端,文件都在本地,同步导入即使卡住主线程影响也不大;ES Module是异步导入,因为用于浏览器,需要下载文件,如果也采用同步导入会对渲染有很大影响

(3)CommonJS 在导出时都是值拷贝,就算导出的值变了,导入的值也不会改变,所以如果想更新值,必须重新导入一次;ES Module采用实时绑定的方式,导入导出的值都指向同一个内存地址,所以导入值会跟随导出值变化

(4)CommonJS在代码运行时加载;ES Module在编译时加载。

6. 异步编程

(1)Generator

(2)Promise

(3)async/await