updating...

262 阅读20分钟

CSS

1、三栏布局问题(左右固定宽度 中间自适应)

flex
<div class="container">
     <div class="left"></div>
     <div class="middle"></div>
     <div class="right"></div>
</div>

.container {
    display: flex;
}

.left, .right {
    width: 200px
}

.middle {
    flex: 1;
}

优点:比较完美 移动端首选;
缺点:不兼容 ie9 及以下;
grid
<div class="container">
     <div class="left"></div>
     <div class="middle"></div>
     <div class="right"></div>
</div>


.container {
    display: grid;
    grid-template-columns: 200px auto 200px;
}

优点:简单强大 解决二维布局问题;
缺点:不兼容 ie9 及以下,很多国产手机浏览器有兼容问题;
float + margin
<div class="container">
     <div class="left"></div>
     <div class="middle"></div>
     <div class="right"></div>
</div>

.container {
    overflow: hidden;
}

.left {
    float:left;
    height:100%;
    width:200px;
}

.right {
    float:right;
    height:100%;
    width:200px;
}

.middle{
    height:100%;
    margin:0 200px;
}

优点:快捷 简单 兼容性较好;
缺点: 有局限性 脱离文档流 需要清除浮动等;

2、CSS 盒模型

margin-area: 外边距区域; border-area: 边框区域; padding-area: 内边距区域; content-area: 内容区域;

标准盒模型和 IE 盒模型: 标准盒模型: box-sizing 值为 content-box 时,在高度和宽度之外绘制内外边距以及边框; IE盒模型:box-sizing 值为 border-box 时,内边距和边框在已设置的宽高内绘制;


3、BFC

BFC(Block Formatting Context)块级格式化上下文,是 Web 页面中盒模型布局的 CSS 渲染模式,指一个独立的渲染区域或者说是一个隔离的独立容器。

形成条件:

  • 浮动元素,float 除 none 以外的值;
  • 定位元素,position(absolute,fixed);
  • display 为以下其中之一的值 inline-block,table-cell,table-caption;
  • overflow 除了 visible 以外的值(hidden,auto,scroll);

特征:

  • 内部的 Box 会在垂直方向上一个接一个的放置;
  • 垂直方向上的距离由margin 决定;(解决外边距重叠问题)
  • bfc 的区域不会与 float 的元素区域重叠;(防止浮动文字环绕)
  • 计算 bfc 的高度时,浮动元素也参与计算;(清除浮动)
  • bfc 就是页面上的一个独立容器,容器里面的子元素不会影响外面元素;

4、外边距重叠

块的上外边距(margin-top)下外边距(margin-bottom)有时合并(折叠)为单个边距,其大小为单个边距的最大值(或如果它们相等,则仅为其中一个),这种行为称为边距折叠

注意有设定floatposition=absolute的元素不会产生外边距重叠行为。

相关文章:MDN 文档


DOM

1、DOM 事件级别

DOM 0级:

写法:el.οnclick=function(){} DOM 0事件绑定,给元素的事件行为绑定方法,这些方法都是在当前元素事件行为的冒泡阶段(或者目标阶段)执行的。

DOM 2级

写法:el.addEventListener(event-name, callback, useCapture) event-name: 事件名称,可以是标准的DOM事件; callback: 回调函数,当事件触发时,函数会被注入一个参数为当前的事件对象 event; useCapture: 默认是false,代表事件句柄在冒泡阶段执行;

DOM 3级

写法和DOM2级一致 只是在DOM 2级事件的基础上添加了更多的事件类型 新增事件:

  • UI事件,当用户与页面上的元素交互时触发,如:load、scroll;
  • 焦点事件,当元素获得或失去焦点时触发,如:blur、focus;
  • 鼠标事件,当用户通过鼠标在页面执行操作时触发如:dblclick、mouseup;
  • 滚轮事件,当使用鼠标滚轮或类似设备时触发,如:mousewheel;
  • 文本事件,当在文档中输入文本时触发,如:textInput;
  • 键盘事件,当用户通过键盘在页面上执行操作时触发,如:keydown、keypress;
  • 合成事件,当为IME(输入法编辑器)输入字符时触发,如:compositionstart;
  • 变动事件,当底层DOM结构发生变化时触发,如:DOMsubtreeModified;

2、事件模型 事件流

事件模型: 捕获和冒泡; 事件流:

  • 捕获阶段:事件从 window 对象自上而下向目标节点传递阶段;
  • 目标阶段:目标节点处理对应事件;
  • 冒泡阶段:事件从目标节点向 window 对象自下而上传播阶段;

3、事件代理

由于在冒泡阶段事件从下而上传播,因此可以将子节点的监听函数放置在父节点上,统一处理子节点的事件。减少内存消耗,提高性能(不需要为每一个子元素绑定事件)。


4、Event 对象

阻止默认行为:

event.preventDefault()

阻止冒泡:
  • event.stopPropation 阻止事件冒泡到父元素
  • event.stopImmediatePropation 既能阻止事件向父元素冒泡,也能阻止元素同事件类型的其它监听器被触发
target 和 currentTarget:

currentTarget始终是监听事件者,而target是事件的真正发出者。


5、自定义事件

// 创建事件:
// event 不能传参
let event = new Event('eventName')
// CustomEvent是可以传递参数的
let customEvent = new CustomEvent('eventName',  {})
// 监听事件
/*
*  addEventListener(event, function, useCapture)
*  useCapture 指定事件是否 在捕获或冒泡阶段执行;
*      true - 事件在捕获阶段执行
*      false- 默认,事件在冒泡阶段执行
*/
dom.addEventListener('eventName', function (e) {...}, false)

// 触发事件
dom. dispatchEvent('eventName')


JavaScript

1、JavaScript 单线程

js 作为浏览器脚本语言,其主要用途是与用户互动,以及操作DOM。如果存在多个线程就会有很复杂的同步问题,例如:一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

2、JavaScript 同步任务与异步任务

由于 js 单线程的特点,每次只能执行单个任务。如果前序任务始终没有结束,后序的任务只能等待,效率较低。所以增加了同步任务和异步任务的区分。

  • 同步任务:在主线程上排队执行的任务,需要按顺序一一执行;
  • 异步任务:不进入主线程而是进入任务队列,只有通知主线程任务可以被执行时,才会加入主线程执行;

3、Event-Loop 事件轮询:

Javascript 的“线程”有一种机制:在每次调用 JS 引擎时,可以随着时间的推移执行你的程序的多个代码块儿,这称为“事件轮询(Event Loop)。

JavaScript 实现异步的具体方式:
  • 同步代码直接执行
  • 异步函数放置到异步队列中
  • 同步代码执行完毕,异步队列轮询执行

宏任务与微任务

宏任务:

分类:

函数浏览器node
I/O
setTimeout
setInterval
setImmediate
requestAnimationFrame

特性:

  • 宏任务所在的队列就是宏任务队列;
  • 第一个宏任务队列中只有一个任务:执行主线程上的JS代码;如果遇到上方表格中的异步任务,会创建出一个新的宏任务队列,存放这些异步函数执行完成后的回调函数;
  • 宏任务中可以创建微任务,但是在宏任务中创建的微任务不会影响当前宏任务的执行;
  • 当一个宏任务队列中的任务全部执行完后,会查看是否有微任务队列,如果有就会优先执行微任务队列中的所有任务,如果没有就查看是否有宏任务队列;
微任务:

分类:

函数浏览器node
process.nextTick
mutionObserver
promise.then/catch/finally

mutionObserver:

  • 用来监视 DOM 变动
  • 等待所有脚本任务完成后,才会运行,即采用异步方式
  • 把 DOM 变动记录封装成一个数组进行处理,而不是一条条地个别处理 DOM 变动
  • 即可以观察发生在 DOM 节点的所有变动,也可以观察某一类变动
经典面试题
console.log(1);

setTimeout(()=>{
    console.log(2);
    new Promise((resolve,reject)=>{
    console.log(3);
    resolve()
}).then(res=>{
    console.log(4);
})
})

new Promise((resolve,reject)=>{
    resolve()
}).then(res=>{
    console.log(5);
}).then(res=>{
    console.log(6);

})

new Promise((resolve,reject)=>{
    console.log(7);
    resolve()
}).then(res=>{
    console.log(8);
}).then(res=>{
    console.log(9);

})

setTimeout(()=>{
    console.log(10);
    new Promise((resolve,reject)=>{
    console.log(11);
    resolve()
}).then(res=>{
    console.log(12);
})
})

console.log(13);

输出结果:
依次 17135869234101112

4、创建对象:

  • 字面量方式(简单,运行速度更快)
let obj = { test: a}
  • 构造函数
function Test () {
    this.test =  a
}
let obj = new Test()
  • Object.create(proto, [propertiesObject])
//Object.create()方法创建的对象时,属性是在原型下面的
let obj = Object.creat({test: a})

5、原型链:

原型链和原型对象是js的核心,原型链保证函数或对象中的方法、属性可以让向下传递,js通过原型链才得以实现函数或对象的继承

prototype 和 constructor:

prototype 指向函数的原型对象,只有函数才拥有该属性。 constructor 指向原型对象的构造函数。

_proto_:

每个对象都有 proto,指向了创建该对象的构造函数原型。由于js中是没有类的概念,而为了实现继承,通过 proto 将对象和原型联系起来组成原型链,就可以让对象访问到不属于自己的属性。

Foo、Function 和 Object 都是函数,它们的 proto 都指向 Function.prototype.

原型对象 _proto_都指向了 Object.prototype,js原型链最终指向的是 Object 原型对象。

总结

实例的 _proto_ 指向原型对象的 prototype,实例远行对象的 _proto_ 是 Object 的原型对象(null 除外) image.png

举例:

  • instanceof 原理
<script>
function Person(){
}
function Foo(){
}

//显示改变Foo.prototype指向Person的实例对象(原型继承)

Foo.prototype=new Person()

let a=new Foo()

console.log(a.__proto__===Foo.prototype); //true

console.log(a instanceof Foo);//true

console.log(Foo.prototype.__proto__===Person.prototype);//true

console.log(a instanceof Person);//true

console.log(a instanceof Object);//true

// 这个时候改变Foo.prototype的指向

Foo.prototype={}

// Foo.prototype已经不在a的原型链上面了

console.log(a.__proto__===Foo.prototype);//false

console.log(a instanceof Foo);//false

//Person.prototype依然在a的原型链上面

console.log(a instanceof Person);//true
</script>
  • new运算符原理 1、创建一个新对象; 2、将空对象的 _proto_ 指向构造函的 prototype; 3、使用apply调用构造器函数,属性和方法被添加到 this 引用的对象中; 4、如果构造函数中没有返回其它对象,那么返回 this,即创建的这个的新对象,否则,返回构造函数中返回的对象;
function _new(func) {
    // 第一步 创建新对象
    let obj= {};
    // 第二步 空对象的_proto_指向了构造函数的prototype成员对象
    obj.__proto__ = func.prototype;//
    // 一二步合并就相当于 let obj=Object.create(func.prototype)

    // 第三步 使用apply调用构造器函数,属性和方法被添加到 this 引用的对象中
    let result = func.apply(obj);
    if (result && (typeof (result) == "object" || typeof (result) == "function")) {
    // 如果构造函数执行的结果返回的是一个对象,那么返回这个对象
        return result;
    }
    // 如果构造函数返回的不是一个对象,返回创建的新对象
    return obj;
}

5、继承实现:

声明父类:

function Father(name) {
      this.name = name || "father";
      this.sayName = function() {
        console.log(this.name);
      }
      this.color = ["red", "blue"]
}

Father.prototype.age = 18;
Father.prototype.sayAge = function() {
      console.log(this.age)    
}
1) 原型链继承
function Son(name) {
    this.name = name || 'son'
}

Son.prototype = new Father()

优点:

  • 简单易于实现;
  • 父类新增的属性和方法,子类都可以访问到;

缺点:

  • 无法实现多继承,因为原型一次只能被一个实例更改;
  • 来自原型对象的所有属性被所有实例共享;
  • 创建子类实例时,无法向父构造函数传参;
2)构造继承:复制父类的实例属性给子类
function Son(name) {
    Father.call(this, '父级需要的参数')
    this.name = name
}

let s = new Son("son");
console.log(s.name); // son
//s.sayAge(); // 抛出错误(无法继承父类原型方法)
 s.sayName(); // son
console.log(s.age); // undefined (无法继承父类原型属性)
console.log(s instanceof Father); // false
console.log(s instanceof Son); // true

优点:

  • 解决了原型链继承中子类实例共享父类引用属性的问题;
  • 创建子类实例时,可以向父类传递参数;
  • 可以实现多继承(call多个父类对象);

缺点:

  • 实例并不是父类的实例,只是子类的实例;
  • 只能继承父类实例的属性和方法,不能继承其原型上的属性和方法;
  • 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能;
3)原型链、构造函数组合继承

使用原型链实现对原型属性和方法的继承,而通过构造函数来实现对实例属性的继承。

function Son(name) {
   // 第一次调用父类构造器 子类实例增加父类实例
    Father.call(this, "我是传给父类的参数");
    this.name = name || "son";
}
// 经过new运算符 第二次调用父类构造器 子类原型也增加了父类实例
Son.prototype = new Father();

let s = new Son("son");
console.log(s.name); // son
s.sayAge(); // 18
s.sayName(); // son
console.log(s.age); // 18
console.log(s instanceof Father); // true
console.log(s instanceof Son); // true
console.log(s.constructor === Father); // true
console.log(s.constructor === Son); // false

优点:

  • 弥补了构造继承的缺点,现在既可以继承实例的属性和方法,也可以继承原型的属性和方法;
  • 既是子类的实例,也是父类的实例;
  • 可以向父类传递参数;
  • 函数可以复用;

缺点:

  • 调用了两次父类构造函数,生成了两份实例;
  • constructor指向问题;
4)*寄生组合继承:

通过寄生方式,砍掉父类的实例属性,避免了组合继承生成两份实例的缺点;

function Son (name) {
    let f = Father.call(this, '传递给父级的参数')
    f.name = name || 'son'
}

# 借用Object.create()方法
Son.prototype = Object.create(Father.prototype)
Son.prototype.constructor = Son

# 自己动手创建一个中间类
// (function() {
//   let NoneFun = function() {};
//   NoneFun.prototype = Father.prototype;
//   Son.prototype = new NoneFun();
//   Son.prototype.constructor = Son;
// })();

let s = new Son("son");
console.log(s.name); // son
s.sayAge(); // 18
s.sayName(); // son
console.log(s.age); // 18
console.log(s instanceof Father); // true
console.log(s instanceof Son); // true
console.log(s.constructor === Father); // false
console.log(s.constructor === Son); // true

优点:

  • js实现继承首选方式;

缺点:

  • 实现较复杂(可通过Object.create简化);
5)实例继承:为父类实例添加新特征,作为子类实例返回
function Son (name) {
    let f = new Father('传递给父级的参数')
    f.name = name || 'son'
    return f
}

let s = new Son("son"); //或者直接调用子类构造函数 let s = Son("son");
console.log(s.name); // son
s.sayAge(); // 18
s.sayName(); // son
console.log(s.age); // 18
console.log(s instanceof Father); // true
console.log(s instanceof Son); // false
console.log(s.constructor === Father); // true
console.log(s.constructor === Son); // false

优点:

  • 不限制调用方式,不管是new 的方式声明子类还是通过直接调用函数方法声明,返回的对象具有相同的效果;

缺点:

  • 实例是父类的实例,不是子类的实例;
  • 不支持多继承;
6)拷贝继承:对父类实例中的的方法与属性拷贝给子类的原型
function Son (name) {
    let f = new Father('要传给父级的数据')
    for (let k in f) {
        Son.prototype[k] = f[k]
    }
    Son.prototype.name = name
}

let s = new Son("son");
console.log(s.name); // son
s.sayAge(); // 18
s.sayName(); // son
console.log(s.age); // 18
console.log(s instanceof Father); // false
console.log(s instanceof Son); // true
console.log(s.constructor === Father); // false
console.log(s.constructor === Son); // true

优点:

  • 支持多继承;

缺点:

  • 效率低,性能差,占用内存高(因为需要拷贝父类属性);
  • 无法获取父类不可枚举的方法(不可枚举的方法,不能使用for-in访问到);
7)ES6 Class 继承
class Son extends Father {
constructor(name) {
    super(name);
        this.name = name || "son";
      }
 }

let s = new Son("son");
console.log(s.name); // son
s.sayAge(); // 18
s.sayName(); // son
console.log(s.age); // 18
console.log(s instanceof Father); // true
console.log(s instanceof Son); // true
console.log(s.constructor === Father); // false
console.log(s.constructor === Son); // true

6、节流和防抖

#####节流:一定时间内执行的操作只执行一次。

应用场景:

  • 鼠标不断点击触发,mousedown(单位时间内只触发一次);
  • 监听滚动事件,比如是否滑到底部自动加载更多(懒加载);
function throttle (fn, timer) {
    let canRun = true
    return function () {
        if ( !canRun ) return 
        canRun = false
        setTimeout(() => {
            fn().apply(this, arguments)
            canRun = true
        }, timer)
    }
}

#####防抖:动作停止后的时间超过设定的时间时执行一次函数。注意:这里的动作停止表示你停止了触发这个函数,从这个时间点开始计算,当间隔时间等于你设定时间,才会执行里面的回调函数。如果你一直在触发这个函数并且两次触发间隔小于设定时间,则函数一直不会执行.

应用场景:

  • 搜索栏用户输入结束后,请求联想数据;
  • window resize时,页面不断调整会不断触犯;
function debance (fn, delayTime) {
    let timer = null
    return function () {
        if (timer) {
            clearTimeout(timer)      
        } 
        timer = setTimeout(() => {
            fn.call(this, arguments)
        },  delayTime)
    }
}

7、bind 实现

this有四种绑定模式
  • 默认绑定
  • 隐式绑定
  • 显式绑定
  • new绑定。

优先级为:new绑定 > 显式绑定 > 隐式绑定 > 默认绑定

// 默认绑定
var str = 'hello world'
function log() {
    console.log(this.str) 
}

// 此时this默认指向window
log() // hello world

// 在严格模式下this默认指向undefined的
'use strict'
var str2 = 'hello world'
function log() {
    console.log(this.str2) 
}

// 此时this指向undefined
log() // 报错TypeError,因为程序从undefined中获取str2

// 隐式绑定一般发生在函数作为对象属性调用时
var bar = 'hello'
function foo() {
    console.log(this.bar)
}

var obj = {
    bar: 'world',
    foo: foo
}

foo() // hello,this指向window所以输出hello
obj.foo() // world,this隐式绑定了obj,这时候this指向obj所以输出world
// 显式绑定就是我们常谈的apply,call,bind
var bar = 'hello'
var context = {
    bar: 'world'
}
function foo() {
    console.log(this.bar);
}

foo() // hello
foo.call(context) // world 可见此时this的指向已经变成了context
// new绑定比较特殊,new大部分情况下是创建一个新的对象,并将this指向这个新对象,最后返回这个对象
function Foo(bar) {
    this.bar = bar
}

// 创建一个新的对象,并将this指向这个对象,将这个对象返回赋值给foo
var foo = new Foo(3);
foo.bar // 3
call、apply 和 bind 区别

共同:三者都是改变了函数执行时的上下文,也就是改变了函数运行时 this 的指向; 不同:

  • bind 返回了一个改变上下文的函数,而 call 和 apply 改变了函数后就立即执行;
  • call 和 apply 的区别在于参数;
    • call的第二个参数为 list 的形式;
    • apply 的第二个参数为一个数组;(方便记忆~ a开头—Array~hhh)
  • bind 在 IE6~8 不兼容;
使用 call 实现 bind
Function.prototype.bind(context, ..args) {
  var fToBind = this
  var fNop = function() {}
  var fBound = function(...innerArgs) {
    // 如果被 new 调用,this 应该是 fBound 的实例
    if(this instanceof fBound) {
       /**
       * cover 住new 调用的情况, new 的优先级最高
       * 所以其实我们这里要模拟fToBind被new调用的情况,并返回
       * 我们使用new创建的对象替换掉bind传进来的context
       */
       return fToBind.call(this, ...args, ...innerArgs)
    } else {
       return fToBind.call(context, ...args, ...innerArgs)
    }
  }
 // 除了维护new的this绑定,我们还需要维护new导致的原型链变化
 // 执行new后返回的对象的原型链会指向fToBind
 // 但是我们调用bind后实际返回的是fBound,所以我们这里需要替换掉fBound的原型
 fNop.prototype = this.prototype
 
 // fBound.prototype.__proto__ = fNop.prototype
 fBound.prototype = new fNop()
 /**
 * 这样当new调用fBound后,实例依然能访问fToBind的原型方法
 * 为什么不直接fBound.prototype = this.prototype呢
 * 考虑下将fBound返回后,给fBound添加实例方法的情况
 * 即fBound.prototype.anotherMethod = function() {}
 * 如果将fToBind的原型直接赋值给fBound的原型,添加原型方法就会
 * 污染源方法即fToBind的原型
 */
 return fBound
}

具体详见从this机制看bind的实现


8、深拷贝

function deepClone (data) {
  var res = data instanceof Array ? [] : {}
  for (var key in data) {
    console.log(key)
    if (data.hasOwnProperty(key)) {
      if (typeof data[key] === 'object' && data[key] !== null) {
        res[key] = deepClone(data[key])
      } else {
        res[key] = data[key]
      }
    }
  }
  return res
}

9、闭包

定义: 有权限访问其他函数作用域内变量的一个函数。

应用场景: 由于 js 中,变量的作用域属于函数作用域,在函数执行后作用于就会被清理,内存也会随之回收。但是由于闭包是建立在一个函数内部的函数,由于其可访问上级作用域的原因,即使上级函数执行完后,作用于也不会随之销毁。这时子函数就是闭包,拥有访问上级作用域的权限。

闭包随处可见,比如一个请求的成功回调,一个事件绑定的回调方法。无论使用何种方式对函数类型的值进行传递,当函数在别处调用时都属于闭包。

function test () {
    var arr = []
    for(var i = 0; i < 10; i++) {
        arr[i] = function () {
            (function (n) {
                arr[n] = function() {
                    console.log(n)
                }
            }(i))
        }
    }
    return arr
}

var myArr = test()

for(var j = 0; j < 10; j++) {
    console.log(myArr[j]())
    myArr[j]()
}

10、promise

Promise 有三个状态值:

  • pending 初始状态
  • fulfilled 操作成功
  • rejected 操作失败
手写简易 promise
function promise (fn) {
  this.value = undefined
  this.err = undefined
  this.status = 'pending'
  var that = this
  
  function resolve (val) {
  	if (this.status === 'pending') {
      that.value = val
      that.status = 'fullfilled'
    }
  }
  
  function reject (err) {
  	if (this.status === 'pending') {
      that.err = err
      that.status = 'rejected'
    }
  }
  
  fn(resolve, reject)
}

promise.prototype.then = function(isSuccess, isFail) {
  var that = this
  return new promise (function(resolve, reject){
    setTimeout(() => {
      if (t.status === 'fullfilled') {
        // 将then1回调函数返回的值传给then2回调函数,以此类推
        resolve(isSuccess(t.value))
      }
      if (t.status === 'rejected') {
        reject(isFail(t.err))
      }
    })
  })
}
实现 promise.all 和 promise.race
function test (words, delay) {
  return new Promise(function(resolve, reject) {
    console.log(delay)
    setTimeout(() => {
      resolve(words)
    }, delay)
  })
}

let p1 = test('测试p1', 500)
let p2 = test('测试p2', 700)
let p3 = test('测试p3', 1500)

function promiseAll (promiseArray) {
  if (!Array.isArray(promiseArray)) {
    console.error('参数为数组')
    return
  }

  return new Promise(function(resolve, reject) {
    let resolveCount = 0
    let resolveRes = []
    for(let i = 0; i < promiseArray.length; i++) {
      Promise.resolve(promiseArray[i]).then(res => {
        resolveCount ++
        resolveRes[i] = res
        
        if (resolveCount === promiseArray.length) {
          resolve(resolveRes)
        }
      }, function(err) {
        reject(err)
      })
    }
  })
}

function promiseRace (promiseArray) {
  if (!Array.isArray(promiseArray)) {
    console.error('参数为数组')
    return
  }

  return new Promise(function(resolve, reject) {
    for(let i = 0; i < promiseArray.length; i++) {
      Promise.resolve(promiseArray[i]).then(function(res) {
        resolve(res)
      }, function(err) {
        reject(err)
      })
    }
  })
}

let pArr = [p1, p2, p3]

promiseAll(pArr).then((res) => {
  console.log("res", res)
}, (err) => {
  console.log("err", err)
})

promiseRace(pArr).then((res) => {
  console.log("resRace", res)
}, (err) => {
  console.log("errRace", err)
})


11、计算函数执行时间

可以使用console.time来计算函数执行的时间,具体查看MDN 相关文档

但是这个方法有一个缺点,只能在控制台显示结果没有返回值....

const getFunctionRunTime = (function() {
  function before (fn, beforeFn){
    beforeFn.apply(this, arguments)
    fn.apply(this, arguments)
  }
  
  function after (fn, afterFn) {
    // 先执行传入的 before 函数
    fn.apply(this, arguments)
    // before 函数和传入函数执行后,执行函数最后需要执行的函数
    afterFn.apply(this, arguments)
  }
  
  return function (functionToRun, functionName) {
    return after(before(functionToRun, function (){
      console.time(functionName)
    }),function(){
      console.timeEnd(functionName)
    })
  }
})()


function test () {
  for(let i = 0; i < 10000; i++) {}
}

test = getFunctionRunTime(test, 'test')
test() // test: 0.653ms

使用 performance.now 代替 console.time

const getFunPerformance = (function() {
   function before (fn, beforeFn) {
     return function () {
       beforeFn.apply(this, arguments)
       fn.apply(this, arguments)
     }
   }

   function after (fn, afterFn) {
     return function () {
       fn.apply(this, arguments)
       after.apply(this, arguments)
     }
   }

   return function (fun) {
      let timer = after(before(fun, function() {
        timer.startTime = performance.now()
      }), function() {
        timer.endTime = performance.now()
        timer.valueof = timer.endTime - timer.startTime
      })
      return timer
   }
})()

function someFunction() {
  console.log('someFunction')
  for (var i = 0; i < 10000; ++i) {
  }
}

someFunction = getFunPerformance(someFunction)
console.log(+someFunction())

浏览器相关

1、HTTP

get和post区别:
  • get 请求会被浏览器主动 cache,而 post 不会,除非手动设置;

  • get 把请求的参数放在 url 上,即 HTTP 协议头上 post 把参数放在 HTTP 的包体内;

  • get 方式传输的数据量较小,一般限制在 2 KB 左右,但是执行效率却比 post 方法好; post 方式传递的数据量相对较大,不过也有字节限制(实际上IIS4中最大量为80KB,IIS5中为100KB),这是为了避免对服务器用大量数据进行恶意攻击;

  • get 请求只能进行 url 编码,而 post 支持多种编码方式;

  • get 产生的 url 地址可以加入书签,而 post 不可以;

  • get 请求参数会被完整保留在浏览器历史记录里,而 post 中的参数不会被保留;

状态码:

状态码:由3位数字组成,第一个数字定义了响应的类别

1xx:指示信息,表示请求已接收,继续处理;
2xx:成功,表示请求已被成功接受处理;
3xx:重定向
  • 301 :永久重定向,表示请求的资源已经永久的搬到了其他位置;
  • 302 :临时重定向,表示请求的资源临时搬到了其他位置;
  • 304 :发送附带条件的请求时,条件不满足;
4xx:客户端错误
  • 400 :客户端请求有语法错误,服务器无法理解;
  • 401 :请求未经授权;
  • 403 :服务器收到请求,但是拒绝提供服务;
  • 404 :请求资源不存在;
5xx:服务器端错误

2、前端通信

跨域

由于同源策略限制限制,协议、域名与端口三者任何一个不一样,就算是跨域。

跨域是浏览器正确做出请求,服务器也能正确做出响应,是浏览器拒绝接收跨域服务器返回的数据。

跨域通信
JSONP(只支持GET请求)

通过 script 标签的异步加载来实现的。利用script标签不受同源策略的限制,天然可以跨域的特性。

let script = document.creatElement('script')
script.type = 'text/javascript'
script.src = 接口地址
//创建并添加script标签到<head>下
document.head.appendChild(script)

function getData(res) {
  return res
}
CORS

服务器返回数据时需要添加一个 Access-Control-Allow-Origin 响应头,并指明可以共享数据的域。 详解所有人都应该知道的跨域及CORS


3、浏览器渲染过程

  • 解析 html 生成 dom 树;
  • 解析 css 生成 cssom 树;
  • 将 dom 和 cssom 组合解析生成渲染树 render-tree;
  • 计算渲染树的布局;
  • 浏览器渲染;

框架

1、SSR 框架的选择, Nuxt、Next、Nest?

Next:性能居中,lighthouse 测试报告中比其他两者低。 优势:

  • 默认情况每一个组件都是服务端渲染;
  • 自动代码拆分,加快页面加载速度;

缺点:

  • 数据会在客户端和服务器重复加载;

Nuxt:性能为三者中最低,lighthouse 测试报告中的大多项都是领先者。 优点:

  • 主要范围是UI渲染,同时抽象出客户端/服务器分布;
  • 项目结构清晰;
  • 路由级别的异步数据获取;

缺点:

  • 周边资源较少;
  • 高流量可能会给服务器带来压力;

Nest:性能是三者中最好的,lighthouse 测试报告得分较低。 优点:

  • 基于TypeScript的Web框架,可以进行严格的类型定义;
  • 自动生成Swagger文档;
  • 为开发人员提供更少的上下文切换。从Angular代码到Nest的过渡相对容易;

缺点:

  • 缺少文档;
  • 与其他框架相比,Nest的社区规模较小;

参考文章原文链接

小程序

1、运行机制

启动方式:
  • 热启动:假如用户已经打开了某个小程序,在一定时间内再次打开小程序的话,这个时候我们就不再需要重新启动了,这需要把我们的后台打开的小程序切换到前台来使用;

  • 冷启动:用户首次打开小程序或被微信主动销毁再次打开的情况,此时小程序需要重新加载启动;

主动销毁:
  • 小程序在进入后台之后,客户端会帮我们在一定时间内维持我们的一个状态,超过五分钟后,会被微信主动销毁;
  • 当我们在短时间内连续两次收到系统告警的时候,微信就会主动销毁,短时间间隔是5s;

2、生命周期

负责页面视图的view线程和处理数据的服务的AppService服务线程协同完成生命周期调用;

页面生命周期:
  • onLoad 页面加载时触发,只会调用一次,可获取当前页面路径中的参数;
  • onShow 页面显示/切入前台时触发,一般用来发送数据请求;
  • onReady 页面初次渲染完成时触发, 只会调用一次,代表页面已可和视图层进行交互;
  • onHide 页面隐藏/切入后台时触发, 如底部 tab 切换到其他页面或小程序切入后台等;
  • onUnload 页面卸载时触发,如redirectTonavigateBack到其他页面时;
应用生命周期:
  • onLaunch 小程序初始化完成时触发,全局只触发一次。参数也可以使用 wx.getLaunchOptionsSync 获取;
  • onShow 小程序启动,或从后台进入前台显示时触发。也可以使用 wx.onAppShow 绑定监听;
  • onHide 小程序从前台进入后台时触发。也可以使用 wx.onAppHide 绑定监听;
  • onError 小程序发生脚本错误或 API 调用报错时触发。也可以使用 wx.onError 绑定监听;
  • onPageNotFound 小程序要打开的页面不存在时触发。也可以使用 wx.onPageNotFound 绑定监听;
  • onThemeChange 系统切换主题时触发。也可以使用 wx.onThemeChange 绑定监听;
  • onUnhandledRejection 小程序有未处理的 Promise 拒绝时触发。也可以使用 wx.onUnhandledRejection 绑定监听;