首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >前端各知识点梳理(施工中...)

前端各知识点梳理(施工中...)

作者头像
前端_AWhile
发布2020-05-18 17:14:38
2.3K0
发布2020-05-18 17:14:38
举报
文章被收录于专栏:前端一会前端一会

JS方面

ES

1. 讲下作用域的理解

作用域是一套用来管理引擎如何在当前作用域以及嵌套的子作用域中根据标识符名称进行变量查找的规则。

  • 作用域分类:
    • 全局作用域
    • 函数作用域
      • 概念:属于这个函数的全部变量都可以在整个函数的范围内使用及复用,包括在嵌套的函数作用域中也可以使用
      • 创建:函数作用域的创建就需要声明一个函数,而声明函数这个行为又有函数声明和函数表达式两种操作方式
    • 块作用域
      • 概念:{...}块内部
      • 创建:在es6版本后,可以通过letconst定义块作用域,典型应用是for循环。注意const虽然也可以创建块作用域,但有别不let,其值是固定的常量,任何对其值的修改都会引起错误
  • 作用域提升:
    • 概念:就是在作用域范围内,不管内部的变量与函数声明在何处,当执行到这个作用域时,引擎都会首先将当前作用域内的变量和函数声明放置到当前作用域顶端后,再按从上到下顺序执行其他代码。
    • 坑点:
      • 函数声明有提升行为,函数表达式不会有提升行为
      • 在同时有变量声明和函数声明的提升行为中,引擎会执行函数优先的准则,即先提升函数,再提升变量,这也体现js中函数是一等公民的地位。
2. 讲下闭包的理解

在掌握作用域的前提下,才能真正理解和识别闭包。

  • 概念: 内层函数能够访问外层函数作用域的变量
  • 缺点: 引起内存泄漏(释放内存)
  • 作用:
    • 使用闭包修正打印值
    • 实现柯里化
    • 实现私有变量,实现JS的模块化应用, 但在ES6后通过官方提供的importexport实现模块应用。
    • 保持变量与函数活性, 可延迟回收和执行

闭包的应用示例:

for( var i=0; i<5; i++ ){
    !function( j ){
        setTimeout(function(){
            console.log( j )
        }, j*1000)
    }( i )
}

// 也可以用 let 通过块作用域实现
for( let i=0; i<5; i++ ){
    setTimeout( function(){
        console.log( i )
    }, i*1000 )
}
3. this

当一个函数被调用时,会创建一个活动记录(也叫执行上下文)。这个上下文会包括函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this就是其中一个属性,会在函数执行过程中用到。而this的指向则取决于函数调用位置而非函数定义位置。也就是说谁调用函数,则函数上下文中的this就指向谁。

this的绑定规则如下。

  1. function关键字的函数:
  • 默认绑定
    • 概念:在没有应用其他规则时,this绑定遵循默认绑定
      • 非严格模式下:全局作用域中函数被调用时,该函数词法作用域内的this指向全局对象,浏览器环境中就是指向window,node环境中就是指向global
      • 严格模式时,函数调用时词法作用域内的this指向 undefined,报 TypeError错误
    • 优先级: p3(最低)
  • 隐式绑定
    • 概念:
      • 当函数在某个上下文对象中调用时,函数中this就指向该上下文对象,如var bar = obj.fn(),但隐式绑定容易造成误导
    • 优先级: p2
  • 显式绑定
    • 概念:
      • 为避免隐式绑定造成的误导,可使用函数自有方法callapply或硬绑定bind来显式明确具体函数调用时其内部this的指向,如var bar = fn.call(obj1);、硬绑定var baz = fn.bind(obj2)
    • 优先级: p1
  • new绑定
    • 概念:
      • 指通过new构造函数生成实例对象,此时构造函数内部的this就指向这个实例对象。
    • 优先级: p0
    • 当使用 new 关键字来发生构造函数调用时,会自动执行如下过程:
      • 如果构造函数本身没有return对象或者return的不是对象类型,那么 new构造函数表达式就自动返回一个实例对象
        • 这个实例对象的原型指向函数的prototype属性(fn.__proto__ === Fn.prototype)
        • 构造函数内部的this指向这个实例对象
      • 如果构造函数本身有return对象,那么 new构造函数表达式返回的是构造函数内部return出来的这个对象

先查看函数的调用位置,然后再通过绑定规则来判定this指向,如同时存在多种绑定规则的,则按优化级对比。

如果函数内部不关心this指向,可以使用例如call(null)来忽略函数中的this绑定。

  1. ES6中的箭头函数不遵循前述四种绑定规则,而是根据词法作用域来决定this绑定。即箭头函数会继承外层函数调用时的this绑定,并且不会管这个this绑定到底是什么。这点其实在ES5中已有实现,为var self = this;机制。
4. 数据类型

js规范中,定义七种数据类型,分为基本类型和引用类型两大类:

  • 基本类型: 字符串(string)、数字(number)、布尔(boolean)、空(null)、未定义(undefined)、Symbol(es6引入的新原始数据类型,表示独一无二的值)
  • 引用类型: 对象(object)

js中还有内置对象:String、Number、Boolean、Object、Function、Array、Date、RegExp、Error等。

其中字符串是基本数据类型,本身不存任何操作方法 。为了方便的对字符串进行操作,ES 提供了一个基本包装类型:String 对象 。它是一种特殊的引用类型,JS引擎每当读取一个字符串的时候,就会在内部创建一个对应的 String 对象,该对象提供了很多操作字符的方法,这就是为什么能对字符串调用方法的原因。

对象Object有4个数据描述符:value(属性值)、writable(可写)、enumerable(可枚举)、configurable(可配置),后三者的默认值均为true;2个访问描述符:getter和setter。

先看4个数据描述符:

  • value是属性的值
  • writable特性就是控制属性是否可改写
  • enumerable特性是控制属性是否会出现在对象的属性枚举中,所谓的可枚举,就相当于 “可以出现在对象属性的遍历中”,比如for…in循环
  • configurable特性就是控制属性是否可配置,即是否能通过defineProperty()方法来修改属性特性,当该特性值为false时,属性就不可配置

在用对象字符量方式创建对象时,对象的属性特性均会使用默认值

如果想自定义属性特性,可以通过Object.defineProperty()来添加一个新属性或者修改一个已有属性,当然想自定义的前提是configurable属性要为true。

var newObj = {};
Object.defineProperty(newObj, 'a', {
    value: 10,
    writable: true,
    enumerable: true,
    configurable: true
})

通过Object.defineProperty()来控制对象属性的特性,比较好玩的一个实现就是生成一个真正的常量属性(不可修改、重定义或者删除)

var obj = {};
Object.defineProperty(obj, 'a', {
    writable: false,
    configurable: false
})

再看2个访问描述符:当对属性定义访问描述符时,js会忽略它们的 value和writable特性,而改为关心 set和get以及configurable和enumerable特性。

var obj = {
    //给a定义一个getter
    get a(){
        return 3;
    }
}

Object.defineProperty(obj, 'b', {
    //给b设置一个getter
    get: function(){return this.a*2;},
    //确保b会出现对象的属性列表中
    enmuerable: true
})

console.log( obj.a );   // 3
console.log( obj.b );   // 6

对象遍历时需注意以下几点:

  • in操作符可以用来判断属性是否在对象及其原型链中
  • for…in…操作符只可以用来判断属性是否可枚举,即属性特性enumerable为true时可枚举
  • propertyIsEnumerable()会检查给定的属性名是否直接存在于对象中(而不是存在于原型链中),并且还需满足enumerable: true
  • Object.keys()会返回一个数组,包含所有可枚举属性
  • Object.getOwnPropertyNames()会返回一个数组,包含所有属性,无论它们是否可枚举
  • in和hasOwnProperty()的区别在于是否查找原型链,然而Object.keys()和Object.getOwnPropertyNames()都只会查找对象直接包含的属性
  • 目前并没有内置的方法可以获取in操作符使用的属性列表(对象本身的属性及原型链上的属性)。不过可以递归遍历某个对象的整条原型链并保存每层中使用Object.keys()得到的属性列表,这里只包含可枚举属性。
5. ES5和ES6继承方式区别
  • ES5以函数形式定义类, 以prototype来实现继承
  • ES6以class形式定义类, 以extend形式继承

手写实现4种继承

function Father () {}
function Child () {}

// 1. 原型继承
Child.prototype = new Father()

// 2. 构造继承
function Child (name) {
  Father.call(this, name)
}

// 3. 组合继承
function Child (name) {
  Father.call(this, name)
}
Child.prototype = new Father()

// 4. 寄生继承
function cloneObj (o) {
  var clone = object.create(o)
  clone.sayName = ...
  return clone
}

// 5. 寄生组合继承
// 6. ES6 class extend继承
class Child extends Father{
    super();
}

Object.create实现(原型式继承,特点:实例的proto指向构造函数本身)

6. Generator了解

ES6提供的一种异步编程解决方案,Generator函数是一个状态机,封装了多个内部状态。

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

let hw = helloWorldGenerator();

// 调用后返回指向内部状态的指针, 调用next()才会移向下一个状态, 参数:
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
7. async和await:
  • Generator函数的语法糖,将*改成async,将yield换成await。
  • 是对Generator函数的改进, 返回promise。
  • 异步写法同步化,遇到await先返回,执行完异步再执行接下来的.
  • 内置执行器, 无需next()
8. Promise调用实现
  • 优点: 解决回调地狱, 对异步任务写法更标准化与简洁化
  • 缺点: 首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消; 其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部; 第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)
const myPromise = new Promise((resolve, reject) => {
  // 需要执行的代码
  // ...
  if (/* 异步执行成功 */) {
    resolve(value)
  } else if (/* 异步执行失败 */) {
    reject(error)
  }
})

// promise一旦新建就会立即执行
myPromise
.then( value => {
    // 成功后调用, 使用value值
    },err=>{
    // 失败后调用,使用error值
})
.catch( error => {
  // 当执行多个then时也可统一捕获失败,使用error值
} )

极简版promise封装:

function Promise () {
  this.msg = '' // 存放value和error
  this.status = 'pending'
  const that = this
  const process = arguments[0]

  process (function () {
    that.status = 'fulfilled'
    that.msg = arguments[0]
  }, function () {
    that.status = 'rejected'
    that.msg = arguments[0]
  })
  return this
}

Promise.prototype.then = function () {
  if (this.status === 'fulfilled') {
    arguments[0](this.msg)
  } else if (this.status === 'rejected' && arguments[1]) {
    arguments[1](this.msg)
  }
}
9. 观察者模式

又称发布-订阅模式

//定义发布订阅对象
let EventEmitter = function() {
    let cacheList = {},     //缓存列表,存放已订阅的事件回调
        listen,             //订阅命名事件和对应事件回调
        emit,               //触发命名事件,必传第一个参数为事件的命名,其后参数为选传,数量不限,用于作为事件回调的实参传入
        remove;             //取消命名事件订阅,并清除该命名事件对应的事件回调

    listen = function(key, fn) {
        //如果还没有订阅过此命名事件,就给该命名事件创建一个数组型的缓存列表,用于存放对应的事件回调
        if (!cacheList[key]) {
            cacheList[key] = []
        }

        //将对应的事件回调传入该命名事件的缓存列表中
        cacheList[key].push(fn)
    }

    emit = function() {
        let key = Array.prototype.shift.call(arguments),     //取出事件命名
            fns = cacheList[key];       //取出该命名事件对应的事件回调缓存列表

        //如果没有订阅该命名事件或对应的事件回调缓存列表为空数组,则直接返回false
        if (!fns || fns.length == 0) {
            return false;
        }

        // 遍历该命名事件对应的事件回调缓存列表数组,对数组中的每个事件回调传入处理后的实参列表,然后执行
        for (let i = 0; i < fns.length; i++) {
            // arguments为触发命名事件时传入的参数类数组,此时arguments已被取出索引为0处的事件命名,剩余元素就是要传入事件回调中的所有参数
            fns[i].apply(this, arguments)
        }
    }

    remove = function(key, fn) {
        // 获取将要被删除的事件命名的事件回调缓存列表
        let fns = cacheList[key]

        // 如果没有预存事件回调或该命名事件对应的事件回调缓存列表为空数组,直接返回false
        if (!fns || fns.length == 0) {
            return false;
        }

        if (!fn) {
            // 如果没有显式传入具体的事件回调函数,则清除该命名事件对应的所有事件回调缓存
            fns.length = 0
        } else {
            // 遍历事件命名对应的事件回调缓存列表,如传入要删除的事件回调函数与缓存列表数组中的某项匹配,就删除该项
            for (let l = fns.length - 1; l >= 0; l--) {
                let _fn = fns[l]
                if (_fn == fn) {
                    fns.splice(l, 1)
                }
            }
        }
    }

    return {
        cacheList,
        listen,
        emit,
        remove
    }
}

let emitter = new EventEmitter();
emitter.listen( "event1", function(data){
    console.log( data )
} )
emitter.emit( "event1", "数据1" )
10. 手写实现bind
Function.prototype.bind = function () {
   // 保存原函数
  var self = this
  // 取出第一个参数作为上下文, 相当于[].shift.call(arguments)
  var context = Array.prototype.shift.call(arguments)
  // 取剩余的参数作为arg; 因为arguments是伪数组, 所以要转化为数组才能使用数组方法
  var arg = Array.prototype.slice.call(arguments)
  // 返回一个新函数
  return function () {
    // 绑定上下文并传参
    self.apply(context, Array.prototype.concat.call(arg, Array.prototype.slice.call(arguments)))
  }
}
11. 封装JSONP
function jsonp ( {url, param, callback} ) {
  return new Promise((resolve, reject) => {
    // 创建script标签
    let script = document.createElement('script')
    window.callback = function (data) {
      resolve(data)
      //移除script标签  
      document.body.removeChild('script')
    }
    let param = {...param, callback}
    let arr = []
    for (let key in param) {
      arr.push(`${key}=${param[key]}`)
    }
    // 拼接url地址
    script.src = `${url}?${arr.join('&')}`
    // 将创建好的script标签添加到body下面
    document.body.appendChild(script)
  })
}
12. setTimeout时间延迟为何不准

JS是单线程, 先执行同步主线程, 再执行异步任务队列

13. 事件循环述,宏任务和微任务有什么区别?
  • 先主线程后异步任务队列
  • 先微任务再宏任务
14. 节流和防抖
  • 函数节流是指一定时间内js方法只跑一次。比如人的眨眼睛,就是一定时间内眨一次。这是函数节流最形象的解释。
  • 函数防抖是指频繁触发的情况下,只有足够的空闲时间,才执行代码一次。比如生活中的坐公交,就是一定时间内,如果有人陆续刷卡上车,司机就不会开车。只有别人没刷卡了,司机才开车。
// 函数节流
let throttle = function( fn, interval ){
    let timer = null,
        firstTime = true;

    return function(){
        let _me = this;

        // 如果是第一次,不需要调用延迟器
        if( firstTime ){    
            fn.apply( _me, arguments );
            return firstTime = false;
        }

        // 如果定时器还在,则前一次延迟还未完成,则忽略该函数的下一次请求
        if( timer ){
            return false;
        }

        // 延迟一段时间执行
        timer = setTimeout( function(){
            clearTimeout( timer );
            timer = null;
            fn.apply( _me, arguments )
        }, interval || 500 )
    }
}

let callback = function(){
    console.log( 124 );
}

window.onresize = throttle( callback, 2000 );

// 函数防抖
var timer = false;
document.getElementById("debounce").onscroll = function(){
    clearTimeout(timer); // 清除未执行的代码,重置回初始化状态

    timer = setTimeout(function(){
        console.log("函数防抖");
    }, 300);
};
15. 实现一个sleep函数

这种sleep函数是最简单粗暴的,调用sleep函数会导致CPU占用高升,存在性能问题。执行环境兼容情况下可以另用async/await。

// 这种实现方式是利用一个伪死循环阻塞主线程。因为JS是单线程的。所以通过这种方式可以实现真正意义上的sleep()。
function sleep(delay) {
  var start = (new Date()).getTime();
  while ((new Date()).getTime() - start < delay) {
    continue;
  }
}

function test() {
  console.log('111');
  sleep(2000);
  console.log('222');
}

test()
16. JavaScript 模块化方案
①. IIFE 模块

为了避免全局污染,可以用匿名函数包裹起来,这就是最简单的 IIFE 模块

// 定义 IIFE 模块
const iifeCounterModule = (() => {
    let count = 0;
    return {
        increase: () => ++count,
        reset: () => {
            count = 0;
            console.log("Count is reset.");
        }
    };
})();

// 使用 IIFE 模块
iifeCounterModule.increase();
iifeCounterModule.reset();

// 可以在模块内部直接使用依赖的全局变量,也可以把依赖作为参数传给 IIFE
// 定义带有依赖的 IIFE 模块
const iifeCounterModule = ((dependencyModule1, dependencyModule2) => {
    let count = 0;
    return {
        increase: () => ++count,
        reset: () => {
            count = 0;
            console.log("Count is reset.");
        }
    };
})(dependencyModule1, dependencyModule2);
②. CommonJS 模块(Node.js 模块)

CommonJS 最初叫 ServerJS,是由 Node.js 实现的模块化方案。默认情况下,每个 .js 文件就是一个模块,模块内部提供了一个module和exports变量,用于暴露模块的 API。使用 require 加载和使用模块。

// 定义 CommonJS 模块: commonJSCounterModule.js.
const dependencyModule1 = require("./dependencyModule1");
const dependencyModule2 = require("./dependencyModule2");

let count = 0;
const increase = () => ++count;
const reset = () => {
    count = 0;
    console.log("Count is reset.");
};

exports.increase = increase;
exports.reset = reset;
// 或者这样:
module.exports = {
    increase,
    reset
};

// -----使用这个模块: 

// 使用 CommonJS 模块
const { increase, reset } = require("./commonJSCounterModule");
increase();
reset();
// 或者这样:
const commonJSCounterModule = require("./commonJSCounterModule");
commonJSCounterModule.increase();
commonJSCounterModule.reset();
③. AMD 模块(RequireJS 模块)

AMD(异步模块定义)也是一种模块格式,由 RequireJS 这个库实现。它通过define函数定义模块,并接受模块名和依赖的模块名作为参数。

// 定义 AMD 模块
define("amdCounterModule", ["dependencyModule1", "dependencyModule2"], 
      (dependencyModule1, dependencyModule2) => {
    let count = 0;
    const increase = () => ++count;
    const reset = () => {
        count = 0;
        console.log("Count is reset.");
    };

    return {
        increase,
        reset
    };
});

// ------用 require加载和使用模块:

require(["amdCounterModule"], amdCounterModule => {
    amdCounterModule.increase();
    amdCounterModule.reset();
});

跟 CommonJS 不同,这里的 requrie接受一个回调函数,参数就是加载好的模块对象。

AMD 的define函数还可以动态加载模块,只要给它传一个回调函数,并带上 require参数:

// Use dynamic AMD module.
define(require => {
    const dynamicDependencyModule1 = require("dependencyModule1");
    const dynamicDependencyModule2 = require("dependencyModule2");

    let count = 0;
    const increase = () => ++count;
    const reset = () => {
        count = 0;
        console.log("Count is reset.");
    };

    return {
        increase,
        reset
    };
});

AMD 模块还可以给define传递module和exports,这样就可以在内部使用 CommonJS 代码:

// 定义带有 CommonJS 代码的 AMD 模块
define((require, exports, module) => {
    // CommonJS 代码
    const dependencyModule1 = require("dependencyModule1");
    const dependencyModule2 = require("dependencyModule2");

    let count = 0;
    const increase = () => ++count;
    const reset = () => {
        count = 0;
        console.log("Count is reset.");
    };

    exports.increase = increase;
    exports.reset = reset;
});

// 使用带有 CommonJS 代码的 AMD 模块
define(require => {
    // CommonJS 代码
    const counterModule = require("amdCounterModule");
    counterModule.increase();
    counterModule.reset();
});
④. UMD 模块

UMD(通用模块定义),是一种支持多种环境的模块化格式,可同时用于 AMD 和 浏览器(或者 Node.js)环境。看起来很复杂,其实就是个 IIFE

兼容 AMD 和浏览器全局引入:

((root, factory) => {
    // 检测是否存在 AMD/RequireJS 的 define 函数
    if (typeof define === "function" && define.amd) {
        // 如果是,在 define 函数内调用 factory
        define("umdCounterModule", ["deependencyModule1", "dependencyModule2"], factory);
    } else {
        // 否则为浏览器环境,直接调用 factory
        // 导入的依赖是全局变量(window 对象的属性)
        // 导出的模块也是全局变量(window 对象的属性)
        root.umdCounterModule = factory(root.deependencyModule1, root.dependencyModule2);
    }
})(typeof self !== "undefined" ? self : this, (deependencyModule1, dependencyModule2) => {
    // 具体的模块代码
    let count = 0;
    const increase = () => ++count;
    const reset = () => {
        count = 0;
        console.log("Count is reset.");
    };

    return {
        increase,
        reset
    };
});

下面这个是兼容 AMD 和 CommonJS(Node.js)模块的 UMD:

(define => define((require, exports, module) => {
    // 模块代码
    const dependencyModule1 = require("dependencyModule1");
    const dependencyModule2 = require("dependencyModule2");

    let count = 0;
    const increase = () => ++count;
    const reset = () => {
        count = 0;
        console.log("Count is reset.");
    };

    module.export = {
        increase,
        reset
    };
}))(// 判断 CommonJS 里的 module 变量和 exports 变量是否存在
    // 同时判断 AMD/RequireJS 的define 函数是否存在
    typeof module === "object" && module.exports && typeof define !== "function"
        ? // 如果是 CommonJS/Node.js,手动定义一个 define 函数
            factory => module.exports = factory(require, exports, module)
        : // 否则是 AMD/RequireJS,直接使用 define 函数
            define);
⑤. ES 模块(ES6 Module)

主要语法就是 import和epxort关键字

// 定义 ES 模块:esCounterModule.js 或 esCounterModule.mjs.
import dependencyModule1 from "./dependencyModule1.mjs";
import dependencyModule2 from "./dependencyModule2.mjs";

let count = 0;
// 具名导出:
export const increase = () => ++count;
export const reset = () => {
    count = 0;
    console.log("Count is reset.");
};
// 默认导出
export default {
    increase,
    reset
};

//  ----- 浏览器里使用该模块,在 script标签上加上type="module",表明引入的是 ES 模块。在 Node.js 环境中使用时,把扩展名改成 .mjs。

// Use ES module.
//浏览器: <script type="module" src="esCounterModule.js"></script> or inline.

// 服务器:esCounterModule.mjs
import { increase, reset } from "./esCounterModule.mjs";
increase();
reset();
// Or import from default export:
import esCounterModule from "./esCounterModule.mjs";
esCounterModule.increase();
esCounterModule.reset();

// 浏览器如果不支持,可以加个兜底属性:
<script nomodule>
    alert("Not supported.");
</script>
// example2.js  // 导出默认, 有且只有一个默认
export default const example2 = {
  name : 'my name',
  age : 'my age',
  getName  = function(){  return 'my name' }
}
//全部导入 // 名字可以修改
import people from './example2.js'

// -------------------我是一条华丽的分界线---------------------------

// example1.js // 部分导出
export let name  = 'my name'
export let age  = 'my age'
export let getName  = function(){ return 'my name'}

// 导入部分 // 名字必须和 定义的名字一样。
import  {name, age} from './example1.js'

//有一种特殊情况,即允许你将整个模块当作单一对象进行导入
//该模块的所有导出都会作为对象的属性存在
import * as example from "./example1.js"
console.log(example.name)
console.log(example.age)
console.log(example.getName())

// -------------------我是一条华丽的分界线---------------------------

// example3.js  // 有导出默认, 有且只有一个默认,// 又有部分导出
export default const example3 = {
  birthday : '2018 09 20'
}
export let name  = 'my name'
export let age  = 'my age'
export let getName  = function(){ return 'my name'}

// 导入默认与部分
import example3, {name, age} from './example1.js'

// 总结:
// 1.当用 export default people 导出时,就用 import people 导入(不带大括号)

// 2.一个文件里,有且只能有一个 export default。但可以有多个 export。

// 3.当用 export name 时,就用 import { name }导入(记得带上大括号)

// 4.当一个文件里,既有一个 export default people, 又有多个 export name 或者 export age 时,导入就用 import people, { name, age } 

// 5.当一个文件里出现 n 多个 export 导出很多模块,导入时除了一个一个导入,也可以用 import * as example

CSS方面

1. 扩大可点击区域
  • 关键实现:伪元素
  • 具体分析:利用伪元素和定位达到鼠标移到边缘时候出现手型且可点击
.expand-range {
  position: relative;
}
.expand-range:after {
  content: '';
  position: absolute;
  top: -10px; right: -10px; bottom: -10px; left: -10px;
}

/* 推荐使用Scss */
@mixin expand-range($top: -10px, $right: $top, $bottom: $top, $left: $right, $position: relative) {
  position: $position;
  &:after {
    content: '';
    position: absolute;
    top: $top;
    right: $right;
    bottom: $bottom;
    left: $left;
  }
}
//使用:.test { @include expand-range($top: -5px, $position: absolute) }

算法方面

浏览器网络方面

1. reflow(回流)和repaint(重绘)优化
  • 浏览器渲染过程: DOM tree, CSS tree --> Render tree --> Paint
  • DOM tree根节点为html
  • 渲染从浏览器左上角到右下角
  • 第一次打开页面至少触发一次重绘和回流, 结构如宽高位置变化时, 触发reflow回流;非结构如背景色变化时, 触发repaint重绘. 二者都会造成体验不佳
  • 如何减少重绘和回流?
    • 通过classname或cssText一次性修改样式, 而非一个一个改
    • 离线模式: 克隆要操作的结点, 操作后再与原始结点交换, 类似于虚拟DOM
    • 避免频繁直接访问计算后的样式, 而是先将信息保存下来
    • 绝对布局的DOM, 不会造成大量reflow
    • 不要嵌套太深, 不要超过六层
2. 一个页面从输入 URL 到页面加载显示完成,这个过程中都发生了什么?
  • 浏览器根据请求的URL交给DNS域名解析,找到真实IP,向服务器发起请求;
  • 服务器交给后台处理完成后返回数据,浏览器接收文件(HTML、JS、CSS、图象等);
  • 浏览器对加载到的资源(HTML、JS、CSS等)进行语法解析,建立相应的内部数据结构(如HTML的DOM Tree);
  • 载入解析到的资源文件,渲染页面,完成。
3. localStorage 与 sessionStorage 与cookie的区别总结
  • 共同点: 都保存在浏览器端, 且同源
  • localStorage 与 sessionStorage 统称webStorage,保存在浏览器,不参与服务器通信,大小为5M
  • 生命周期不同: localStorage永久保存, sessionStorage当前会话, 都可手动清除
  • 作用域不同: 不同浏览器不共享local和session, 不同会话不共享session
  • Cookie: 设置的过期时间前一直有效, 大小4K.有个数限制, 各浏览器不同, 一般为20个.携带在HTTP头中, 过多会有性能问题.可自己封装, 也可用原生
4. 浏览器如何阻止事件传播,阻止默认行为
  • 阻止事件传播(冒泡): e.stopPropagation()
  • 阻止默认行为: e.preventDefault()
5. 虚拟DOM方案相对原生DOM操作有什么优点,实现上是什么原理?

虚拟DOM可提升性能, 无须整体重新渲染, 而是局部刷新。JS对象, diff算法

6. 浏览器事件机制中事件触发三个阶段
  • 事件捕获阶段: 从dom树节点往下找到目标节点, 不会触发函数
  • 事件目标处理函数: 到达目标节点
  • 事件冒泡: 最后从目标节点往顶层元素传递, 通常函数在此阶段执行

addEventListener第三个参数默认false(冒泡阶段执行),true(捕获阶段执行)

7. 什么是跨域?为什么浏览器要使用同源策略?你有几种方式可以解决跨域问题?了解预检请求嘛?
  • 跨域是指一个域下的文档或脚本试图去请求另一个域下的资源
  • 防止XSS、CSFR等攻击, 协议+域名+端口不同
  • jsonp; 跨域资源共享(CORS)(Access control); 服务器正向代理等
  • 预检请求: 需预检的请求要求必须首先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。"预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响
8. 了解浏览器缓存机制吗?
  • 浏览器缓存就是把一个已经请求过的资源拷贝一份存储起来,当下次需要该资源时,浏览器会根据缓存机制决定直接使用缓存资源还是再次向服务器发送请求
  • from memory cache ; from disk cache
  • 作用: 减少网络传输的损耗以及降低服务器压力。
  • 优先级: 强制缓存 > 协商缓存; cache-control > Expires > Etag > Last-modified
9. 为什么操作 DOM 慢?

DOM本身是一个js对象, 操作这个对象本身不慢, 但是操作后触发了浏览器的行为, 如repaint和reflow等浏览器行为, 使其变慢

10. 什么情况会阻塞渲染
  • js脚本同步执行
  • css和图片虽然是异步加载, 但js文件执行需依赖css, 所以css也会阻塞渲染
11. 如何判断js运行在浏览器中还是node中

判断有无全局对象global和window

12. 关于web以及浏览器处理预加载有哪些思考
  • 图片等静态资源在使用之前就提前请求
  • 资源使用到的时候能从缓存中加载, 提升用户体验
  • 页面展示的依赖关系维护
13. http多路复用
  • Keep-Alive: Keep-Alive解决的核心问题:一定时间内,同一域名多次请求数据,只建立一次HTTP请求,其他请求可复用每一次建立的连接通道,以达到提高请求效率的问题。这里面所说的一定时间是可以配置的,不管你用的是Apache还是nginx。
  • 解决两个问题: 串行文件传输(采用二进制数据帧); 连接数过多(采用流, 并行传输)
14. http和https
  • http: 最广泛网络协议,BS模型,浏览器高效。
  • https: 安全版,通过SSL加密,加密传输,身份认证,密钥
    • https相对于http加入了ssl层, 加密传输, 身份认证;
    • 需要到ca申请收费的证书;
    • 安全但是耗时多,缓存不是很好;
    • 注意兼容http和https;
    • 连接方式不同, 端口号也不同, http是80, https是443
15. 正向代理和反向代理

正向代理:

  • 访问原来无法访问的资源,如google
  • 可以做缓存,加速访问资源
  • 对客户端访问授权,上网进行认证
  • 代理可以记录用户访问记录(上网行为管理),对外隐藏用户信息

反向代理:

  • 保证内网的安全,可以使用反向代理提供WAF功能,阻止web攻击大型网站,通常将反向代理作为公网访问地址,Web服务器是内网。
  • 负载均衡,通过反向代理服务器来优化网站的负载

webpack方面

1. 有哪些常见的Loader?你用过哪些Loader?
  • raw-loader:加载文件原始内容(utf-8)
  • file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件 (处理图片和字体)
  • url-loader:与 file-loader 类似,区别是用户可以设置一个阈值,大于阈值时返回其 publicPath,小于阈值时返回文件 base64 形式编码 (处理图片和字体)
  • source-map-loader:加载额外的 Source Map 文件,以方便断点调试
  • svg-inline-loader:将压缩后的 SVG 内容注入代码中
  • image-loader:加载并且压缩图片文件
  • json-loader:加载 JSON 文件(默认包含)
  • handlebars-loader: 将 Handlebars 模版编译成函数并返回
  • babel-loader:把 ES6 转换成 ES5
  • ts-loader: 将 TypeScript 转换成 JavaScript
  • awesome-typescript-loader:将 TypeScript 转换成 JavaScript,性能优于 ts-loader
  • style-loader:将 CSS 代码注入 JavaScript 中,通过 DOM 操作去加载 CSS
  • css-loader:加载 CSS,支持模块化、压缩、文件导入等特性
  • style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS
  • postcss-loader:扩展 CSS 语法,使用下一代 CSS,可以配合 autoprefixer 插件自动补齐 CSS3 前缀
  • eslint-loader:通过 ESLint 检查 JavaScript 代码
  • tslint-loader:通过 TSLint检查 TypeScript 代码
  • mocha-loader:加载 Mocha 测试用例的代码
  • coverjs-loader:计算测试的覆盖率
  • vue-loader:加载 Vue.js 单文件组件
  • i18n-loader: 国际化
  • cache-loader: 可以在一些性能开销较大的 Loader 之前添加,目的是将结果缓存到磁盘里
2. 有哪些常见的Plugin?你用过哪些Plugin?
  • define-plugin:定义环境变量 (Webpack4 之后指定 mode 会自动配置)
  • ignore-plugin:忽略部分文件
  • html-webpack-plugin:简化 HTML 文件创建 (依赖于 html-loader)
  • web-webpack-plugin:可方便地为单页应用输出 HTML,比 html-webpack-plugin 好用
  • uglifyjs-webpack-plugin:不支持 ES6 压缩 (Webpack4 以前)
  • terser-webpack-plugin: 支持压缩 ES6 (Webpack4)
  • webpack-parallel-uglify-plugin: 多进程执行代码压缩,提升构建速度
  • mini-css-extract-plugin: 分离样式文件,CSS 提取为独立文件,支持按需加载 (替代extract-text-webpack-plugin)
  • serviceworker-webpack-plugin:为网页应用增加离线缓存功能
  • clean-webpack-plugin: 目录清理
  • ModuleConcatenationPlugin: 开启 Scope Hoisting
  • speed-measure-webpack-plugin: 可以看到每个 Loader 和 Plugin 执行耗时 (整个打包耗时、每个 Plugin 和 Loader 耗时)
  • webpack-bundle-analyzer: 可视化 Webpack 输出文件的体积 (业务组件、依赖第三方模块)
3. 那你再说一说Loader和Plugin的区别?

Loader 本质就是一个函数,在该函数中对接收到的内容进行转换,返回转换后的结果。因为 Webpack 只认识 JavaScript,所以 Loader 就成了翻译官,对其他类型的资源进行转译的预处理工作。

Plugin 就是插件,基于事件流框架 Tapable,插件可以扩展 Webpack 的功能,在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

Loadermodule.rules 中配置,作为模块的解析规则,类型为数组。每一项都是一个 Object,内部包含了 test(类型文件)、loader、options (参数)等属性。

Plugin 在 plugins 中单独配置,类型为数组,每一项是一个 Plugin 的实例,参数都通过构造函数传入。

4. Webpack构建流程简单说一下

Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:

  • 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数
  • 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译
  • 确定入口:根据配置中的 entry 找出所有的入口文件
  • 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
  • 完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系
  • 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会
  • 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统

在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。

简单说

  • 初始化:启动构建,读取与合并配置参数,加载 Plugin,实例化 Compiler
  • 编译:从 Entry 出发,针对每个 Module 串行调用对应的 Loader 去翻译文件的内容,再找到该 Module 依赖的 Module,递归地进行编译处理
  • 输出:将编译后的 Module 组合成 Chunk,将 Chunk 转换成文件,输出到文件系统中
5. 使用webpack开发时,你用过哪些可以提高效率的插件?
  • webpack-dashboard:可以更友好的展示相关打包信息。
  • webpack-merge:提取公共配置,减少重复配置代码
  • speed-measure-webpack-plugin:简称 SMP,分析出 Webpack 打包过程中 Loader 和 Plugin 的耗时,有助于找到构建过程中的性能瓶颈。
  • size-plugin:监控资源体积变化,尽早发现问题
  • HotModuleReplacementPlugin:模块热替换
6. source map是什么?生产环境怎么用?

source map 是将编译、打包、压缩后的代码映射回源代码的过程。打包压缩后的代码不具备良好的可读性,想要调试源码就需要 soucre map。

map文件只要不打开开发者工具,浏览器是不会加载的。

线上环境一般有三种处理方案:

  • hidden-source-map:借助第三方错误监控平台 Sentry 使用
  • nosources-source-map:只会显示具体行数以及查看源代码的错误栈。安全性比 sourcemap 高
  • sourcemap:通过 nginx 设置将 .map 文件只对白名单开放(公司内网)

注意:避免在生产中使用 inline- 和 eval-,因为它们会增加 bundle 体积大小,并降低整体性能。

7. 模块打包原理知道吗?

Webpack 实际上为每个模块创造了一个可以导出和导入的环境,本质上并没有修改代码的执行逻辑,代码执行顺序与模块加载顺序也完全一致。

8. 文件监听原理呢?

在发现源码发生变化时,自动重新构建出新的输出文件。

Webpack开启监听模式,有两种方式:

  • 启动 webpack 命令时,带上 --watch 参数
  • 在配置 webpack.config.js 中设置 watch:true

缺点:每次需要手动刷新浏览器

原理:轮询判断文件的最后编辑时间是否变化,如果某个文件发生了变化,并不会立刻告诉监听者,而是先缓存起来,等 aggregateTimeout 后再执行。

module.export = {
    // 默认false,也就是不开启
    watch: true,
    // 只有开启监听模式时,watchOptions才有意义
    watchOptions: {
        // 默认为空,不监听的文件或者文件夹,支持正则匹配
        ignored: /node_modules/,
        // 监听到变化发生后会等300ms再去执行,默认300ms
        aggregateTimeout:300,
        // 判断文件是否发生变化是通过不停询问系统指定文件有没有变化实现的,默认每秒问1000次
        poll:1000
    }
}
9. 说一下 Webpack 的热更新原理吧

Webpack 的热更新又称热替换(Hot Module Replacement),缩写为 HMR。这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。

HMR的核心就是客户端从服务端拉去更新后的文件,准确的说是 chunk diff (chunk 需要更新的部分),实际上 WDS 与浏览器之间维护了一个 Websocket,当本地资源发生变化时,WDS 会向浏览器推送更新,并带上构建时的 hash,让客户端与上一次资源进行对比。客户端对比出差异后会向 WDS 发起 Ajax 请求来获取更改内容(文件列表、hash),这样客户端就可以再借助这些信息继续向 WDS 发起 jsonp 请求获取该chunk的增量更新。

后续的部分(拿到增量更新之后如何处理?哪些状态该保留?哪些又需要更新?)由 HotModulePlugin 来完成,提供了相关 API 以供开发者针对自身场景进行处理,像react-hot-loader 和 vue-loader 都是借助这些 API 实现 HMR。

10. 如何对bundle体积进行监控和分析?

VSCode 中有一个插件 Import Cost 可以帮助我们对引入模块的大小进行实时监测,还可以使用 webpack-bundle-analyzer 生成 bundle 的模块组成图,显示所占体积。

bundlesize 工具包可以进行自动化资源体积监控。

11. 文件指纹是什么?怎么用?

文件指纹是打包后输出的文件名的后缀。

  • Hash:和整个项目的构建相关,只要项目文件有修改,整个项目构建的 hash 值就会更改
  • Chunkhash:和 Webpack 打包的 chunk 有关,不同的 entry 会生出不同的 chunkhash
  • Contenthash:根据文件内容来定义 hash,文件内容不变,则 contenthash 不变

JS的文件指纹设置

设置 output 的 filename,用 chunkhash。

module.exports = {
    entry: {
        app: './scr/app.js',
        search: './src/search.js'
    },
    output: {
        filename: '[name][chunkhash:8].js',
        path:__dirname + '/dist'
    }
}

CSS的文件指纹设置

设置 MiniCssExtractPlugin 的 filename,使用 contenthash。

module.exports = {
    entry: {
        app: './scr/app.js',
        search: './src/search.js'
    },
    output: {
        filename: '[name][chunkhash:8].js',
        path:__dirname + '/dist'
    },
    plugins:[
        new MiniCssExtractPlugin({
            filename: `[name][contenthash:8].css`
        })
    ]
}

图片的文件指纹设置

设置file-loader的name,使用hash。

占位符名称及含义

  • ext 资源后缀名
  • name 文件名称
  • path 文件的相对路径
  • folder 文件所在的文件夹
  • contenthash 文件的内容hash,默认是md5生成
  • hash 文件内容的hash,默认是md5生成
  • emoji 一个随机的指代文件内容的emoji
const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        filename:'bundle.js',
        path:path.resolve(__dirname, 'dist')
    },
    module:{
        rules:[{
            test:/\.(png|svg|jpg|gif)$/,
            use:[{
                loader:'file-loader',
                options:{
                    name:'img/[name][hash:8].[ext]'
                }
            }]
        }]
    }
}
12. 在实际工程中,配置文件上百行乃是常事,如何保证各个loader按照预想方式工作?

可以使用 enforce 强制执行 loader 的作用顺序,pre 代表在所有正常 loader 之前执行,post 是所有 loader 之后执行。(inline 官方不推荐使用)

13. 如何优化 Webpack 的构建速度?
  • 使用高版本的 Webpack 和 Node.js
  • 多进程/多实例构建:thread-loader
  • 压缩代码
    • webpack-paralle-uglify-plugin
    • uglifyjs-webpack-plugin 开启 parallel 参数 (不支持ES6)
    • terser-webpack-plugin 开启 parallel 参数
    • 多进程并行压缩
    • 通过 mini-css-extract-plugin 提取 Chunk 中的 CSS 代码到单独文件,通过 css-loader 的 minimize 选项开启 cssnano 压缩 CSS。
  • 图片压缩
    • 使用基于 Node 库的 imagemin (很多定制选项、可以处理多种图片格式)
    • 配置 image-webpack-loader
  • 缩小打包作用域
    • exclude/include (确定 loader 规则范围)
    • resolve.modules 指明第三方模块的绝对路径 (减少不必要的查找)
    • resolve.mainFields 只采用 main 字段作为入口文件描述字段 (减少搜索步骤,需要考虑到所有运行时依赖的第三方模块的入口文件描述字段)
    • resolve.extensions 尽可能减少后缀尝试的可能性
    • noParse 对完全不需要解析的库进行忽略 (不去解析但仍会打包到 bundle 中,注意被忽略掉的文件里不应该包含 import、require、define 等模块化语句)
    • IgnorePlugin (完全排除模块)
    • 合理使用alias
  • 提取页面公共资源
    • 使用 html-webpack-externals-plugin,将基础包通过 CDN 引入,不打入 bundle 中
    • 使用 SplitChunksPlugin 进行(公共脚本、基础包、页面公共文件)分离(Webpack4内置) ,替代了 CommonsChunkPlugin 插件
    • 基础包分离
  • DLL
    • 使用 DllPlugin 进行分包,使用 DllReferencePlugin(索引链接) 对 manifest.json 引用,让一些基本不会改动的代码先打包成静态资源,避免反复编译浪费时间。
    • HashedModuleIdsPlugin 可以解决模块数字id问题
  • 充分利用缓存提升二次构建速度
    • babel-loader 开启缓存
    • terser-webpack-plugin 开启缓存
    • 使用 cache-loader 或者 hard-source-webpack-plugin
  • Tree shaking
    • purgecss-webpack-plugin 和 mini-css-extract-plugin配合使用(建议)
    • 打包过程中检测工程中没有引用过的模块并进行标记,在资源压缩时将它们从最终的bundle中去掉(只能对ES6 Modlue生效) 开发中尽可能使用ES6 Module的模块,提高tree shaking效率
    • 禁用 babel-loader 的模块依赖解析,否则 Webpack 接收到的就都是转换过的 CommonJS 形式的模块,无法进行 tree-shaking
    • 使用 PurifyCSS(不在维护) 或者 uncss 去除无用 CSS 代码
  • Scope hoisting
    • 构建后的代码会存在大量闭包,造成体积增大,运行代码时创建的函数作用域变多,内存开销变大。Scope hoisting 将所有模块的代码按照引用顺序放在一个函数作用域里,然后适当的重命名一些变量以防止变量名冲突
    • 必须是ES6的语法,因为有很多第三方库仍采用 CommonJS 语法,为了充分发挥 Scope hoisting 的作用,需要配置 mainFields 对第三方模块优先采用 jsnext:main 中指向的ES6模块化语法
  • 动态Polyfill
    • 建议采用 polyfill-service 只给用户返回需要的polyfill,社区维护。(部分国内奇葩浏览器UA可能无法识别,但可以降级返回所需全部polyfill)
14. 你刚才也提到了代码分割,那代码分割的本质是什么?有什么意义呢?

代码分割的本质其实就是在源代码直接上线和打包成唯一脚本main.bundle.js这两种极端方案之间的一种更适合实际场景的中间状态。

「用可接受的服务器性能压力增加来换取更好的用户体验。」

源代码直接上线:虽然过程可控,但是http请求多,性能开销大。

打包成唯一脚本:一把梭完自己爽,服务器压力小,但是页面空白期长,用户体验不好。

15. 是否写过Loader?简单描述一下编写loader的思路?

Loader 支持链式调用,所以开发上需要严格遵循“单一职责”,每个 Loader 只负责自己需要负责的事情。

Loader的API 可以去官网查阅: https://www.webpackjs.com/api/loaders

  • Loader 运行在 Node.js 中,我们可以调用任意 Node.js 自带的 API 或者安装第三方模块进行调用
  • Webpack 传给 Loader 的原内容都是 UTF-8 格式编码的字符串,当某些场景下 Loader 处理二进制文件时,需要通过 exports.raw = true 告诉 Webpack 该 Loader 是否需要二进制数据
  • 尽可能的异步化 Loader,如果计算量很小,同步也可以
  • Loader 是无状态的,我们不应该在 Loader 中保留状态
  • 使用 loader-utils 和 schema-utils 为我们提供的实用工具
  • 加载本地 Loader 方法
    • Npm link
    • ResolveLoader
16. 是否写过Plugin?简单描述一下编写Plugin的思路?

webpack在运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在特定的阶段钩入想要添加的自定义功能。Webpack 的 Tapable 事件流机制保证了插件的有序性,使得整个系统扩展性良好。

Plugin的API 可以去官网查阅:https://www.webpackjs.com/api/plugins

  • compiler 暴露了和 Webpack 整个生命周期相关的钩子
  • compilation 暴露了与模块和依赖有关的粒度更小的事件钩子
  • 插件需要在其原型上绑定apply方法,才能访问 compiler 实例
  • 传给每个插件的 compiler 和 compilation对象都是同一个引用,若在一个插件中修改了它们身上的属性,会影响后面的插件
  • 找出合适的事件点去完成想要的功能
    • emit 事件发生时,可以读取到最终输出的资源、代码块、模块及其依赖,并进行修改(emit 事件是修改 Webpack 输出资源的最后时机)
    • watch-run 当依赖的文件发生变化时会触发
  • 异步的事件需要在插件处理完任务时调用回调函数通知 Webpack 进入下一个流程,不然会卡住
17. 聊一聊Babel原理吧

大多数JavaScript Parser遵循 estree 规范,Babel 最初基于 acorn 项目(轻量级现代 JavaScript 解析器)Babel大概分为三大部分:

  • 解析:将代码转换成 AST
    • 词法分析:将代码(字符串)分割为 token 流,即语法单元成的数组
    • 语法分析:分析 token 流(上面生成的数组)并生成 AST
  • 转换:访问 AST 的节点进行变换操作生产新的 AST
    • Taro 就是利用 babel 完成的小程序语法转换
  • 生成:以新的 AST 为基础生成代码
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-05-14 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • JS方面
    • ES
      • 1. 讲下作用域的理解
      • 2. 讲下闭包的理解
      • 3. this
      • 4. 数据类型
      • 5. ES5和ES6继承方式区别
      • 6. Generator了解
      • 8. Promise调用实现
      • 9. 观察者模式
      • 10. 手写实现bind
      • 11. 封装JSONP
      • 12. setTimeout时间延迟为何不准
      • 13. 事件循环述,宏任务和微任务有什么区别?
      • 14. 节流和防抖
      • 15. 实现一个sleep函数
      • 16. JavaScript 模块化方案
      • 1. 扩大可点击区域
      • 1. reflow(回流)和repaint(重绘)优化
      • 2. 一个页面从输入 URL 到页面加载显示完成,这个过程中都发生了什么?
      • 3. localStorage 与 sessionStorage 与cookie的区别总结
      • 4. 浏览器如何阻止事件传播,阻止默认行为
      • 5. 虚拟DOM方案相对原生DOM操作有什么优点,实现上是什么原理?
      • 6. 浏览器事件机制中事件触发三个阶段
      • 7. 什么是跨域?为什么浏览器要使用同源策略?你有几种方式可以解决跨域问题?了解预检请求嘛?
      • 8. 了解浏览器缓存机制吗?
      • 9. 为什么操作 DOM 慢?
      • 10. 什么情况会阻塞渲染
      • 11. 如何判断js运行在浏览器中还是node中
      • 12. 关于web以及浏览器处理预加载有哪些思考
      • 13. http多路复用
      • 14. http和https
      • 15. 正向代理和反向代理
      • 1. 有哪些常见的Loader?你用过哪些Loader?
      • 2. 有哪些常见的Plugin?你用过哪些Plugin?
      • 3. 那你再说一说Loader和Plugin的区别?
      • 4. Webpack构建流程简单说一下
      • 5. 使用webpack开发时,你用过哪些可以提高效率的插件?
      • 6. source map是什么?生产环境怎么用?
      • 7. 模块打包原理知道吗?
      • 8. 文件监听原理呢?
      • 9. 说一下 Webpack 的热更新原理吧
      • 10. 如何对bundle体积进行监控和分析?
      • 11. 文件指纹是什么?怎么用?
      • 12. 在实际工程中,配置文件上百行乃是常事,如何保证各个loader按照预想方式工作?
      • 13. 如何优化 Webpack 的构建速度?
      • 14. 你刚才也提到了代码分割,那代码分割的本质是什么?有什么意义呢?
      • 15. 是否写过Loader?简单描述一下编写loader的思路?
      • 16. 是否写过Plugin?简单描述一下编写Plugin的思路?
      • 17. 聊一聊Babel原理吧
  • CSS方面
  • 算法方面
  • 浏览器网络方面
  • webpack方面
相关产品与服务
内容分发网络 CDN
内容分发网络(Content Delivery Network,CDN)通过将站点内容发布至遍布全球的海量加速节点,使其用户可就近获取所需内容,避免因网络拥堵、跨运营商、跨地域、跨境等因素带来的网络不稳定、访问延迟高等问题,有效提升下载速度、降低响应时间,提供流畅的用户体验。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档