总结

一. Vue

1. v-show和v-if的区别

v-if是真正的条件渲染,子组件会适当的被销毁和重建;
v-show不管初始条件是什么,元素总是会被渲染,他利用CSS的display属性进行切换。
因此v-if适用于运行时很少改变条件,不需要频繁切换条件的场景;v-show适用于需要频繁切换条件的场景。

2.Vue的单向数据流

所有的prop使父子prop之间形成了一个单向下行绑定,父级prop的更新会向下流动到子组件中,但是反过来不行。如果在子组件中更新prop的值,vue会在浏览器控制台中发出警告。子组件可以通过$emit自定义一个事件,父组件接收这个事件,由父级来更改。

3.computed和watch的区别

computed:是计算属性,依赖于其他属性,只有依赖的属性值发生改变,才会重新计算computed值;
watch:类似于数据的监听回调

4.Vue的生命周期

beforeCreate;created;beforeMount;mounted;beforeUpdate;updated;activated;deactivated;beforeDestory;destoryed

5.Vue的父组件和子组件生命周期钩子函数执行顺序

加载渲染过程
父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父 mounted
子组件更新过程
父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated
父组件更新过程
父beforeUpdate -> 父updated
销毁过程
父beforeDestory -> 子beforeDestory -> 子destoryed -> 父destoryed

6.在哪个生命周期中调用异步请求

可以在created、beforeMount、mounted中进行调用,因为在这三个函数中,data已经创建。
推荐在created中调用异步请求,原因:
1. 能更快的获取到服务器数据,减少页面的loading时间
2. ssr不支持beforeMount、mounted函数,所以放在created中有助于一致性

7.在什么阶段可以访问操作DOM

在mounted之前,Vue已经将编译好的模板挂载到页面上,所以在mounted中可以访问操作DOM

8.组件中的data为什么是一个函数,return一个对象;在new Vue({})中,data可以直接是一个对象?

因为组件是用来复用的,JS里对象是引用关系,如果组件中的data是一个对象,这样的作用域没有隔离,子组件中data属性值会相互影响;如果组件中data是一个函数,每个实例可以维护一份被返回对象的独立的拷贝,组件实例之间的data属性值不会相互影响;而new Vue的实例,不会被复用,因此不存在引用哪个对象的问题。

9.Vue 3.0的特性

1. 监测机制的改变
使用Proxy实现Observer,取代Object.defineProperty
2. 模板: 把作用域插槽改成了函数的方式
3. 对象式组件的声明方式.

10.Vue组件间通信的方式

三类通信:父子通信、兄弟通信、隔代通信
1. props / $emit, 适用于父子间通信
2. $refs / $parent / $children 适用于父子间通信
3. $eventbus.$emit   /   $eventbus.$on   适用于父子、隔代、兄弟
4. $attrs / $listeners 适用于隔代通信
$attrs包含了prop 以外的父级绑定,可以使用v-bind=“$attrs”传入内部组件,设置inheritAttrs为false,可以让未被注册的属性不被渲染.
$listeners接收父级除了带.native的v-on事件,可以使用v-on=“$listeners”传入内部组件
5. provide(){}和inject:[]  适用于隔代通信
6. Vuex   适用于父子、隔代/兄弟通信
Vuex的核心是一个store,存放着很多的state;
this.$store.state.xxx 来获取数据
this.$store.commit('methodA', {nameA: valueOfA})

11.Vuex

Vuex是一个专为Vue.js应用程序开发的状态管理模式。每一个Vuex应用的核心是store,它包含着大部分的state
1. Vuex的存储是响应式的。store中改变,获取的地方也会改变。
2.改变store的唯一方法就是显示地提交commit mutation
主要包括以下几个模块:
Store:定义了初始的数据结构,相当于data
Getter:允许组件从Store中获取数据
Mutation: 改变store中值的唯一方法,必须是同步的
Action:用于提交mutation,可以异步
Module: 允许将单一的Store拆分为多个store,且同时保存在单一的状态树中

12.Vue的双向数据绑定原理是什么?

vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
具体步骤:
第一步:需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter和getter
这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化
第二步:compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
第三步:Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是:
1、在自身实例化时往属性订阅器(dep)里面添加自己
2、自身必须有一个update()方法
3、待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。
第四步:MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。

13.虚拟DOM

Vue通过建立一个虚拟DOM树来对真实DOM发生的变化保持追踪
实现原理:
1. 用JS对象模拟真实DOM树,对真实DOM进行抽象;
2. 使用Diff算法比较两个虚拟DOM树的差异
3. 再使用pach算法将两个虚拟DOM对象的差异应用到真正的DOM树中
优点:
1. 在不需要手动优化的情况下,可以提供不错的性能;
2. 无需手动操作DOM:只需要写好View-Model的代码逻辑,框架会根据虚拟DOM和数据双向绑定来更新视图,极大提高开发效率。
3. 跨平台:虚拟DOM本质上是JS对象,而DOM与平台强相关,相比之下虚拟DOM可以更方便的进行跨平台操作,例如服务器渲染等。
缺点:
无法进行极致优化

二、JS

1. 事件循环Event Loop

执行顺序:
1.一开始整个脚本作为一个宏任务执行
2.执行过程中同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列
3.当前宏任务执行完出队,检查微任务列表,有则依次执行,直到全部执行完
4.执行浏览器UI线程的渲染工作
5.检查是否有Web Worker任务,有则执行
6.执行完本轮的宏任务,回到2,依此循环,直到宏任务和微任务队列都为空
微任务包括:MutationObserver、Promise.then()或catch()、Promise为基础开发的其它技术,比如fetch API、V8的垃圾回收过程、Node独有的process.nextTick。
宏任务包括:script 、setTimeout、setInterval 、setImmediate 、I/O 、UI rendering。
注:在所有任务开始的时候,由于宏任务中包括了script,所以浏览器会先执行一个宏任务,在这个过程中看到的延迟任务(例如setTimeout)将被放到下一轮宏任务中来执行。

2.闭包

闭包是指有权访问另一个函数作用域中变量的函数。
闭包的三个特性:
1. 闭包可以访问当前函数以外的变量
2. 即使外部函数的变量已经返回,闭包仍能访问外部函数中的变量。
3. 闭包可以更新外部变量的值
使用场景:
1. setTimeout:原生的setTimeout传递的第一个函数不能带参数,通过闭包可以实现传参效果。
2.回调:定义行为,然后把它关联到某个用户事件上(点击或者按键)。代码通常会作为一个回调(事件触发时调用的函数)绑定到事件。
3.防抖: 在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。实现的关键就在于setTimeOut这个函数,由于还需要一个变量来保存计时,考虑维护全局纯净,可以借助闭包来实现。
4.封装私有变量

3.Promise

Promise 是一个对象,它代表了一个异步操作的最终完成或者失败。由于它的then方法和catch、finally方法会返回一个新的Promise所以可以允许我们链式调用,解决了传统的回调地狱问题。
再说一下then以及catch方法:
1. Promise的状态一经改变就不能再改变。
2. .then和.catch都会返回一个新的Promise。
3. catch不管被连接到哪里,都能捕获上层未捕捉过的错误。
4. 在Promise中,返回任意一个非 promise 的值都会被包裹成 promise 对象,例如return 2会被包装为return Promise.resolve(2)。
5. Promise 的 .then 或者 .catch 可以被调用多次, 但如果Promise内部的状态一经改变,并且有了一个值,那么后续每次调用.then或者.catch的时候都会直接拿到该值。
6. .then 或者 .catch 中 return 一个 error 对象并不会抛出错误,所以不会被后续的 .catch 捕获。
7. .then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环。
8. .then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传。
9. .then方法是能接收两个参数的,第一个是处理成功的函数,第二个是处理失败的函数,再某些时候你可以认为catch是.then第二个参数的简便写法。
10. .finally方法也是返回一个Promise,他在Promise结束的时候,无论结果为resolved还是rejected,都会执行里面的回调函数。
再说一下finally方法:
1. .finally()方法不管Promise对象最后的状态如何都会执行
2. .finally()方法的回调函数不接受任何的参数,也就是说你在.finally()函数中是没法知道Promise最终的状态是resolved还是rejected的
3. 它最终返回的默认会是一个上一次的Promise对象值,不过如果抛出的是一个异常则返回异常的Promise对象。
最后说一下all以及race方法:
1. Promise.all()的作用是接收一组异步任务,然后并行执行异步任务,并且在所有异步操作执行完后才执行回调。
2. .race()的作用也是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。
3. Promise.all().then()结果中数组的顺序和Promise.all()接收到的数组顺序一致。
4. all和race传入的数组中如果有会抛出异常的异步任务,那么只有最先抛出的错误会被捕获,并且是被then的第二个参数或者后面的catch捕获;但并不会影响数组中其它的异步任务的执行。

4. call/apply/bind区别

相同点:
1、都是用来改变函数的this对象的指向的。
2、第一个参数都是this要指向的对象。
3、都可以利用后续参数传参。
不同点:
apply的第二个参数传的是一个函数参数列表的数组形式。
call的用法和apply差不多,就只有传参方式不一样。它可以把多个参数分开来传,而不是像apply一样,需要把所有参数放到一个数组里边传进来。
bind的传参方式和call一样,只不过它的不同之处是,apply和call方法调用之后会立即执行,而bind方法调用之后会返回一个新的函数,它并不会立即执行,需要我们手动执行。

5. this

this的5种绑定方式:
(1)默认绑定(非严格模式下this指向全局对象, 严格模式下this会绑定到undefined)
(2)隐式绑定(当函数引用有上下文对象时, 如 obj.foo()的调用方式, foo内的this指向obj)
(3)显示绑定(通过call()或者apply()方法直接指定this的绑定对象, 如foo.call(obj))
(4)new绑定
(5)箭头函数绑定(this的指向由外层作用域决定的)

1. 默认绑定
在非严格模式下this指向的是全局对象window,而在严格模式下会绑定到undefined。

2. 隐式绑定
this 永远指向最后调用它的那个对象。

3. 隐式绑定的隐式丢失问题
隐式丢失其实就是被隐式绑定的函数在特定的情况下会丢失绑定对象。
有两种情况容易发生隐式丢失问题:
(1)使用另一个变量来给函数取别名
(2)将函数作为参数传递时会被隐式赋值,回调函数丢失this绑定
如果把一个函数当成参数传递到另一个函数的时候,会发生隐式丢失的问题,且与包裹着它的函数的this指向无关。在非严格模式下,会把该函数的this绑定到window上,严格模式下绑定到undefined。

4. 显式绑定
就是强行使用某些方法,改变函数内this的指向。
通过call()、apply()或者bind()方法直接指定this的绑定对象, 如foo.call(obj)。
注:
(1)使用.call()或者.apply()的函数是会直接执行的
(2)bind()是创建一个新的函数,需要手动调用才会执行
(3).call()和.apply()用法基本类似,不过call接收若干个参数,而apply接收的是一个数组
(4)如果call、apply、bind接收到的第一个参数是空或者null、undefined的话,则会忽略这个参数。

5. 显式绑定的其它用法
可以在一个函数内使用call来显式绑定某个对象,这样无论怎样调用它,其内部的this总是指向这个对象。
forEach、map、filter函数的第二个参数也是能显式绑定this的

6. new 绑定
使用new来调用一个函数,会构造一个新对象并把这个新对象绑定到调用函数中的this。

7. 箭头函数绑定
(1)它里面的this是由外层作用域来决定的,且指向函数定义时的this而非执行时
(2)字面量创建的对象,作用域是window,如果里面有箭头函数属性的话,this指向的是window
(3)构造函数创建的对象,作用域是可以理解为是这个构造函数,且这个构造函数的this是指向新建的对象的,因此this指向这个对象。
(4)箭头函数的this是无法通过bind、call、apply来直接修改,但是可以通过改变作用域中this的指向来间接修改。
优点:
(1)箭头函数写代码拥有更加简洁的语法(当然也有人认为这是缺点)
(2)this由外层作用域决定,所以在某些场合我们不需要写类似const that = this这样的代码
避免使用的场景:
(1)使用箭头函数定义对象的方法;
(2)定义原型方法;
(3)构造函数使用箭头函数;
(4)作为事件的回调函数;

8.手写题
(1)手写一个new实现
使用new创建的实例:
    能访问到构造函数里的属性(name)
    能访问原型中的属性(eat)
function new () {
  // 1. 获取构造函数,并且删除 arguments 中的第一项
  var Con = [].shift.call(arguments);
  // 2. 创建一个空的对象并链接到构造函数的原型,使它能访问原型中的属性
  var obj = Object.create(Con.prototype);
  // 3. 使用apply改变构造函数中this的指向实现继承,使obj能访问到构造函数中的属性
  var ret = Con.apply(obj, arguments);
  // 4. 优先返回构造函数返回的对象
  return ret instanceof Object ? ret : obj;
}

(2)手写一个call函数实现
ES3实现:
function fnFactory(context) {
  var unique_fn = "fn";
  while (context.hasOwnProperty(unique_fn)) {
    unique_fn = "fn" + Math.random();
  }
  return unique_fn;
}
Function.prototype.call2 = function(context) {
  // 1. 若是传入的context是null或者undefined时指向window;
  // 2. 若是传入的是原始数据类型, 原生的call会调用 Object() 转换
  context = (context !== null && context !== undefined) ? Object(context) : window;
  // 3. 创建一个独一无二的fn函数的命名
  var fn = fnFactory(context);
  // 4. 这里的this就是指调用call的那个函数
  // 5. 将调用的这个函数赋值到context中, 这样之后执行context.fn的时候, fn里的this就是指向context了
  context[fn] = this;
  // 6. 定义一个数组用于放arguments的每一项的字符串: ['agruments[1]', 'arguments[2]']
  var args = [];
  // 7. 要从第1项开始, 第0项是context
  for (var i = 1, l = arguments.length; i < l; i++) {
    args.push("arguments[" + i + "]");
  }
  // 8. 使用eval()来执行fn并将args一个个传递进去
  var result = eval("context[fn](" + args + ")");
  // 9. 给context额外附件了一个属性fn, 所以用完之后需要删除
  delete context[fn];
  // 10. 函数fn可能会有返回值, 需要将其返回
  return result;
};

ES6实现:
Function.prototype.call3 = function(context) {
  context = (context !== null && context !== undefined) ? Object(context) : window;
  var fn = Symbol();
  context[fn] = this;

  let args = [...arguments].slice(1);
  let result = context[fn](...args);

  delete context[fn];
  return result;
};

(3)手写一个apply实现
ES3实现:
function fnFactory(context) {
  var unique_fn = "fn";
  while (context.hasOwnProperty(unique_fn)) {
    unique_fn = "fn" + Math.random();
  }
  return unique_fn;
}
Function.prototype.apply2 = function(context, arr) {
  // 1. 若是传入的context是null或者undefined时指向window;
  // 2. 若是传入的是原始数据类型, 原生的call会调用 Object() 转换
  context = context ? Object(context) : window;
  // 3. 创建一个独一无二的fn函数的命名
  var fn = fnFactory(context);
  // 4. 这里的this就是指调用call的那个函数
  // 5. 将调用的这个函数赋值到context中, 这样之后执行context.fn的时候, fn里的this就是指向context了
  context[fn] = this;

  var result;
  // 6. 判断有没有第二个参数
  if (!arr) {
    result = context[fn]();
  } else {
    // 7. 有的话则用args放每一项的字符串: ['arr[0]', 'arr[1]']
    var args = [];
    for (var i = 0, len = arr.length; i < len; i++) {
      args.push("arr[" + i + "]");
    }
    // 8. 使用eval()来执行fn并将args一个个传递进去
    result = eval("context[fn](" + args + ")");
  }
  // 9. 给context额外附件了一个属性fn, 所以用完之后需要删除
  delete context[fn];
  // 10. 函数fn可能会有返回值, 需要将其返回
  return result;
};

ES6实现:
Function.prototype.apply3 = function(context, arr) {
  context = context ? Object(context) : window;
  let fn = Symbol();
  context[fn] = this;

  let result = arr ? context[fn](...arr) : context[fn]();
  delete context[fn];
  return result;
};

(4)手写一个bind函数实现
提示:
函数内的this表示的就是调用的函数
可以将上下文传递进去, 并修改this的指向
返回一个函数
可以传入参数
柯里化
一个绑定的函数也能使用new操作法创建对象, 且提供的this会被忽略
Function.prototype.bind2 = function(context) {
  // 1. 判断调用bind的是不是一个函数
  if (typeof this !== "function") {
    throw new Error(
      "Function.prototype.bind - what is trying to be bound is not callable"
    );
  }
  // 2. 外层的this指向调用者(也就是调用的函数)
  var self = this;
  // 3. 收集调用bind时的其它参数
  var args = Array.prototype.slice.call(arguments, 1);

  // 4. 创建一个返回的函数
  var fBound = function() {
    // 6. 收集调用新的函数时传入的其它参数
    var innerArgs = Array.prototype.slice.call(arguments);
    // 7. 使用apply改变调用函数时this的指向
    // 作为构造函数调用时this表示的是新产生的对象, 不作为构造函数用的时候传递context
    return self.apply(
      this instanceof fNOP ? this : context,
      args.concat(innerArgs)
    );
  };
  // 5. 创建一个空的函数, 且将原型指向调用者的原型(为了能用调用者原型中的属性)
  // 下面三步的作用有点类似于 fBoun.prototype = this.prototype 但有区别
  var fNOP = function() {};
  fNOP.prototype = this.prototype;
  fBound.prototype = new fNOP();
  // 8. 返回最后的结果
  return fBound;
};

6.封装

封装:把客观事物封装成抽象的类,隐藏属性和方法,仅对外公开接口。

1. ES6之前的封装:是借助于原型对象和构造函数来实现的

(一) 私有属性、公有属性、静态属性概念
(1)私有属性和方法:只能在构造函数内访问不能被外部所访问(在构造函数内使用var声明的属性)
(2)公有属性和方法(或实例方法):对象外可以访问到对象内的属性和方法(在构造函数内使用this设置,或者设置在构造函数原型对象上比如Cat.prototype.xxx)
(3)静态属性和方法:定义在构造函数上的方法(比如Cat.xxx),不需要实例就可以调用
注:静态属性和方法:Promise.all()、Promise.race()、Object.assign()、Array.from();
    实例方法:例如Array.prototype.push,它们实际上是存在于原型对象上的

(二) 实例对象上的属性和构造函数原型上的属性:
定义在构造函数原型对象上的属性和方法虽然不能直接表现在实例对象上,但是实例对象却可以访问或者调用它们。
原型链查找:当访问一个对象的属性 / 方法时,它不仅仅在该对象上查找,还会查找该对象的原型,以及该对象的原型的原型,一层一层向上查找,直到找到一个名字匹配的属性 / 方法或到达原型链的末尾(null)。
原型对象本质也是个对象,所以它的__proto__也就是Object.prototype。

(三) 遍历实例对象属性的三种方法:
(1)使用for...in...能获取到实例对象自身的属性和原型链上的属性
(2)使用Object.keys()和Object.getOwnPropertyNames()只能获取实例对象自身的属性
(3)可以通过.hasOwnProperty()方法传入属性名来判断一个属性是不是实例自身的属性

2. ES6之后的封装

(一) class的基本概念:
(1)当使用class的时候,它会默认调用constructor这个函数,来接收一些参数,并构造出一个新的实例对象(this)并将它返回,因此它被称为constructor构造方法(函数)。
(2)如果class没有定义constructor,也会隐式生成一个constructor方法

(二) class中几种定义属性的区别:
(1)在constructor中var一个变量,它只存在于constructor这个构造函数中
(2)在constructor中使用this定义的属性和方法会被定义到实例上
(3)在class中使用=来定义一个属性和方法,效果与第二点相同,会被定义到实例上
(4)在class中直接定义一个方法,会被添加到原型对象prototype上,并且在使用类的时候,会隐式执行constructor函数。
(5)在class中使用了static修饰符定义的属性和方法被认为是静态的,被添加到类本身,不会添加到实例上。也可以使用Cat.xxx这种方式定义。

(三) other:
(1)class本质虽然是个函数,但是并不会像函数一样提升至作用域最顶层
(2)如遇class中箭头函数等题目请参照构造函数来处理
(3)使用class生成的实例对象,也会有沿着原型链查找的功能
(4)在ES6之后,新增了class 这个关键字。它可以用来代替构造函数,达到创建“一类实例”的效果。并且类的数据类型就是函数,所以用法上和构造函数很像,直接用new命令来配合它创建一个实例。
(5)constructor中定义的相同名称的属性和方法会覆盖在class里定义的

7.继承

继承就是子类可以使用父类的所有功能,并且对这些功能进行扩展。
八种继承方式:
1.原型链继承
2.构造继承
3.组合继承
4.寄生组合继承
5.原型式继承
6.寄生继承
7.混入式继承
8.class中的extends继承

1.原型链继承
将子类的原型对象指向父类的实例
Child.prototype = new Parent()、
优点:
(1)继承了父类的模板,又继承了父类的原型对象
缺点:
(1)如果要给子类的原型上新增属性和方法,就必须放在Child.prototype = new Parent()这样的语句后面
(2)无法实现多继承(因为已经指定了原型对象了)
(3)来自原型对象的所有属性都被共享了,这样如果不小心修改了原型对象中的引用类型属性,那么所有子类创建的实例对象都会受到影响
(4)创建子类时,无法向父类构造函数传参数

2. instanceof
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
a instanceof B
实例对象a instanceof 构造函数B
检测a的原型链(__proto__)上是否有B.prototype,有则返回true,否则返回false。
isPrototypeOf()属于Object.prototype上的方法,它的用法和instanceof相反。
它是用来判断指定对象object1是否存在于另一个对象object2的原型链中,是则返回true,否则返回false。

3. 构造继承
在子类构造函数内部使用call或apply来调用父类构造函数
function Child () {
    Parent.call(this, ...arguments)
}
优点:解决了原型链继承中子类共享父类引用对象的问题,实现多继承,创建子类实例时,可以向父类传递参数。
缺点:
(1)构造继承只能继承父类的实例属性和方法,不能继承父类原型的属性和方法,
(2)实例并不是父类的实例,只是子类的实例(见题目3.5)
(3)无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

4. 组合继承
// 构造继承
function Child () {
  Parent.call(this, ...arguments)
}
// 原型链继承
Child.prototype = new Parent()
// 修正constructor
Child.prototype.constructor = Child

概念:
组合继承就是将原型链继承与构造函数继承组合在一起,从而发挥两者之长的一种继承模式。
实现方式:
使用原型链继承来保证子类能继承到父类原型中的属性和方法
使用构造继承来保证子类能继承到父类的实例属性和方法
基操:
通过call/apply在子类构造函数内部调用父类构造函数
将子类构造函数的原型对象指向父类构造函数创建的一个匿名实例
修正子类构造函数原型对象的constructor属性,将它指向子类构造函数

construcotr总结:
(1)constructor它是构造函数原型对象中的一个属性,正常情况下它指向的是原型对象。
(2)它并不会影响任何JS内部属性,只是用来标示一下某个实例是由哪个构造函数产生的而已。
(3)如果我们使用了原型链继承或者组合继承无意间修改了constructor的指向,那么出于编程习惯,我们最好将它修改为正确的构造函数。

匿名函数外给构造函数的原型对象中添加一个方法:
(1)通过a.__proto__来访问到原型对象
(2)通过a.constructor.prototype来访问到原型对象

优点:
(1)可以继承父类实例属性和方法,也能够继承父类原型属性和方法
(2)弥补了原型链继承中引用属性共享的问题
(3)可传参,可复用
缺点:
(1)使用组合继承时,父类构造函数会被调用两次
(2)并且生成了两个实例,子类实例中的属性和方法会覆盖子类原型(父类实例)上的属性和方法,所以增加了不必要的内存。

5. 寄生组合继承
// 构造继承
function Child () {
  Parent.call(this, ...arguments)
}
// 原型式继承
Child.prototype = Object.create(Parent.prototype)
// 修正constructor
Child.prototype.constructor = Child

Object.create(proto, propertiesObject)
参数一,需要指定的原型对象,它的作用就是能指定你要新建的这个对象它的原型对象是谁
参数二,可选参数,给新对象自身添加新属性以及描述器
优点:
(1)避免了组合继承中调用两次父类构造函数,初始化两次实例属性的缺点。
(2)只调用了一次父类构造函数,只创建了一份父类属性
(3)子类可以用到父类原型链上的属性和方法
(4)能够正常的使用instanceOf和isPrototypeOf方法

6. 原型式继承
var child = Object.create(parent)
实现方式:
原理是创建一个构造函数,构造函数的原型指向对象,然后调用 new 操作符创建实例,并返回这个实例,本质是一个浅拷贝。
在ES5之后可以直接使用Object.create()方法来实现,而在这之前就只能手动实现一个了。
function objcet (obj) {
    function F () {};
    F.prototype = obj;
    F.prototype.constructor = F;
    return new F();
}
优点:
在不用创建构造函数的情况下,实现了原型链继承,代码量减少一部分。
缺点:
一些引用数据操作的时候会出问题,两个实例会公用继承实例的引用数据类
谨慎定义方法,以免定义方法也继承对象原型的方法重名
无法直接给父级构造函数使用参数

7. 寄生式继承
function createAnother (original) {
    var clone = Object.create(original);; // 通过调用 Object.create() 函数创建一个新对象
    clone.fn = function () {}; // 以某种方式来增强对象
    return clone; // 返回这个对象
}

实现方式:
是在原型式继承的基础上再封装一层,来增强对象,之后将这个对象返回。
优点:
再不用创建构造函数的情况下,实现了原型链继承,代码量减少一部分。
缺点:
一些引用数据操作的时候会出问题,两个实例会公用继承实例的引用数据类
谨慎定义方法,以免定义方法也继承对象原型的方法重名
无法直接给父级构造函数使用参数

8. 混入方式继承多个对象
function Child () {
    Parent.call(this)
    OtherParent.call(this)
}
Child.prototype = Object.create(Parent.prototype)
Object.assign(Child.prototype, OtherParent.prototype)
Child.prototype.constructor = Child

ES6中的方法Object.assign():
它的作用就是可以把多个对象的属性和方法拷贝到目标对象中,若是存在同名属性的话,后面的会覆盖前面。(是一种浅拷贝)

9. class中的继承
class Child extends Parent {
    constructor (...args) {
        super(...args)
    }
}

extends的作用:
(1)class可以通过extends关键字实现继承父类的所有属性和方法
(2)若是使用了extends实现继承的子类内部没有constructor方法,则会被默认添加constructor和super

子类必须得在constructor中调用super方法,否则新建实例就会报错,因为子类自己没有自己的this对象,而是继承父类的this对象,然后对其加工,如果不调用super的话子类就得不到this对象。

ES6中的继承:
主要是依赖extends关键字来实现继承,且继承的效果类似于寄生组合继承
使用了extends实现继承不一定要constructor和super,因为没有的话会默认产生并调用它们
extends后面接着的目标不一定是class,只要是个有prototype属性的函数就可以了

super相关:
在实现继承时,如果子类中有constructor函数,必须得在constructor中调用一下super函数,因为它就是用来产生实例this的。
super有两种调用方式:当成函数调用和当成对象来调用。
super当成函数调用时,代表父类的构造函数,且返回的是子类的实例,也就是此时super内部的this指向子类。在子类的constructor中super()就相当于是Parent.constructor.call(this)。使用限制:子类constructor中如果要使用this的话就必须放到super()之后;super当成函数调用时只能在子类的construtor中使用
super当成对象调用时,普通函数中super对象指向父类的原型对象,静态函数中指向父类。且通过super调用父类的方法时,super会绑定子类的this,就相当于是Parent.prototype.fn.call(this)。
通过super调用父类的方法时,super会绑定子类的this。在使用它的时候,必须得显式的指定它是作为函数使用还是对象来使用,否则会报错。

ES5继承和ES6继承的区别:
在ES5中的继承(例如构造继承、寄生组合继承) ,实质上是先创造子类的实例对象this,然后再将父类的属性和方法添加到this上(使用的是Parent.call(this))。
而在ES6中却不是这样的,它实质是先创造父类的实例对象this(也就是使用super()),然后再用子类的构造函数去修改this。
 

三、HTML

1. meta标签

meta 标签主要是用来描述一个 html 网页文档的属性的。还可以用于 seo 的搜索优化。
基本属性分为必选属性和可选属性
必选属性:
content 属性。该属性是为了定义与http-equiv或者name属性相关的元信息,其中的内容是为了便于搜索机器人查找信息和分类使用的。
可选属性:
name 属性。主要用于描述网页。name属性的值有:author、description、keywords、generator、robot等等
<meta name="robot" content="none">
name="robot" content值有all、none、index、noindex、follow和nofollow。默认为all。
设定为all:文件将被检索,且页面上的链接可以被查询;
设定为none:文件将不被检索,且页面上的链接不可以被查询;
设定为index:文件将被检索;
设定为follow:页面上的链接可以被查询;
设定为noindex:文件将不被检索,但页面上的链接可以被查询;
设定为nofollow:文件将不被检索,页面上的链接可以被查询。
http-equiv 属性。相当于http的头文件作用,可以向浏览器返回一些有用的信息,以帮助正确和精确的显示内容。http-equiv属性的值有content-type、expires、pragma、refresh、set-cookie等。
content-type(文档内容类型:用于设定文档的类型和字符集)
expires(期限:可以用于设定网页的到期期限)
pragma(cache模式:即是否从缓存中访问网页内容)
refresh(刷新:等待一定时间自动刷新或跳转到其他url)
// 文档类型
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
// 必须是 GMT 格式
<meta http-equiv="expires" content="Fri,12 Jan 2001 15:15:15 GMT">
// 是否设置缓存
<meta http-equiv="pragma" content="no-cache">
// 等待一定时间自动跳转
<meta http-equiv="refresh" content="1; url=https://www.baidu.com"/>

四、浏览器

1. 跨域

概念:由于浏览器的同源策略,为了防范跨站脚本的攻击,禁止客户端脚本对不同域下的文件或脚本进行跨站调用资源。
同源策略的概念:同源是指“协议+域名+端口”相同,即使不同域名指向同一个IP地址,也非同源。
跨域的解决方案
(1) jsonp跨域
(2) document.domain + iframe跨域
(3) location.hash + iframe跨域
(4) window.name + iframe跨域
(5) postMessage跨域
(6) 跨域资源共享(CORS)
(7) nginx代理跨域
(8) nodejs中间件代理跨域
(9) WebSocket协议跨域

2. 从输入URL到页面呈现发生了什么?

浏览器端的网络请求过程:
首先,浏览器会构建请求,请求行包括请求方法、路径和HTTP协议版本。
然后查找强缓存,如果命中直接使用,否则进入下一步DNS解析过程。
由于我们输入的是域名,而数据包是通过IP地址传给对方的,因此我们需要得到域名对应的IP地址,这个过程需要依赖一个服务系统,这个系统将域名和 IP 一一映射,这个系统就叫做DNS(域名系统),得到具体 IP 的过程就是DNS解析,浏览器提供了DNS数据缓存功能,即如果一个域名已经解析过,那会把解析的结果缓存下来,下次处理直接走缓存,不需要经过 DNS解析,另外,如果不指定端口的话,默认采用对应的 IP 的 80 端口。
接着建立TCP连接,Chrome 在同一个域名下要求同时最多只能有 6 个 TCP 连接,超过 6 个的话剩下的请求就得等待。假设现在不需要等待,进入了 TCP 连接的建立阶段。TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。建立 TCP连接经历了下面三个阶段: 
(1)通过三次握手建立客户端和服务器之间的连接,总共发送3个数据包确认已经建立连接。
(2)然后进行数据传输。接收方在接收到数据包后必须要向发送方确认, 如果发送方没有接到这个确认的消息,就判定为数据包丢失,并重新发送该数据包。在发送的过程中还有一个优化策略,就是把大的数据包拆成一个个小包,依次传输到接收方,接收方按照这个小包的顺序把它们组装成完整数据包。
(3)数据传输完成后,通过四次挥手来断开连接。
TCP连接建立完成后,浏览器可以和服务器开始通信,开始发送 HTTP 请求。浏览器发 HTTP 请求包括:请求行、请求头和请求体。请求行由请求方法、请求URI和HTTP版本协议组成。Cache-Control、If-Modified-Since、If-None-Match都由可能被放入请求头中作为缓存的标识信息。请求体只有在POST方法下存在,常见的场景是表单提交。HTTP 请求到达服务器,服务器进行对应的处理。最后要把数据传给浏览器,也就是返回网络响应。网络响应具有三个部分:响应行、响应头和响应体。响应行由HTTP协议版本、状态码和状态描述组成。响应头包含了服务器及其返回数据的一些信息, 服务器生成数据的时间、返回的数据类型以及对即将写入的Cookie信息。响应完成之后,判断Connection字段, 如果请求头或响应头中包含Connection: Keep-Alive,表示建立了持久连接,这样TCP连接会一直保持,之后请求统一站点的资源会复用这个连接。否则断开TCP连接, 请求-响应流程结束。

解析算法部分:
完成了网络请求和响应,如果响应头中Content-Type的值是text/html,浏览器就会进行解析和渲染。解析部分,主要分为构建 DOM树、样式计算以及生成布局树(Layout Tree)。
由于浏览器无法直接理解HTML字符串,因此将这一系列的字节流转换为DOM树的结构。DOM树本质上是一个以document为根节点的多叉树。
CSS样式,一般来源于link标签引用、style标签中的样式以及元素的内嵌style属性。浏览器是无法直接识别 CSS 样式文本的,因此渲染引擎接收到 CSS 文本之后会先将其转化为一个结构化的对象,即styleSheets。样式被格式化和标准化后,通过继承和层叠两个计算规则来计算每个节点的具体样式信息。
DOM树和DOM样式生成之后,通过浏览器的布局系统确定元素的位置,生成一棵布局树。这棵布局树值仅包含可见元素,对于 head标签和设置了display: none的元素,将不会被放入其中。

渲染过程主要包括建立图层树(Layer Tree)、生成绘制列表、生成图块并栅格化、显示器显示内容。

3. CSRF攻击

CSRF跨站请求伪造,指的是黑客诱导用户点击链接,打开黑客的网站,然后黑客利用用户目前的登录状态发起跨站请求。
CSRF可能会自动发 GET 请求、自动发 POST 请求或者诱导点击发送 GET 请求。
自动发 GET 请求:进入页面后自动发送 get 请求,这个请求会自动带上关于 xxx.com 的 cookie 信息(如果已经在 xxx.com 中登录过)。并且服务器端没有相应的验证机制,它可能认为发请求的是一个正常的用户,因为携带了相应的 cookie,然后进行相应的各种操作,可以是转账汇款以及其他的恶意操作。
自动发 POST 请求:黑客可能自己填了一个表单,写了一段自动提交的脚本。同样也会携带相应的用户 cookie 信息,让服务器误以为是一个正常的用户在操作。
导点击发送 GET 请求:在黑客的网站上,可能会放上一个链接,诱导用户点击,点击后,自动发送 get 请求,这个请求会自动带上关于 xxx.com 的 cookie 信息(如果已经在 xxx.com 中登录过)。并且服务器端没有相应的验证机制,它可能认为发请求的是一个正常的用户,因为携带了相应的 cookie,然后进行相应的各种操作,可以是转账汇款以及其他的恶意操作。
和XSS攻击对比,CSRF 攻击并不需要将恶意代码注入用户当前页面的html文档中,而是跳转到新的页面,利用服务器的验证漏洞和用户之前的登录状态来模拟用户进行操作。

防范措施:
(1)利用 Cookie 的 SameSite 属性,SameSite可以设置为三个值,Strict、Lax和None。在Strict模式下,浏览器完全禁止第三方请求携带Cookie。在Lax模式下只能在 get 方法提交表单或者a 标签发送 get 请求的情况下可以携带 Cookie,其他情况均不能。在None模式下,也就是默认模式,请求会自动携带上 Cookie。
(2)验证来源站点,需要用到请求头中的:Origin和Referer两个字段,Origin只包含域名信息,Referer包含了具体的 URL 路径。这两个都是可以伪造的, Ajax 中可以自定义请求头,安全性略差。
(3)CSRF Token,浏览器向服务器发送请求时,服务器生成一个字符串,将其植入到返回的页面中。然后浏览器如果要发送请求,就必须带上这个字符串,然后服务器来验证是否合法,如果不合法则不予响应。这个字符串也就是CSRF Token,通常第三方站点无法拿到这个 token, 因此也就是被服务器给拒绝。

4. XSS攻击

XSS 攻击是指浏览器中执行恶意脚本(无论是跨域还是同域),从而拿到用户的信息并进行操作。它可以窃取Cookie。监听用户行为,比如输入账号密码后直接发送到黑客服务器。修改 DOM 伪造登录表单。在页面中生成浮窗广告。
通常情况,XSS 攻击的实现有三种方式——存储型、反射型和文档型。
存储型,就是将恶意脚本存储存储到了服务端的数据库,然后在客户端执行这些脚本,从而达到攻击的效果。常见的场景是留言评论区提交一段脚本代码,如果前后端没有做好转义的工作,那评论内容存到了数据库,在页面渲染过程中直接执行, 相当于执行一段未知逻辑的 JS 代码。
反射型XSS指的是恶意脚本作为网络请求的一部分。过服务器,然后再反射到HTML文档中,执行解析。和存储型不一样的是,服务器并不会存储这些恶意脚本。
文档型的 XSS 攻击并不会经过服务端,而是作为中间人的角色,在数据传输过程劫持到网络数据包,然后修改里面的 html 文档。这样的劫持方式包括WIFI路由器劫持或者本地恶意软件等。
防范的措施:
(1)不要相信用户的输入,无论是在前端和服务端,都要对用户的输入进行转码或者过滤,让其不可执行。
(2)利用 CSP,即浏览器中的内容安全策略,它的核心思想就是服务器决定浏览器加载哪些资源,可以限制其他域下的资源加载、禁止向其它域提交数据、提供上报机制,能帮助我们及时发现 XSS 攻击。
(3)利用 HttpOnly,很多 XSS 攻击脚本都是用来窃取Cookie, 而设置 Cookie 的 HttpOnly 属性后,JavaScript 便无法读取 Cookie 的值。这样也能很好的防范 XSS 攻击。

五、HTTP

六、CSS

1. 说一下回流和重绘

回流:
触发条件:
当我们对 DOM 结构的修改引发 DOM 几何尺寸变化的时候,会发生回流的过程。
例如以下操作会触发回流:

  1. 一个 DOM 元素的几何属性变化,常见的几何属性有width、height、padding、margin、left、top、border 等等, 这个很好理解。
  2. 使 DOM 节点发生增减或者移动。
  3. 读写 offset族、scroll族和client族属性的时候,浏览器为了获取这些值,需要进行回流操作。
  4. 调用 window.getComputedStyle 方法。

回流过程:由于DOM的结构发生了改变,所以需要从生成DOM这一步开始,重新经过样式计算、生成布局树、建立图层树、再到生成绘制列表以及之后的显示器显示这整一个渲染过程走一遍,开销是非常大的。


重绘:
触发条件:
当 DOM 的修改导致了样式的变化,并且没有影响几何属性的时候,会导致重绘(repaint)。
重绘过程:由于没有导致 DOM 几何属性的变化,因此元素的位置信息不需要更新,所以当发生重绘的时候,会跳过生存布局树和建立图层树的阶段,直接到生成绘制列表,然后继续进行分块、生成位图等后面一系列操作。
如何避免触发回流和重绘:

  1. 避免频繁使用 style,而是采用修改class的方式。
  2. 将动画效果应用到position属性为absolute或fixed的元素上。
  3. 也可以先为元素设置display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘
  4. 使用createDocumentFragment进行批量的 DOM 操作。
  5. 对于 resize、scroll 等进行防抖/节流处理。
  6. 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
  7. 利用 CSS3 的transform、opacity、filter这些属性可以实现合成的效果,也就是GPU加速。


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值