前端核心基础1

https://segmentfault.com/a/1190000039783802?utm_source=weekly&utm_medium=email&utm_campaign=email_weekly
[TOC]

js 基础:bind 手写实现

算法:排序、查找、二叉树

ES6:新特性、promise 和 setTimeout 区别 - Event Loop、map、set、async、let/const/var、解构赋值、剪头函数

原型链:封装、继承、多态;闭包

Vue/react:diff 原理、watch data 响应式原理、vuex 原理、实现数据传递多种方法

Webpack:配置 设计 cli

协议:http 和 https 区别、TCP 三次握手

安全:xss、csrf

Css: repaint、reflow

Babel: ployfill 和 transform-runtime

js 原生-自定义事件new Event()和 new CustomEvent(),dispatchEvent()/fireEvent

架构思路:

JS 基础知识点

全等

=======类型转换规则

运算符优先级

官网优先级表
连续赋值

浅拷贝

浅拷贝这样定义:只拷贝第一层的原始类型值,和第一层的引用类型地址。
详解文章

  • Object.assign({}, aObj, bObj)只会拷贝所有的属性值到新的对象中,如果属性值是对象的话,拷贝的是地址,所以并不是深拷贝。
  • Object.create(aObj)同样只会拷贝所有的属性值到新的对象中,如果属性值是对象的话,拷贝的是地址,所以并不是深拷贝。
  • Array.prototype.slice
  • Array.prototype.concat
  • ...运算符

深拷贝

深拷贝定义为:拷贝所有的属性值,以及属性地址指向的值的内存空间。

浅拷贝只解决了第一层 key:val 的问题,如果接下去的值中还有对象的话,因为两者享有相同的地址。要解决这个问题,我们就得使用深拷贝了

  • bObj = JSON.parse(JSON.stringify(aObj))来解决,但是局限性:1.忽略undefinedsymbol;2.不能序列化函数,不能解决循环引用的对象
  • 递归存在 引用丢失 的的问题。步骤:判断 typeof keyValue === 'object' 考虑null, 函数;再细分Array[...keyValue],Setnew Set([...keyValue]),Mapnew Map([...keyValue]) 还是普通Object{...keyValue} & 递归自身 - 见示例代码
  • 如果你所需拷贝的对象含有内置类型并且不包含函数,可以使用MessageChannel
  • 利用”栈”,很赞的想法:引入一个数组 uniqueList 用来存储已经拷贝的数组,每次循环遍历时,先判断对象是否在 uniqueList 中了,如果在的话就不执行拷贝逻辑了。代码在这!
  • 使用 lodash 库的cloneDeep

原型 proto

  • JavaScript中,一切都是对象,函数也是对象,数组也是对象,但是数组是对象的子集。
  • 函数实际上是对象,而且与其他引用类型一样具有属性和方法,由于函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。
    对象都是由函数创建的。
  • 所有的对象都是由Object继承而来,而Object对象却是一个函数。
  • 每一个函数实际上都是Function类型的实例

图片
每个 JS 对象都有 __proto__ 属性,这个属性指向了原型对象,在该对象中定义了很多函数让我们来使用;原型对象的 constructor 属性指向构造函数,构造函数又通过 prototype 属性指回原型。

instance.__proto__ = Fn.prototype

  • 构造函数Object.prototype 是所有对象的爸爸,所有对象都可以通过 __proto__ 找到它
  • 构造函数Function.prototype 是所有函数的爸爸,所有函数都可以通过 __proto__ 找到它
  • 函数的 prototype 是一个对象,即Function.prototype函数原型
  • 对象的 __proto__ 属性指向Object.prototype对象原型, __proto__ 将对象和原型连接起来组成了原型链

闭包

  • 函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包
  • 经典面试题,循环中使用闭包解决 var 定义函数的问题:for(var i){setTimeout(fun(){}, i)}。解决办法 1.用自执行闭包;2.setTimeout 的第三个参数当作内部函数的参数传入;3.let代替var

Call, apply 和 bind,手写简单实现

apply 可接受多个参数少于65536个
区别:

https://www.jianshu.com/p/bc541afad6ee;

https://juejin.im/post/59bfe84351882531b730bac2;

bind 返回值是函数;低版本浏览器没有 bind 方法,自己实现

this 将永久地被绑定到了 bind 的第一个参数

Function.prototype.myCall = function(context) {
  if (typeof this !== 'function') { // 判断调用call的是否是函数对象
    throw new TypeError('Error')
  }
  context = context || window // 没参数时,this指向window(第一个参数为null、undefined时,也指向window)
  context.fn = this // 被调用函数 赋值给.fn 暂时
  const args = [...arguments].slice(1)
  const result = context.fn(...args) // 返回调用函数用参数执行结果
  delete context.fn
  return result
}

Function.prototype.myApply = function(context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  context = context || window
  context.fn = this
  let result
  // 处理参数和 call 有区别
  if (arguments[1]) {
    result = context.fn(...arguments[1])
  } else {
    result = context.fn()
  }
  delete context.fn
  return result
}

Function.prototype.myBind = function (context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  const _this = this
  const args = [...arguments].slice(1)
  // 返回一个函数
  return function F() {
    // 因为返回了一个函数,我们可以 new F(),所以需要判断
    if (this instanceof F) {
      return new _this(...args, ...arguments)
    }
    return _this.apply(context, args.concat(...arguments))
  }
}

/**使用例子
 */
let a = {
  name: 'ss';
  getAttr : function(n,m) {
    return this.name + n + m;
  }
}
a.getAttr(1,2);// 'ss12'
a.getAttr.call({name:'dd'},1) // 'dd1undefined'
let fna = a.getAttr.bind({name: 'ff'},1);
fna(); // 'ff1undefined'
fna(2); // 'ff12'

let b = function() {
  let attr = 'bb';
  function fn() {
    return this.attr;
  }
  return fn
}
b() // 'bb'
b.call({attr: 'dd'}) // dd

构造函数的内部执行流程,new官网文档 和 Object.create(prototype, [ownProperty]) 继承的区别

https://github.com/mqyqingfeng/Blog/issues/16
https://juejin.cn/post/6844903917835436045
https://segmentfault.com/a/1190000037740981

  • New的过程新生成了一个对象;;;返回新对象

使用es5实现class

new 关键字会进行如下的操作:

  1. 创建一个空的简单JavaScript对象(即{});let obj = {}
  2. 链接该对象(设置该对象的constructor)到原型 ;obj.__proto__ = Foo.prototype
  3. 将步骤1新创建的对象作为this的上下文 绑定 this;let result = Foo.apply(obj, arguments)
  4. 如果该函数没有返回对象,则返回this。return result instanceof Object ? result : obj

具体到当代码 new Foo(…) 执行时,会发生以下事情:

  1. 一个继承自 Foo.prototype 的新对象被创建。
  2. 使用指定的参数调用构造函数 Foo,并将 this 绑定到新创建的对象。new Foo 等同于 new Foo(),也就是没有指定参数列表,Foo 不带任何参数调用的情况。
  3. 由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤1创建的对象。(一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建步骤)

JS构造函数内的方法与构造函数prototype属性上方法的对比

  • 定义在构造函数内部的方法,会在它的每一个实例上都克隆这个方法;
  • 定义在构造函数的prototype属性上的方法会让它的所有示例都共享这个方法,但是不会在每个实例的内部重新定义这个方法
  • 当我们需要访问构造函数内部的私有变量时,就需要将某些方法定义在构造函数中
  • 定义在函数的prototype上的属性都不会被序列化JSON.stringify(a)

    函数内部可以使用new.target属性。如果当前函数是new命令调用,new.target指向当前函数,否则为undefined
    Object.create(prototype) 其中prototype中的引用类型数据key: [] 是共享的。比如xx.key.push()会让所有的prototype一起改

    instanceof 的原理

内部机制是通过判断对象的原型链中是不是能找到类型的 __proto__

es5继承的多种方式

https://segmentfault.com/a/1190000015727237
https://github.com/mqyqingfeng/Blog/issues/16

测试代码

  • 原型链继承, 问题: 1. Child 和 Parent所有实例共享Parent.prototype里的属性; 2. Child所有实例共享(new Parent).constructor里的this.属性 2. 创建 Child 的实例时,不能向Parent传参
    function Child(){}
    Child.prototype = new Parent()
    var child = new Child()
  • 借用构造函数(经典继承),问题:方法都在构造函数中定义,每次创建实例都会创建一遍方法; 只会克隆一组构造函数内部的属性做为初始值;并没有共享prototype
    function Child() { Parent.call(this) }
    var child = new Child()
  • 原型链继承和经典继承 组合继承,结论:是 JavaScript 中最常用的继承模式。缺点是会调用两次父构造函数,一次是设置子类型实例的原型的时候,一次在创建子类型实例的时候,加上最后的new其实是3次
    function Child(pKey, cKey){ Parent.call(this, pKey); this.cKey = cKey }
    Child.prototype = new Parent()
    Child.prototype.constructor = Child
    var child = new Child(pKeyValue, cKeyValue)
  • 原型式继承,问题:1. Child 和 Parent所有实例共享Parent.prototype里的属性; 2. 不会继承Parent构造函数内的this.属性
    function Child(){}
    Child.prototype = Parent.prototype
    var child = new Child()
  • 寄生式继承,使用原型式继承获得一个目标对象的浅复制,然后增强这个浅复制的能力。问题:仅提供一种思路,没什么优点。不能做到函数复用,引用类型数据依然共享
    var newObj = Object.create(obj)
    newObj.selfAttribute = xx
  • 寄生组合式继承,问题:1. 相对组合继承 只在new F的时候调用了Parent.constructor;2. 引入F的目的是隔离Child和Parent.prototype的共享
    function Child(){xxx}
    function F(){}
    F.prototype = Parent.prototype
    const cachePrototype = new F()
    cachePrototype.constructor = Child
    Child.prototype = cachePrototype
    var child = new Child()

Object.seal()Object.freeeze() 区别

https://zhuanlan.zhihu.com/p/145153864
共同点

  • 作用的对象变得不可扩展,这意味着不能再添加新属性。
  • 作用的对象中的每个元素都变得不可配置,这意味着不能删除属性。
  • 如果在 ‘use strict’ 模式下使用,这两个方法都可能抛出错误,例如在严格模式下修改 obj.a = 500。
    不同点
  • Object.seal 能让你修改属性的值,但 Object.freeze 不能.

freeze只能冻结对象第一层,如果有需要 可以自定义deepFreeze

js的面向对象

https://github.com/Kelichao/javascript.basics/issues/2

ES6 知识点

var 和 let/const

  1. var在全局作用域下声明变量会导致变量挂载在 window
  2. varfunction x(){}声明的变量和函数 会提升(hoisting)到作用域的顶部,并且函数优先于变量提升
  3. 在全局作用域下使用 letconst 声明变量,变量并不会被挂载到 window
  4. 暂时性死区:不能在变量声明前就使用变量
  5. 提升(hoist)的作用:解决函数间互相调用的情况

继承

  1. 原型继承 - 组合继承

    子类的构造函数Child()中通过 Parent.call(this) 继承父类的属性,然后改变子类的原型Child.prototype指向 new Parent() 来继承父类的函数,子类重新赋值构造函数Child.prototype.constructor = Child。(instanceof 等操作依赖这个 constructor.prototype)

    • 构造函数可以传参,不会与父类引用属性共享,可以复用父类的函数
    • 在继承父类函数的时候调用了父类构造函数,导致子类的原型上多了不需要的父类属性,存在内存上的浪费
  2. 原型继承 - 寄生组合继承

    将父类的原型赋值给了子类,并且将构造函数设置为子类Child.prototype = Object.create(Parent.prototype, { constructor: {value: Child} } )

  3. Class 继承

    使用 extends 表明继承自哪个父类,并且在子类构造函数中必须调用 supersuper(value)可以看成 Parent.call(this, value)

模块化

目的:解决命名冲突;提供复用性;提高代码可维护性

立即执行函数

(function(x){})(xx),解决了命名冲突、污染全局作用域的问题

AMD 和 CMD

CommonJS

a.js 中module.exports = {a:1};b.js 中var module1 = require(./a.js);console.log(module1.a)

简单原理解释

var module = {
  exports: {},
};

(function (module, exports) {
  exports.multiply = function (n) {
    return n * 1000;
  }; // 代表一个单模块文件内部,在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见
})(module, module.exports); // 得到对应文件的内容,加一个闭包,把内容塞进去,之后执行即可

var f = module.exports.multiply; // require
f(5); // 5000

详细版:

/*1. Module构造函数 */
function Module(p) {
  this.id = p; // 当前文件的表示(绝对路径)
  this.exports = {}; // 每个模块都有一个 exports 属性,用来存储模块的内容
  this.loaded = false; // 标记是否被加载过
}
Module.wrapper = [
  // 函数后面需要使用的闭包的字符串
  "(function (exports, require, module, __dirname, __filename) {",
  "\n})",
];
Module.wrap = function (content) {
  // 创建闭包 wrap 方法。加了一个闭包环境(即套了一层函数并传入了我们需要的参数),里面所有的变量都是私有的
  return Module.wrapper[0] + content + Module.wrapper[1];
};
Module._cacheModule = {}; // 根据绝对路径进行缓存的模块的对象
Module._extensions = {
  // 处理不同文件后缀名的方法,当require参数不带后缀名时用到
  ".js": function (module) {
    let script = fs.readFileSync(module.id, "utf8"); // 读取 js 文件,返回文件的内容
    let fn = Module.wrap(script); // 给 js 文件的内容增加一个闭包环境
    vm.runInThisContext(fn).call(
      // 创建虚拟机,将我们创建的 js 函数执行,将 this 指向模块实例的 exports 属性
      module.exports,
      module.exports,
      req,
      module
    );
    return module.exports; // 返回模块实例上的 exports 属性(即模块的内容)
  },
  ".json": function (module) {
    // .json 文件的处理相对简单,将读出的字符串转换成对象即可
    return JSON.parse(fs.readFileSync(module.id, "utf8"));
  },
};
Module._resolveFileName = function (moduleId) {}; // 将 req 方法的参数根据是否有后缀名两种方式处理成带后缀名的文件绝对路径
// module实例方法 - 加载模块
Module.prototype.load = function (filepath) {
  let ext = path.extname(filepath); // 判断加载的文件是什么后缀名
  let content = Moudule._extensions[ext](this); // 根据不同的后缀名处理文件内容,参数是当前实例
  return content; // 将处理后的结果返回
};

/*2. require的模拟 */
function req(moduleId) {
  let p = Module._resolveFileName(moduleId); // 将 req 传入的参数处理成绝对路径
  if (Module._cacheModule[p]) {
    // 判断是否已经加载过
    return Module._cacheModule[p].exprots; // 模块存在,如果有直接把 exports 对象返回即可
  }
  let module = new Module(p); // 生成一个新的模块
  let content = module.load(p); // 加载模块
  Module._cacheModule[p] = module; // 存储时是拿模块的绝对路径作为键与模块内容相对应的
  module.loaded = true; // 是否缓存表示改为 true
  module.exports = content; // 将加载后返回的内容赋值给模块实例的 exports 属性上
  return module.exports; // 最后返回 模块实例的 exports 属性,即加载模块的内容
}

ES Module

ES Module 是原生实现的模块化方案,与 CommonJS 有以下几个区别:

  • CommonJS 支持动态导入,也就是 require(${path}/xx.js),后者目前不支持,但是已有提案
  • CommonJS 是同步导入,因为用于服务端,文件都在本地,同步导入即使卡住主线程影响也不大。而后者是异步导入,因为用于浏览器,需要下载文件,如果也采用同步导入会对渲染有很大影响
  • CommonJS 在导出时都是值拷贝,就算导出的值变了,导入的值也不会改变,所以如果想更新值,必须重新导入一次或者用 get 函数代替直接拿值。但是 ES Module 采用实时绑定的值引用,导入导出的值都指向同一个内存地址,所以导入值会跟随导出值变化
  • ES Module 会编译成 require/exports 来执行的

Proxy 和其简单实现

ES6 中新增的功能,它可以用来自定义对象中的操作。

vue2 用Object.defineProperty实现数据 watch;vue3 改用proxy,无需一层层递归为每个属性添加代理,一次即可完成以上操作,性能上更好,并且原本的实现有一些数据更新不能监听到,但是 Proxy 可以完美监听到任何方式的数据改变,唯一缺陷可能就是浏览器的兼容性不好了

简单实现如下:

let onWatch = (obj, setBind, getLogger) => {
  let handler = {
    get(target, property, receiver) {
      getLogger(target, property);
      return Reflect.get(target, property, receiver);
    },
    set(target, property, value, receiver) {
      setBind(value, property);
      return Reflect.set(target, property, value);
    },
  };
  return new Proxy(obj, handler);
};

let obj = { a: 1 };
let p = onWatch(
  obj,
  (v, property) => {
    console.log(`监听到属性${property}改变为${v}`);
  },
  (target, property) => {
    console.log(`'${property}' = ${target[property]}`);
  }
);
p.a = 2; // 监听到属性a改变
p.a; // 'a' = 2

Array 的 map,filter 和 reduce

reduce 可以将数组中的元素通过回调函数最终转换为一个值:const sum = [1,2,3].reduce((acc, currentItem, currentIndex, arr) => acc + current, 0)

Generator 迭代器和 yield

一个用途在于 解决 回调地狱

ajax(url, () => {
  // 处理逻辑
  ajax(url1, () => {
    // 处理逻辑
    ajax(url2, () => {
      // 处理逻辑
    });
  });
});
/* 改用generator实现 */
function* fetch() {
  yield ajax(url, () => {});
  yield ajax(url1, () => {});
  yield ajax(url2, () => {});
}
let it = fetch();
let result1 = it.next();
let result2 = it.next();
let result3 = it.next();

Promise

三种状态,从pendingresolvereject后不再改变。

Promise 实现了链式调用,也就是说每次调用 then 之后返回的都是一个 Promise,并且是一个全新的 Promise,原因也是因为状态不可变。如果你在 then 中 使用了 return,那么 return 的值会被 Promise.resolve() 包装;

也可以用来解决 回调地狱问题

promise 优缺点;多个同时怎么使用

手写 Promise

todo

async 和 await

一个函数如果加上 async ,那么该函数就会返回一个 Promiseasync 就是将函数返回值使用 Promise.resolve() 包裹了下,和 then 中处理返回值一样;

await内部实现了 generator,其实 await 就是 generator 加上 Promise 的语法糖,且内部实现了自动执行 generator, 只能配套 async 使用。await可以等价为Promise.resolve()

const gen = function* () {
  const f1 = yield readFile("/etc/fstab");
  const f2 = yield readFile("/etc/shells");
  console.log(f1.toString());
  console.log(f2.toString());
};
// async await实现
const asyncReadFile = async function () {
  const f1 = await readFile("/etc/fstab");
  const f2 = await readFile("/etc/shells");
  console.log(f1.toString());
  console.log(f2.toString());
};

Event Loop 事件循环

一个详解教程

js 单线程,一般的服务器语言都是线程模型,nodejs 的非阻塞事件循环是独有的机制。

线程是进程中的更小单位,描述了执行一段指令所需的时间。一个进程中可以有多个线程,比如渲染线程、JS 引擎线程、HTTP 请求线程等等

JS 引擎线程和渲染线程,在 JS 运行的时候可能会阻止 UI 渲染,这说明了两个线程是互斥的,因为 JS 可以修改 DOM,如果在 JS 执行的时候 UI 线程还在工作,就可能导致不能安全的渲染 UI。得益于 JS 是单线程运行的,可以达到节省内存,节约上下文切换时间,没有锁的问题的好处。

执行栈是一个存储函数调用的栈结构,遵循先进后出的原则,开始执行 JS 代码时,首先会执行一个 main 函数,然后执行我们的代码。

动图对比浏览器端和 node 端处理区别:microtask 的执行时机不同

浏览器 javascript 的环境

主线程执行,当遇到异步的代码时,会被挂起放到堆栈队列里并在需要执行的时候加入到 Task(有多种 Task) 队列中。一旦执行栈为空,Event Loop 就会从 Task 队列中拿出需要执行的代码并放入执行栈中执行,所以本质上来说 JS 中的异步还是同步行为。

main函数=>同步代码(属于宏任务)=>有异步 task 的话执行 MicroTask 所有=>渲染页面开始下一轮 Loop=>MacroTask 第二个里面的异步 jobs…

微任务包括 process.nextTickpromiseMutationObserver,其中 process.nextTick 为 Node 独有。

宏任务包括 scriptsetTimeoutsetIntervalsetImmediateI/OUI rendering

Node 环境下

Node 的环境

教程(https://juejin.im/post/5dd24ecce51d453fb903ff37)

6 个阶段,它们会按照顺序反复运行。每当进入某一个阶段的时候,都会从对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量到达系统设定的阈值,就会进入下一阶段

   ┌───────────────────────────┐
┌─>│      timers定时器          │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘      ┌───────────────┐
│  ┌─────────────┴─────────────┐      │   incoming:   │
│  │           poll轮询         │<─────┤  connections, │
│  └─────────────┬─────────────┘      │   data, etc.  │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │        check检测           │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │
   └───────────────────────────┘
  1. timer:执行定时器队列中的回调,比如 setTimeoutsetInterval 回调,并且是由 poll 阶段控制的

  2. I/O callbacks:处理一些上一轮循环中的少数未执行(即除了 setTimeout…4 个回调)的 I/O ,某些系统操作的回调,例如 TCP 错误。

  3. idle, prepare:仅系统内部使用;闲置阶段,等待新的 I/O 事件

  4. poll:回到 timer 阶段检索新的 I/O 事件比如用户输入数据,操作读取文件;执行 I/O 回调(除了关闭回调,计时器调度的回调和 setImmediate 之外,几乎所有这些回调) 适当时,node 将在此处阻塞

    1. 当 event loop 进入 poll 阶段,并且 有设定的 timers 队列:

      一旦 poll 队列为空(或者说在文件读取完成 将回调加入 poll 队列 之前)

      event loop 将监听 timers,如果有 1 个或多个 timers 的下限时间已经到达,event loop 将绕回 timers 阶段,并执行 timer 队列

    2. 当 event loop 进入 poll 阶段,并且 没有设定的 timers:

      1. 如果 poll 队列不空:event loop 会遍历队列并同步执行回调,直到队列清空(或执行的回调数到达系统上限);【即使在这期间 timers 队列有新的 timer 进入,也会放在下一轮 loop 执行】
      2. 如果 poll 队列为空
        1. 如果代码已经被 setImmediate()设定了回调, event loop 将结束 poll 阶段进入 check 阶段来执行 check 队列(里面的回调 callback)
        2. 如果代码没有被 setImmediate()设定回调,event loop 将阻塞在该阶段等待回调被加入 poll 队列,并立即执行
  5. check:执行 setImmediate

  6. close callbacks:执行 close 事件。一些关闭回调,例如 socket.on('close', ...)

大致顺序就是:定时器回调上一轮的,执行 I/O 上一轮未执行的那些,外部输入,轮询,检测,关闭回调

特别的:process.nextTick 不属于事件循环的任何一个阶段,它属于该阶段与下阶段之间的过渡, 即本阶段执行结束, 进入下一个阶段前, 所要执行的回调。在各个事件阶段之间执行,一旦执行,要直到 nextTick 队列被清空,才会进入到下一个事件阶段。有给人一种插队的感觉。递归的调用process.nextTick()会导致 I/O starving,官方推荐使用setImmediate()

JS 垃圾回收机制-可触及

https://segmentfault.com/a/1190000015641168

JavaScript 内存管理的关键概念是可触及,即能被root导出的树访问的,可引用的root指:

  • 当前函数的局部变量和参数。
  • 当前调用链(current chain of nested calls)中所有函数的局部变量和参数。
  • 全局变量。
  • (以及其他内部变量)

内部 标记-清除算法:

  • 垃圾回收器获取并标记 root
  • 然后访问并标记来自他们的所有引用
  • 访问被标记的对象,标记他们的引用。所有被访问过的对象都会被记录,以后将不会重复访问同一对象。
  • ……直到只剩下未访问的引用
  • 所有未被标记的对象都会被移除

引擎垃圾回收效率优化:

  • 分代收集

    V8 垃圾回收机制,将内存(堆)分为新生代老生代两部分,很多对象完成任务后很快就不再需要了,所以对于他们的清理可以很频繁。而在清理中留下的称为“老生代”一员。

    新生代:内存空间分为两部分,分别为 From 空间和 To 空间。新分配的对象会被放入 From 空间中,当 From 空间被占满时,新生代 GC 就会启动了。算法会检查 From 空间中存活的对象并复制到 To 空间中,如果有失活的对象就会销毁。当复制完成后将 From 空间和 To 空间互换。

  • 增量收集

    尝试把标记完所有对象这个操作分割成多份,每次执行一份。这样做要记录额外的数据,但是可以有效降低延迟对用户体验的影响。

  • 闲时收集

    垃圾回收器尽量只在 CPU 空闲时运行,减少对程序执行的影响

浏览器知识点

协议

UDP 面向无连接 传输层通信协议

面向无连接,不保证有序且不丢失的传递到对端,没有任何控制流量的算法

TCP 面向连接 传输层通信协议

建立连接断开连接都需要先需要进行握手,算法保证数据的可靠性

TCP Header 头部字段几个重要的点

建立连接三次握手:1. 客户端向服务端发送连接请求报文段 2. 服务端收到连接请求报文段后,发送应答 3. 客户端收到应答,向服务端发送确认报文,双方进入ESTABLISHED状态

断开连接四次握手:TCP 是全双工的,在断开连接时两端都需要发送 FIN 和 ACK。 1. 客户端 向服务端发送发送连接释放请求 2. 服务端告诉应用层释放 TCP 请求,发送 ACK 包, 此时服务端 仍旧可以发送数据给 客户端。 3. 服务端传送完毕向客户端发送释放请求 4. 客户端收到释放请求后,向 服务端发送确认应答。经过短暂TIME-WAIT状态后 进入CLOSED状态

HTTP 传输 应用层通信协议

  • 请求行:如GET /url/xx.png HTTP/1.1

    Get 多用于无副作用,幂等的场景,例如搜索关键字;请求能缓存;Get 请求都包含在 URL 里不安全;URL 有长度限制,会影响 Get 请求

    Post 多用于副作用,不幂等的场景,例如注册;请求不能缓存;支持更多的编码类型且不对数据类型限制

  • 首部:

    • 通用首部Cache-Control, Connection, Transfer-encoding等
    • 请求首部Host, Accept, User-Agent, TE传输编码方式等
    • 响应首部Age, ETag, Location, WWW-Authenticate等
    • 实体首部Allow, Content-Length, Content-Type, Expires, Last_modified等
  • 实体body

常见状态码:2xx成功3XX 重定向4XX 客户端错误5XX 服务器错误

TLS 加密 安全传输层协议

HTTPS 还是通过了 HTTP 来传输信息,但是信息通过 TLS 协议进行了加密

TLS 协议位于传输层之上,应用层之下

  • 对称加密:两边拥有相同的秘钥,两边都知道如何将密文加密解密
  • 非对称加密:有公钥私钥之分,公钥所有人都可以知道,可以将数据用公钥加密,但是将数据解密必须使用私钥解密,私钥只有分发公钥的一方才知道

HTTP/2 和 HTTP/3

  • HTTP/2 采用二进制格式编码:中引入了新的编码机制,所有传输的数据都会被分割
  • HTTP/2 多路复用:有两个非常重要的概念,分别是帧(frame)和流(stream)。就是在一个 TCP 连接中可以存在多条流,可以发送多个请求
  • HTTP /2 中,使用了 HPACK 压缩格式对传输的 header 进行编码,减少了 header 的大小

输入 URL 到页面渲染的整个流程

  1. DNS 查询,通过域名查询到具体的 IP
  2. TCP 握手,应用层会下发数据给传输层,这里 TCP 协议会指明两端的端口号,然后下发给网络层。网络层中的 IP 协议会确定 IP 地址,并且指示了数据传输中如何跳转路由器。然后包会再被封装到数据链路层的数据帧结构中,最后就是物理层的传输了
  3. TCP 握手结束后就会进行 TLS 握手,两种加密方式
  4. 服务端响应一个 HTML 文件
  5. 浏览器会判断状态码
  6. 浏览器开始解析文件成 AST
  7. 渲染流程,先会根据 HTML 构建 DOM 树,有 CSS 的话会去构建 CSSOM 树。如果遇到 script 标签的话,会判断是否存在 async 或者 defer
  8. CSSOM 树和 DOM 树构建完成后会组合生成 Render 树,这一步就是确定页面元素的布局、样式等等诸多方面的东西
  9. 浏览器调用 GPU 绘制,合成图层,将内容显示在屏幕上

跨域

因为浏览器出于安全考虑,有同源策略。也就是说,如果协议、域名或者端口有一个不同就是跨域,Ajax 请求会失败。主要是用来防止 CSRF 攻击的。简单点说,CSRF 攻击是利用用户的登录态发起恶意请求。跨域并不能完全阻止 CSRF,因为请求毕竟是发出去了,浏览器拦截了响应,表单可以发起跨域请求是因为表单不会获取 responese。

解决跨越问题的几种方法:

  • JSONP

    JSONP 的原理很简单,就是利用 标签没有跨域限制的漏洞。通过 标签指向一个需要访问的地址并提供一个回调函数来接收数据当需要通讯时。只限于 get 请求

  • CORS

    CORS 需要浏览器和后端同时支持。IE 8 和 9 需要通过 XDomainRequest 来实现。浏览器会自动进行 CORS 通信,实现 CORS 通信的关键是后端。只要后端实现了 CORS,就实现了跨域。

    服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。

    app.use((req, res, next) => {
      res.header("Access-Control-Allow-Origin", "*");
      res.header(
        "Access-Control-Allow-Methods",
        "PUT, GET, POST, DELETE, OPTIONS"
      );
      res.header(
        "Access-Control-Allow-Headers",
        "Origin, X-Requested-With, Content-Type, Accept, Authorization, Access-Control-Allow-Credentials"
      );
      next();
      res.statusCode = 204;
      res.setHeader("Content-Length", "0");
      res.end();
    });
    
  • document.domain

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

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

  • postMessage

    这种方式通常用于获取嵌入页面中的第三方页面数据。一个页面发送消息,另一个页面判断来源并接收消息

存储

  • cookie:不建议用于存储,一般由服务器生成,可以设置过期时间;每次都会携带在 header 中,对于请求性能影响

  • localStorage

  • sessionStorage

  • indexDB

  • Service Worker 缓存

    是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker 的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。

    3 个步骤:

    1. 注册 Service Worker:在 index.js 中
    2. 监听到 install 事件,缓存需要的文件
    3. 拦截所有请求事件fetch
    if ("serviceWorker" in navigator) {
      // 判断 Service Worker API 的可用情况,支持的话咱们才继续谈实现
      window.addEventListener("load", function () {
        navigator.serviceWorker
          .register("/sw.js", { scope: "/" }) //scope作用域,如果不指定,默认是sw.js所在文件夹(scope取值必须在sw所在文件夹下的深层路径)
          .then(function (registration) {
            console.log("注册成功 with scope: ", registration.scope);
          })
          .catch(function (err) {
            console.log("注册成功: ", err);
          });
      });
    }
    
/* sw.js */
// 监听 `install` 事件,回调中缓存所需文件
self.addEventListener("install", (e) => {
  e.waitUntil(
    // 监听到了 service worker 已经安装成功的话,就会调用 event.waitUntil 回调函数
    caches.open("my-cache").then(function (cache) {
      // 操作 CacheStorage 缓存,使用之前需要先通过 caches.open() 打开对应缓存空间
      return cache.addAll(["./index.html", "./index.js"]);
    })
  );
});
// 拦截所有请求事件, 每次任何被 Service Worker 控制的资源被请求到时,都会触发 fetch 事件
self.addEventListener("fetch", (e) => {
  e.respondWith(
    caches.match(e.request).then(function (response) {
      if (response) {
        return response; // 如果缓存中已经有请求的数据就直接用缓存
      }
      // 否则去请求数据
      console.log("fetch source");
    })
  );
});

浏览器缓存机制

详解

  • 缓存位置:

    1. Service Worker:缓存是持续性的
    2. Memory Cache:内存中的缓存,读取高效,可是缓存持续性很短,会随着进程的释放而释放
    3. Disk Cache:硬盘中的缓存,容量大存储时效性长
    4. Push Cache:HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用,时效性短随会话Session释放
    5. 网络请求
  • 缓存策略:

    • 强缓存:设置新鲜度限值,在缓存期间不需要请求,state code状态码 为 200

      1. Cache-Control 相对时间 res.setHeader('Cache-Control', "max-age=" + 5);//缓存五秒

        cache-control 可选值:public, private, max-age, s-maxage, no-store, no-cache, max-stale, min-fresh

      2. Expires 客户端确定时间点,优先级没 cache-control 高会被其覆盖

    • 协商缓存 浏览器发起请求验证资源是否有更新,如果资源没有做改变,那么服务端就会返回 state code状态码为 304,并且更新浏览器缓存有效期。

      1. ETag文件指纹 hash 值:随文件修改变化
      2. Last-Modified本地文件最后修改日期
  • 实际场景应用缓存策略

    一般,现在都用打包工具,对文件名进行哈希处理,只有当代码修改后才会生成新的文件名。

    基于此,可以设置缓存有效期一年 Cache-Control: max-age=31536000,这样只有当 HTML 文件中引入的文件名发生了改变才会去下载最新的代码文件,否则就一直使用缓存。

浏览器渲染原理

  • 过程
    1. DOM 树:字节数据 -> 字符串 -> Token 标记 ->Node ->DOM 树
    2. CSSOM 树:字节数据 -> 字符串 -> Token 标记 ->Node ->CSSOM 树
    3. 由 前两个 组合成 render 渲染树
  • 优化
    1. DOM 操作慢的原因:因为 DOM 渲染引擎和 js 引擎,是两个线程通信,并且还可能产生回流重绘
    2. 优化阻塞渲染的情况:
      • 降低渲染文件大小 DOM 树层级;
      • css 减少子/后代选择器加快递归 CSSOM 树过程;
      • script 标签放最后或deferasync
      • 首屏不放太多src属性的元素

Reflow 和 Repaint

改变元素布局或几何属性宽高等会导致回流,只更改外观如颜色背景就只重绘。回流必定会发生重绘,重绘不一定会引发回流。

回流发生在Event LoopmicroTask后:检查document是否更新,判断resize``scroll事件,判断media query,更新动画,判断全屏操作事件,执requestAnimationFrame回调,更新界面

减少回流方法

  1. transfrom代替top,left
  2. visitility代替display:none
  3. 不使用table
  4. 将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点,比如video

安全

XSS

攻击者 将可执行的代码注入到网页

  • 持久型:攻击的代码被服务端写入进数据库。比如评论功能输入<script>alert(1)</script>
  • 非持久型:一般通过修改 URL 参数www.xx.com?name=<script>alert</script>,并且页面会拿参数加载 的方式加入攻击代码,诱导用户访问链接从而进行攻击

防御方法:

  1. 转义字符:把<和"和/ 转义掉str = str.replace(/&/g, '&amp;')
  2. 白名单:使用js-xss库来实现只过滤 script:const xssFn = require('xss');xssFn('stringxx')
CSP

本质上就是建立白名单。配置规则告知浏览器哪些外部资源可以加载和执行,开启方法二选一:

  1. node 端:res.setHeader('Content-Security-Policy','default-src ‘self’')
  2. 前端:<meta http-equiv="Content-Security-Policy">

CSRF 跨站请求伪造

攻击者构造出一个后端请求地址,诱导用户点击或者通过某些途径自动发起请求。防御规则:

  1. Get 请求不对数据进行修改
  2. 不让第三方网站访问到用户 Cookie:对 Cookie 设置 SameSite 属性。该属性表示 Cookie 不随着跨域请求发送
  3. 阻止第三方网站请求接口:验证 Referer 来判断该请求是否为第三方网站发起的
  4. 请求时附带验证信息,比如验证码或者 Token:服务器下发一个随机 Token,每次发起请求时将 Token 携带上,服务器验证 Token 是否有效

点击劫持

攻击者将需要攻击的网站通过 iframe 嵌套的方式嵌入自己的网页中,并将 iframe 设置为透明,在页面中透出一个按钮诱导用户点击。防御方法 2 种:

  1. node 端:res.setHeader('X-FRAME-OPTIONS','DENY/SAMEORIGIN/ALLOW-FROM三选一')
  2. js 手写功能:当 html 文件通过 iframe 的方式被加载时,html{display:none}

中间人攻击

攻击方同时与服务端和客户端建立起了连接,并让对方认为连接是安全的,但是实际上整个通信过程都被攻击者控制了。攻击者不仅能获得双方的通信信息,还能修改通信信息。比如公共的 Wi-Fi。开启HTTPS并且不允许加载 http 资源,就可以防御

性能优化

见另一个post 前端性能优化

打包工具

Webpack 性能优化 todo

减少打包时间

  1. 优化 loader 的文件搜索范围:对 loader 来说,影响最大的是 Babel 会将代码转为字符串生成 AST,然后对 AST 继续进行转变最后再生成新的代码。rules: [{...include: [resolve('src')], exclude: /node_modules/}];将 babel 编译过的文件缓存起来loader: 'babel-loader?cacheDirectory=true'
  2. HappyPack 该插件可以将 Loader 的同步执行转换为并行:本身 node 是单线程的。rules: [{...loader: 'happypack/loader?id=happybabel'}] 并且添加插件plugins: [new HappyPack({id: 'happybabel',loaders: ['babel-loader?cacheDirectory'],threads: 4})]
  3. DllPlugin 插件该可以将特定的类库提前打包然后引入,减少打包类库的次数,只有当类库更新版本才有需要重新打包,并且也实现了将公共代码抽离成单独文件的优化方案。入口:entry: {vendor: ['react']};插件:new webpack.DllPlugin({})new webpack.DllReferencePlugin({})
  4. 代码压缩:webpack3 的话:UglifyJS 是单线程的压缩,webpack-parallel-uglify-plugin 来并行运行 UglifyJs;webpack4 的话:mode设置为production
  5. resolve.extensions:自定义文件后缀列表,频率高的后缀排在前面
  6. resolve.alias:别名,webpack 更快找到路径
  7. module.noParse:如果一个文件没有别的依赖,让 webpack 不扫描

减小打包体积

  1. 按需加载:每个路由页面单独打包为一个文件。实现原理是当使用的时候再去下载对应文件,返回一个 Promise,当 Promise 成功以后去执行回调
  2. Scope Hoisting:Webpack4 中:optimization: { concatenateModules: true }
  3. Tree Shaking:打包时删除项目中未被引用的代码,webpack4 中modeproduction自动实现

实现小型打包工具 重点 todo

  1. 将 ES6 转换为 ES5
    • 安装 babel 相关:yarn add babylon babel-traverse babel-core babel-preset-env
  2. 支持在 JS 文件中 import CSS 文件
  3. 找出入口文件所有的依赖关系
  4. 然后通过构建 CommonJS 代码来获取 exports 导出的内容

React/Vue

  • MVVM

    Vue 框架来举例,ViewModel 就是组件的实例。View 就是模板,Model 的话在引入 Vuex 的情况下是完全可以和组件分离的;隐式的 Binder 层(实现 view 和 viewmodal 双向映射)就是 Vue 通过解析模板中的插值指令从而实现 View 与 ViewModel 的绑定

  • Virtual DOM

    相较于 DOM 操作 来说,操作 JS 对象会快很多,并且我们也可以通过 JS 来模拟 DOM。实现组件的高度抽象化;作为一个兼容层实现跨端开发

    diff 算法找 DOM 多叉树最小差异,局部更新:(为什么不推荐 key 是 index 数组)

    1. 首先从上至下,从左往右遍历对象,也就是树的深度遍历,这一步中会给每个节点添加索引 key,便于最后渲染差异。判断新旧节点的 tagName 是否相同,不相同的话就代表节点被替换了,否则判断是否子元素,深度递归
    2. 一旦节点有子元素,就去判断子元素是否有不同:根据key识别判断 原本的列表中是否有节点被移除,在新的列表中需要判断是否有新的节点加入,还需要判断节点是否有移动,节点的属性是否有变化
  • 前端路由机制:

    • hash 模式:通过监听事件window.addEventListener('hashchange', () => {})
    • History 模式:H5 的history.pushState(stateObject, title, URL)history.replaceState(stateObject, title, URL) 以及 监听事件比如后退事件window.addEventListener('popstate', e => {});会发起 URL 请求,后端需要配置 index.html 页面用于匹配不到静态资源的时候
  • React/Vue 区别

    v-model语法糖;

    改变数据方式上:React 用setState,Vue 的底层使用了依赖追踪,页面更新渲染最优,但是 React 还是需要用户手动去优化

    JSX 比模版语法 灵活

Vue 知识点

  • 生命周期钩子

  • 组件通信:父子父传子props子传父emit 或 $parent $children、兄弟this.$parent.$children、跨多层父子provide inject、任意组件Event Bus自己new Vue()的data上

  • extend是什么:扩展/继承组件生成一个构造器,通常会与 $mount 一起使用

  • mixinmixinsmixin全局混入,会影响到每个组件实例,用来实现全局混入封装好的 ajax 或者一些工具函数; mixins 混入,如果多个组件中有相同的业务逻辑,就可以将这些逻辑剥离出来,,比如上拉下拉加载数据这种逻辑等等

  • computedwatch

  • keep-alive组件:在组件切换的时候,保存一些组件的状态防止多次渲染;两个独有的生命周期钩子函数activateddeactivated。该组件切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数

  • v-ifv-show

  • data函数还是对象:组件复用时所有组件实例都会共享 data;函数的话就是每次获取时执行函数

  • 响应式原理:Object.defineProperty()Proxy

    Dep构造函数内部subs数组属性依赖收集方法addSub有更新的时候派发更新方法notify

    defineProperty缺陷:通过下标方式修改数组数据或者给对象新增属性并不会触发组件的重新渲染

  • template编译成render函数过程

    编译器将模版编译成 render函数 ;执行render函数生成virtual DOM映射真实 DOM

    编译过程:

    1. 将模板解析为 AST:正则表达式去匹配模板中的内容,并且对比前后开闭标签是否一致,判断根组件是否只存在一个等等
    2. 优化 AST:对节点进行了静态内容提取,将永远不会变动的节点提取出来,实现复用 Virtual DOM,跳过对比算法
    3. 将 AST 转换为 render 函数:遍历整个 AST,根据不同的条件生成不同的代码
  • nextTick原理

    下次 DOM 更新循环结束之后执行延迟回调,用于获得更新后的 DOM。

    原理 默认使用 microtasks,但在特殊情况下会使用 macrotasks(setImmediate->MessageChannel->setTimeout),比如 v-on。

React 知识点

https://hulufei.gitbooks.io/react-tutorial/content/introduction.html

  • 生命周期
  • setState
  • 性能优化
  • 组件通信:父子、兄弟、跨多层父子、任意组件
  • HOC 是什么?相比 mixins 有什么优点
  • 事件机制
  • Hook 该怎么用

设计模式

数据结构和算法

数据结构 todo

  • 队列
  • 链表

常见算法 todo

  • 排序
    1. 冒泡排序
    2. 插入排序
    3. 选择排序
    4. 归并排序
    5. 快排
    6. 堆排序
    7. 系统自带排序
  • 链表
    1. 反转单项链表
    1. 二叉树的先序,中序,后序遍历
    2. 中序遍历的前驱后继节点
  • 动态规划
    1. 斐波那契数列
    2. 0 - 1 背包问题
    3. 最长递增子序列

前端监控

Date.UTC(year,month[,date[,hrs[,min[,sec[,ms]]]]]) 接受参数是 以换算成UTC的时间为准,比如上海本地时间14点,需要输入GMC timezone的6点
new Date(string) 当里面参数是string格式时,有两种逻辑。官网解释

  • new Date('2021-4-1T13:33') 本地时间 13:33
  • new Date('2021-4-1T13:33Z') 以UTC时间13点定义,对应的本地(上海)时间 21:33

  1. 打开console,统计当前页中 出现的标签一共多少种,出现最多的是什么tag
    ```js
    // 统计
    new Set(
    […document.getElementsByTagName(‘*’)].map(v=>v.tagName)
    ).size

// 出现最多的是什么tag
let c = {};
[…document.getElementsByTagName(‘*’)].map(v=>v.tagName).forEach(tagName=>{
if(tagName in c) c[tagName]++;
else c[tagName] = 1
})
// 最多的tag出现的次数
Math.max(…Object.values(c))

// 最多的tag key 和 其数量
const arr = Object.entries(c)
return arr.reduce((r,c,i)=>{
// c format: [tagName, tagCount]
if(c[1]>r[1]) return c
else return r
}, arr[0])

2. 项目有什么亮点,企业级的项目该有的特点
  比如: 用文件上传举个例子
- 最简单的 axios.post
- 体验优化:粘贴,拖拽,进度条
- 断点续传 文件切片 类似TCP一旦丢包了一部分就补发这部分就好,秒传;文件类型判断(准确的按 二进制 的 头信息来判断其特定的文件格式)
- webworker,时间切片 time splice;首 中 尾各区两个字节抽样hash md5(request idle callback)布隆过滤器算法(虽然存在误判...)
- 异步任务并发数 axios限制6个同时 所以如果100个切片会卡,对应字节的一个面试题,异步任务怎么控制并发数;切片报错重试
- TCP的慢启动控制,解决包的大小匹配网速 快增慢减;碎片清理,其他优化

比如 框架源码层面,vue里加了Plugin,
loader,
工程化CI/CD,
try.catch在webpack 声明周期内做动作

其中一定要加了**算法,数据结构,设计模式等元素**


-------



# h5:

1.canvas元素的大小与绘图区域的大小怎么修改?

2.span和p标签有什么区别

3.输入框的默认语:palceholder = “默认语句”

4.script defer 和sync 区别

![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b0a8a139519f46dfa2d1992c58eb5397~tplv-k3u1fbpfcp-zoom-1.image)

其中蓝色代表js脚本网络加载时间,红色代表js脚本执行时间,绿色代表html解析。

\5. src和href的区别

src和href都是**用来引用外部的资源**,它们的区别如下:

- **src:** 表示对资源的引用,它指向的内容会嵌入到当前标签所在的位置。src会将其指向的资源下载并应⽤到⽂档内,如请求js脚本。当浏览器解析到该元素时,会暂停其他资源的下载和处理,直到将该资源加载、编译、执⾏完毕,所以⼀般js脚本会放在页面底部。
- **href:** 表示超文本引用,它指向一些网络资源,建立和当前元素或本文档的链接关系。当浏览器识别到它他指向的⽂件时,就会并⾏下载资源,不会停⽌对当前⽂档的处理。 常用在a、link等标签上。

# css:

1.position的值, fixed,relative和absolute分别是相对于谁进行定位的?

2.display:none和visibility:hidden的区别

3.小于12px字怎么实现?

4.不同大小文字如何基于底部对齐?

5.两种盒模型的区别?

6.em代表什么?rem代表什么?rem和px如何换算?

7.less scoped原理 scopd 怎么去修改样式?

8.translate和绝对定位有什么区别?优势?

\9. 对BFC的理解,如何创建BFC





# js:

1.函数防抖是什么?怎么实现

2.函数里的this什么含义,作用域怎么理解,箭头函数对作用域有什么影响?改变this三种方法call、apply、bind三种区别?

3.事件委托和代理?

4.如何获取链接中的参数值?

5.7大基础类型有哪些是引用类型?怎么拷贝一个对象,属性修改互不影响?

\6. 什么是跨域?如何解决跨域?

7.作用域链,结果输出30

```js
var a = 10
function fn() {
var b = 20
function bar() {
  console.log(a + b)
}
return bar
}
var x = fn(),
b = 200
x()

8.说出下面log出现顺序并解释原因:

console.log('1');
setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})
 
setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})

vue:

1.vue生命周期有哪些

2.router的histroy模式和hash模式对应的链接有什么区别?

3.vue父子组件怎么传递数据?

4.Vue双向绑定对于数组有什么不一样

5.v-model和.sync的区别

6.函数式组件是什么,函数式组件应用场景

7.Vue3.0中,ref,reactive,toRef和toRefs的区别

\8. vue中key的作用

\9. $nextTick有什么用?什么场景下使用?

\10. v-if 和 v-show 的区别

\11. v-for和v-if的优先级谁高?一起使用怎么优化?

\12. 组件中的data为什么是函数?为什么跟组件的data可以不用函数?

\13. 为什么要使用异步组件?

\14. vue项目怎么做项目优化?

15.keep-alive的作用是什么?
答:keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。

\16. 怎么实现组件异步加载?

\17. 什么是动态路由?动态路由怎么获取参数?

React:

1.render函数的作用?如何触发Render函数?

2.React声明组件有哪几种方法,有什么不同?

3.react组件的生命周期及对应的钩子函数是哪些?16之后废弃了哪些?新增的有什么用?想要在一个新加载的元素上绑定事件应该在哪个生命周期操作?

4.组件的状态(state)和属性(props)有什么区别?

5.redux是什么?

6.react-hooks是什么?

7.如何使用react-router进行参数传递。

8.了解过Fiber吗?对Fiber的理解,解决了什么问题?

9.pureComponent、React.memo有什么用?

10.哪些方法会触发 React 重新渲染?重新渲染 render 会做些什么?

11.React中refs的作用是什么?有哪些应用场景?React.forwardRef是什么?它有什么作用?

12.React中什么是受控组件和非控组件?

13.React的状态提升是什么?使用场景有哪些?

14.父子组件的通信方式?跨级组件的通信方式?非嵌套关系组件的通信方式?如何解决 传递props 层级过深的问题?

15.React setState 调用之后发生了什么?是同步还是异步?

16.React 中的 key的作用 引出 ==> React Diff算法的原理是什么?

17.React-router的路由有几种模式?hash、history分别是通过什么实现的。

工具链:

1.git基本命令,创建一个新文件推送到远程代码库流程&&git stash怎么用&&什么是快速向前合并?

2.webpack为什么需要loader?

网络协议:

1.常用状态码有哪些

2.https和http有什么区别

3.HTTP 1.0、1.1、2.0分别有些啥区别

4.HTTP请求、返回报文的结构分别是什么样的

5.https的TLS/SSL的工作原理是什么

6.浏览器输入域名之后按下回车后发生了什么

浏览器:

1.http缓存机制,强缓存和协商缓存原理

2.浏览器本地存储的方式有哪些?有什么区别?使用场景是什么?

3.如何获取浏览器的url,window的location对象有哪些子元素?分别有什么作用?如何设置不能回退的跳转?如何设置改变url但不刷新页面?

4.什么是XSS、CSRF?如何防范

5.浏览器的渲染过程以及如何优化

6.同源策略、跨域怎么解决、Nginx相关?

算法:

1.两数之和:https://leetcode-cn.com/problems/two-sum/

2.有效的括号:https://leetcode-cn.com/problems/valid-parentheses/

项目综合能力:

1.在项目中怎么定位问题?

2.移动端性能优化?

3.是否做过移动端适配?简述做过的一个适配

4.根据简历问一个?

宿主环境

浏览器、node

1.主流浏览器内核有哪些?

2.webkit内核浏览器渲染html页面的过程?

3.浏览器的event-loop是怎么回事?

3.页面卡顿可能的原因有哪些,怎么分析?

4.node作为宿主环境和浏览器有何区别?

全局环境this指向

node环境没有dom、没有window

i/o操作:如fs文件系统模块、http模块、Buffer处理二进制缓存区、stream流处理等等

事件循环有区别:事件循环中没有渲染任务、对于宏任务在node11(好像是)以前是宏任务队列清空之后再清空微任务队列,新版的nodejs事件循环和浏览器保持一致

模块加载不同

语言

ES6,TS,sass,less

作用域和执行上下文是什么?

原型、原型链与原型链继承

框架

vue、react、nuxt

手写简版双向绑定(编码)

手写event-bus(编码)

vue的nextTick是怎么回事?

vue的数据驱动是怎么实现的?

了解vue3.0吗?vue2存在哪些缺点?

vue框架在ssr场景怎么用?

对 React Hook 的理解,它的实现原理是什么?

React Hooks 解决了哪些问题?

useEffect 与 useLayoutEffect 的区别?

React key 是干嘛用的 为什么要加?key 主要是解决哪一类问题的? React diff 算法的原理是什么?

工具链

webpack、rollup、fis3、vue-cli

webpack基本原理是什么?是怎么解决模块依赖的?

webpack产出的bundle文件是如何执行的?

都用过哪些webpack优化插件?

vue-cli脚手架有哪些优缺点?

组件化

组件通信方式有哪些?

建立组件库有哪些重点工作?

组件如何主题定制?

可视化

网络

大文件(视频、音频)如何传输?

https的原理

有哪些常见web安全漏洞

浏览器发出请求前有哪些状态?

跨域如何处理?

浏览器常见缓存有哪些,区别?

TCP 三次握手和四次挥手的过程?

如何控制http请求的并发数?

什么是cdn,有什么作用?

质量管控

JS异常有哪些,如何捕获异常?

网络不好如何处理?

首屏加速有哪些方法?

如何衡量页面体验的好坏?


   转载规则


《前端核心基础1》 Ryan Who 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
GIS-几个地图可视化工具-对比试用 GIS-几个地图可视化工具-对比试用
我们常常听说的数据可视化大多指狭义的数据可视化以及部分信息可视化。根据数据类型和性质的差异,经常分为以下几种类型: 统计数据可视化:用于对统计数据进行展示、分析,一般都是以数据库表的形式提供,常见的有 HighCharts、ECharts
2020-06-28
下一篇 
英语工具链 英语工具链
欧陆词典多终端同步生词本,是个很好的笔记工具,搭配Mac下的Better Touch Tool代替系统三指查询,体验很畅快。同时有复习背记生词本功能。 扇贝单词专业的背单词工具,丰富的单词书,如大学英语六级词汇书、托福词汇书 etc. 哈哈
2020-05-20
  目录