烂笔头

251 阅读19分钟

JS

继承

  1. 通过原型链继承
function Parent() {
    this.lastName = "wang"  //父类属性
}
Parent.prototype.asset = ['house', 'car']  //父类原型属性
function Child() {
}
Child.prototype = new Parent()  //将父类的实例作为子类的原型  !!! 原型链继承的核心

var child = new Child();
var child1 = new Child();
child.asset.push("plane")
console.log(child.lastName);//"wang"  //继承父类属性
console.log(child1.asset);//[ 'house', 'car', 'plane' ] // 继承父类属性 可以修改父类属性

优点:可以访问父类的属性和方法和原型上的属性和方法
缺点:继承方法如果是引用类型,其中一个子类进行修改,那么全部都会受到影响

  1. 通过构造函数继承
    用.call()和.apply()将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行(复制))
function Parent() {
    this.lastName = "wang";
    this.hobby = ['a', 'b']
}
Parent.prototype.asset = ['house', 'car']

function Child() {
    Parent.call(this)  // 在子类构造函数中调用父类的构造函数  !!! 构造函数继承的核心
}


var child = new Child();
var child1 = new Child();
child.hobby.push("c")
console.log(child.lastName);//“wang" //继承父类属性
console.log(child1.hobby);//['a', 'b'] //引用类型不会被修改
console.log(child1.asset);//undefined // 无法继承父类原型链上的属性

优点:可以保证每个子类维护自己的属性
缺点:无法访问原型链上的属性和方法

  1. 组合继承(组合原型链继承和借用构造函数继承)
function Parent() {
    this.lastName = "wang";
    this.hobby = ['a', 'b']
}
Parent.prototype.asset = ['house', 'car']

function Child() {
    Parent.call(this)  //调用父类构造函数
}
Child.prototype = new Parent(); // 第二次调用父类构造函数

var child = new Child();
var child1 = new Child();
child.hobby.push("c")

console.log(child.lastName); // wang // 继承父类属性
console.log(child1.hobby); // [ 'a', 'b' ] 引用类型不会被修改
console.log(child1.asset); // [ 'house', 'car' ] 继承父类原型链上的属性
  1. 原型式继承 没有使用构造函数,借助原型可以基于已有的对象创建新对象,同时还不必创建自定义类型。就是 ES5 Object.create 的模拟实现,将传入的对象作为创建的对象的原型
function createObj(o){  //传入一个对象
  function F(){}   
  F.prototype = o;
  return new F(); // 相当于对传入对象的浅拷贝
}
let dog = {
    name: "wang"
}
dog1 = createObj(dog); //dog1.name = wang
  1. 寄生式继承
function create(obj){
 var clone = Object.create(obj);
 clone.getColor = function(){
   console.log(clone.color)
 }
 return clone
}

var dog = {
 species: '贵宾犬',
 color: 'yellow'
}

var dog1 = create(dog);
dog1.getColor();  // yellow

  1. 寄生式组合继承(优化组合继承)
function Parent() {
    this.lastName = "wang";
    this.hobby = ['a', 'b']
}
Parent.prototype.asset = ['house', 'car']

function Child() {
    Parent.call(this)  //调用父类构造函数
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype = Parent.prototype; //子类原型指向父类原型
Child.prototype.constructor = Child; // 子类构造函数指向自身构造函数

var child = new Child();
var child1 = new Child();
child.hobby.push("c")

console.log(child.lastName); // wang // 继承父类属性
console.log(child1.hobby); // [ 'a', 'b' ] 引用类型不会被修改
console.log(child1.asset); // [ 'house', 'car' ] 继承父类原型链上的属性
  1. ES6 Class继承
class Parent{
    constructor(){
        this.name = 'Parent'
        this.arr = [1, 2, 3]
    }
    say(){
        console.log(this.name)
    }
}
class Child extends Parent{
   constructor(){
    super() //通过super()调用父类构造函数
    this.type="Child"
  }
}
let child1 = new Child()
let child2 = new Child()
child1.arr.push(4)
console.log(child1.say()) // Parent  // 继承父类方法和属性
console.log(child1.arr, child2.arr) // [1, 2, 3, 4]  [1, 2, 3]  // 引用类型属性隔离
console.log(child1.constructor === Child) // true // 子类指向其构造函数
console.log(child2.constructor === Parent) // false

跨域

1、JSONP
基于script标签不会进行同源验证的原理,实现跨域
2、cors同源
cors同源是值服务端修改自己的请求头,修改“Access-Control-Allow-Origin”的值,需要服务端进行配置
3、webSocket

this指向

  1. 在对象的方法中使用,this指向当前的对象
  2. 通过call\apply\bind来指定
  3. 箭头函数 this指向作用域链上一层的this
  4. new 关键字调用函数时,函数中的 this 一定是 JS 创建的新对象

new过程

  • 创建一个新对象
  • 把新对象的_proto_隐式原型指向构造函数的原型
  • 执行构造函数中的代码,并把this指向新对象
  • 返回新对象
function my_new(constructor, ...args) {
    // 创建一个新的对象
    const obj = {}
    // 将 构造函数 的 原型对象 赋值给新建对象的 隐式原型
    obj._proto_ = constructor.prototype
    // 执行 构造函数 并将 构造函数里面的this指向 新建对象obj
    const result = constructor.call(obj, ...args)
    // 如果 执行结构是一个对象,那么就返回执行结果 否则返回新建对象
    return typeof result === 'object' ? result : obj 
}

数据类型判断

typeof

typeof可以判断原始数据类型,除了null之外

console.log(typeof 2) //number
console.log(typeof 'hello') //string
console.log(typeof null) //object
console.log(typeof true)//boolean
console.log(typeof undefined) //undefined
console.log(typeof []) //object

instanceof

instanceof 可以正确的判断对象的类型,因为内部机制是通过判断对象的原型链中是不是能找到类型的prototype:

console.log([] instanceof Array) //true
console.log({} instanceof Object)//true
console.log(function(){} instanceof Function)//true
console.log(1 instanceof Number)//false

Object.prototype.toString.call

使用 Object 对象的原型方法toString,返回值是[object 类型]字符串,该方法基本上能判断所有的数据类型.

var toString = Object.prototype.toString;
console.log(toString.call(2)) //[object Number]
console.log(toString.call(true)) //[object Boolean]
console.log(toString.call(function(){})) //[object Function]

Promise

Promise对象的状态不受外界影响,它有三种状态,pending(进行中)、fulfilled(已成功)、rejected(已失败)。它的状态可以从pending变为fulfilled,或者从pending变为rejected

// MyPromise.js

// 先定义三个常量表示状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

// 新建 MyPromise 类
class MyPromise {
  constructor(executor){
    // executor 是一个执行器,进入会立即执行
    // 并传入resolve和reject方法
    executor(this.resolve, this.reject)
  }

  // 储存状态的变量,初始值是 pending
  status = PENDING;

  // resolve和reject为什么要用箭头函数?
  // 如果直接调用的话,普通函数this指向的是window或者undefined
  // 用箭头函数就可以让this指向当前实例对象
  // 成功之后的值
  value = null;
  // 失败之后的原因
  reason = null;

  // 更改成功后的状态
  resolve = (value) => {
    // 只有状态是等待,才执行状态修改
    if (this.status === PENDING) {
      // 状态修改为成功
      this.status = FULFILLED;
      // 保存成功之后的值
      this.value = value;
    }
  }

  // 更改失败后的状态
  reject = (reason) => {
    // 只有状态是等待,才执行状态修改
    if (this.status === PENDING) {
      // 状态成功为失败
      this.status = REJECTED;
      // 保存失败后的原因
      this.reason = reason;
    }
  }
  then(onFulfilled, onRejected) {
   // 判断状态
   if (this.status === FULFILLED) {
    // 调用成功回调,并且把值返回
    onFulfilled(this.value);
   } else if (this.status === REJECTED) {
    // 调用失败回调,并且把原因返回
    onRejected(this.reason);
   }
 }
}

作用域

函数在执行的时候遇到一个变量,他会先看看自己的作用域里有没有该变量,没有的话就会向上从父级作用域里去查找,直到找到位置,否则报错undefined

闭包及闭包常用应用场景

闭包的定义:当函数可以记住并访问所在的作用域时,就产生了闭包,即使这个函数是在当前词法作用域之外执行。 再来说的直白一点,闭包就是一个有权限访问其所在作用域中变量的一个函数。

应用场景: 节流防抖 https://segmentfault.com/a/1190000018428170

function foo(){
    var a=3;
    function bar(){
        console.log(a);
    }
    return bar;
}
const a=foo()
a()

节流、防抖

//节流 执行第一次,间隔时间到后再执行后续
function throttle(fn, time) {
    var timer;
    return function() {
     if (timer) return;  //如果标记没更新 不执行函数
     timer = setTimeout(() => {
        clearTimeout(timer)
        timer = null;
        fn();
     }, time);
    }
}
//防抖  执行最后一次,间隔时间内触发方法 重新计时
function debounce(fn, time) {
    var timer;
    return function() {
        if (timer) {
            clearTimeout(timer) // 如果第二次执行 还在间隔时间内  清空定时器
            timer = null;

        }
        timer = setTimeout(() => {  //延迟time执行fn
            clearTimeout(timer)
            timer = null;
            fn();
        }, time);
    }
}

原型和原型链

原型就是一个叫做 prototype的对象。 在js中每个数据类型都是一个个对象,即使像number、string这样的简单数据类型也是由Number、String这些构造函数实例出来的。
这些个实例对象本身都有一个__proto__属性,这个属性保存的(一般情况下)是它构造函数原型的引用。
函数也是对象,但它能作为构造函数生成实例对象。它的特殊在于它除了拥有__proto__之外还拥有一个Prototype 这个Prototype就是主角

function Foo(){
}
var foo = new Foo();

// 原型链
foo.__proto__ === Foo.prototype  // 对象的隐式原型 指向其构造函数的 原型
Foo.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ = null 

Event Loop

在js中,任务进入执行栈,先判断任务类型,如果是同步任务,直接进入到主线程执行。如果是异步任务,会把任务放到异步队列,等同步任务执行完以后,事件触发线程会从消息队列中取出刚才加入队列的函数,如果有,就一条一条的去执行。

一个页面从输入 URL 到页面加载显示完成,这个过程中都发生了什么

1、浏览器会开启一个线程来处理这个请求,对 URL 分析判断如果是 http 协议就按照 Web 方式来处理;
2、调用浏览器内核中的对应方法,比如 WebView 中的 loadUrl 方法;
3、通过DNS解析获取网址的IP地址,设置 UA 等信息发出第二个GET请求;
4、进行HTTP协议会话,客户端发送报头(请求报头);
5、进入到web服务器上的 Web Server,如 Apache、Tomcat、Node.JS 等服务器;
6、进入部署好的后端应用,如 PHP、Java、JavaScript、Python 等,找到对应的请求处理;
7、处理结束回馈报头,此处如果浏览器访问过,缓存上有对应资源,会与服务器最后修改时间对比,一致则返回304; 8、浏览器开始下载html文档(响应报头,状态码200),同时使用缓存;
9、文档树建立,根据标记请求所需指定MIME类型的文件(比如css、js),同时设置了cookie;
10、页面开始渲染DOM,JS根据DOM API操作DOM,执行事件绑定等,页面显示完成。

CSS

怎么让一个div水平垂直居中

  1. position + transform
  2. 使用flex
.parent{
    display: flex;
    align-items: center;
    justify-content: center;
}
  1. display:table
.parent{
    display: table;
}
.child{
    display: table-cell;
    vertical-align: middle;
    text-align: center;
}

css实现三角形

div{
   width: 0;
   height: 0;
   border-top: 10px solid transparent;
   border-left: 10px solid transparent;
   border-right: 10px solid transparent;
   border-bottom: 10px solid #000;
}

flex布局

  • display:flex; 在父元素设置,子元素受弹性盒影响,默认排成一行,如果超出一行,按比例压缩
  • flex:1; 子元素设置,设置子元素如何分配父元素的空间,flex:1,子元素宽度占满整个父元素
  • align-items:center 定义子元素在父容器中的对齐方式,center 垂直居中
  • justify-content: center 设置子元素在父元素中居中,前提是子元素没有把父元素占满,让子元素水平居中
  • flex-direction:决定主轴的方向(即项目的排列方向)

flex:1 含义

flex 是 flex-grow、flex-shrink、flex-basis的缩写。

  • flex-grow:属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。
  • flex-shrink: 属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。
  • flex-basis: 属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。

伪元素和伪类

  • 伪类 伪类就是一个选择处于特定状态的元素的选择器 一般用单冒号
    :avtive :disabled :hover :first-child 等等
  • 伪元素 伪元素是创建了一个文档外的元素 一般用双冒号
    ::before ::after

重绘和重排

  • 重排:当渲染树的一部分必须更新并且节点的尺寸发生了变化,浏览器会使渲染树中受到影响的部分失效,并重新构造渲染树
  • 重绘: 是在一个元素的外观被改变所触发的浏览器行为,浏览器会根据元素的新属性重新绘制,使元素呈现新的外观

BFC

  1. BFC里面的box都会以垂直方向排列
  2. 同一个BFC里面中相邻的两个盒子的外边距会重叠
  3. BFC的区域不会和float Box重叠
  4. BFC就是一个独立的容器,容器里面的元素不会影响到外面的元素,反之也如此

VUE

生命周期 https://segmentfault.com/a/1190000019743049

1. beforeCreat() 创建前

实例初始化在这个生命周期遍历 data 对象下所有属性将其转化为 getter/setter, 也就是说添加一个被观察者

2. created()创建

这个生命周期的时候 实例已经被创建完毕 属性已经绑定 属性是可以操作的 但是dom还不存在

3. beforeMount()挂载前

这个生命周期做的是模板编译 el还未对数据进行渲染 还是不能操作dom

4. mounted()挂载

这个生命周期 虚拟dom已经被渲染到了真实的dom

5. beforeupdate()数据更改前 6. updated()数据更改

mounted更新一个属性的话 beforeUpdate 和 updated 生命周期函数 会被触发 但是仅限于 被观察的属性做出的变化且被引用他们才会触发

而且需要注意的是 不要在updated 函数里边直接就修改属性 会进入死循环

7. beforeDestroy()销毁前

这个生命周期 是组件销毁前的时候 还是可以对元素进行操作的

8. destroyed()销毁

销毁这个生命周期执行过后 实例的属性和方法就不能再用了

vue双向绑定的原理是什么

vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。主要分为以下几个步骤:

1、需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化

2、compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图

3、Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是: ①在自身实例化时往属性订阅器(dep)里面添加自己 ②自身必须有一个update()方法 ③待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。

4、MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。

总体流程:

  1. 组件初始化时候 对每个vue属性用Object.defineProperty()实现数据劫持,为每个属性分配一个订阅者集合的管理数组dep
  2. Compile解析模板指令的时候(v-model、{{}}、v-bind)在该属性的数组dep中添加订阅者
  3. Dep是一个订阅器,被observe的数据都有一个Dep实例,Dep实例里面存放了N个订阅者
  4. 数据变化时 触发setter,会遍历观察者列表(dep.subs),通知相关的Watcher
  5. Watcher 订阅者会接收到来自dep的通知,执行自己的update()

nexttick原理

  1. 修改数据,这是同步任务。同一事件循环的所有的同步任务都在主线程上执行,形成一个执行栈,此时还未涉及 DOM
  2. Vue 开启一个异步队列,并缓冲在此事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次
  3. 同步任务执行完毕,开始执行异步 watcher 队列的任务,更新 DOM

data()为什么是函数

组件中的data写成一个函数,data数据以函数返回值形式定义,这样每复用一次组件,就会生成一个新的data对象,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份data,就会造成一个变了全都会变的结果。

keep-alive

缓存实现原理
patch阶段,会执行createComponent函数

  1. 首次访问时,会把组件缓存到this.cache里
  2. 再次访问被包裹组件时,vnode.componentInstance的值就是已经缓存的组件实例,这样就直接把上一次的DOM插入到父元素中

虚拟DOM

  • 左边:DOM
  • 右边:虚拟DOM

image.png

Diff算法

  • 只比较同一层级,不跨级比较
  • tag不相同,则直接删掉重建不再深度比较
  • tag和key,两者都相同,则认为是相同节点,不再深度比较

image.png

组件渲染过程

  1. generate(将AST语法树转化成render function字符串的过程)得到render函数
  2. 执行render函数,生成vnode(即虚拟DOM,VNode理解为虚拟DOM的总节点)

image.png

Computed实现原理 https://segmentfault.com/a/1190000010408657

  • data 属性初始化 getter setter
  • computed 计算属性初始化,提供的函数将用作属性 vm.reversedMessage 的 getter
  • 当首次获取 reversedMessage 计算属性的值时,Dep 开始依赖收集
  • 在执行 message getter 方法时,如果 Dep 处于依赖收集状态,则判定 message 为 reversedMessage 的依赖,并建立依赖关系
  • 当 message 发生变化时,根据依赖关系,触发 reverseMessage 的重新计算

父子、兄弟组件传值

  • props
  • vue.$emit / vue.$on
  • vuex

VUEX

store是一个仓库,仓库保存着项目的数据(store.state);
为了解决无法追踪状态变化的问题,仓库里的数据不直接通过修改store.state来改变,而是通过commit一个动作告诉仓库,直接修改将会报错 [vuex] Do not mutate vuex store state outside mutation handlers
仓库收到了commit的动作后,将会在store.mutation里查找对应事件,并改变store.state

this.$store.commit(mutation) 是更改状态的唯一方法,并且这个过程是同步的 异步逻辑都应该封装到 action 里面 调用时候dispatch(action) 异步完成后回调里再commmit mutation

vue-router

import router from './router'
// 路由守卫
router.beforeEach((to, from, next)=>{
  if(window.sessionStorage.userData){   // 判断是否登录   
    if(to.path === '/' || to.path === '/login'){  //  
      //登录状态下 访问login.vue页面 会跳到index.vue
      next({path: '/index'});
    }else{
      next();
    }
  }else{
    // 如果没有session ,访问任何页面。都会进入到 登录页
    if (to.path === '/') { // 如果是登录页面的话,直接next() -->解决注销后的循环执行bug
      next();
    } else { // 否则 跳转到登录页面
      next({ path: '/' });
    }
  }
})

webpack vueCli

webpack构建原理

Webpack在启动后,会从Entry开始,递归解析Entry依赖的所有Module,每找到一个Module,就会根据Module.rules里配置的Loader规则进行相应的转换处理,对Module进行转换后,再解析出当前Module依赖的Module,这些Module会以Entry为单位进行分组,即为一个Chunk。因此一个Chunk,就是一个Entry及其所有依赖的Module合并的结果。最后Webpack会将所有的Chunk转换成文件输出Output。在整个构建流程中,Webpack会在恰当的时机执行Plugin里定义的逻辑,从而完成Plugin插件的优化任务。

常用loader

  • style-loader : 用于将css编译完成的样式,挂载到页面style标签上。需要注意loader执行顺序,style-loader放到第一位,因为loader都是从下往上执行,最后全部编译完成挂载到style上
  • css-loader : 用于识别.css文件, 处理css必须配合style-loader共同使用,只安装css-loader样式不会生效。
  • postcss-loader: 打包css样式,自动添加前缀
  • file-loader / url-loader: 图片打包使用的是url-loader。可以设置当打包图片小于某个值时打包成base64。
  • babel-loader @babel/core @babel/preset-env es6+ 转 es5

loader plugin

juejin.cn/post/694346…

HTTP

HTTP和TCP

  • TCP 建立起一个TCP连接需要经过“三次握手”,断开过程需要经过“四次挥手”
  • HTTP 协议是建立在TCP协议之上的一种应用 请求结束后,会主动释放连接

三握四挥

浏览器缓存

强缓存: Expires、Cache-Control
协商缓存:Last-Modified / If-Modified-Since 和 ETag / If-None-Match

  1. Expires
    Expires: Thu, 10 Dec 2015 23:21:37 GMT 在此日期之前,客户端都会认为缓存是有效的

  2. Cache-Control Cache-Control: max-age=3600 在一段时间(3600秒)内缓存

  3. Last-Modified / If-Modified-Since
    Last-Modified: Mon, 30 Nov 2015 23:21:37 GMT 客户端收到资源最后一次修改时间
    If-Modified-Since: Mon, 30 Nov 2015 23:21:37 GMT 下次请求时候 带上时间 服务端判断是否缓存 304

  4. ETag / If-None-Match
    Etag是个标记 请求时候通过If-None-Match带上标记 服务端判断标记是否修改

HTTP状态码

  • 200 - 请求成功
  • 301 - 资源(网页等)被永久转移到其它URL
  • 404 - 请求的资源(网页等)不存在
  • 500 - 内部服务器错误 1** 信息,服务器收到请求,需要请求者继续执行操作
    2** 成功,操作被成功接收并处理
    3** 重定向,需要进一步的操作以完成请求
    4** 客户端错误,请求包含语法错误或无法完成请求
    5** 服务器错误,服务器在处理请求的过程中发生了错误

HTTP和HTTPS

HTTPS和HTTP的区别主要如下:

  1. https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
  2. http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
  3. http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
  4. http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

HTTPS

  1. 客户使用https的URL访问Web服务器,要求与Web服务器建立SSL连接。
  2. Web服务器收到客户端请求后,会将网站的证书信息(证书中包含公钥)传送一份给客户端。
  3. 客户端的浏览器与Web服务器开始协商SSL连接的安全等级,也就是信息加密的等级。
  4. 客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站。
  5. Web服务器利用自己的私钥解密出会话密钥。
  6. Web服务器利用会话密钥加密与客户端之间的通信。

OSI 七层/五层 网络协议

image.png