一些忘了的东西。。。

复习

函数的原型对象上面有一个constructor指回person函数

面向对象有三大特性:封装、继承、多态
/* 多态:当对不同数据类型进行同一个操作但是表现出来的形式不同就是多态的体现 */
class类定义的属性都是直接放到了this上,也就意味着它是放到了创建出来的新对象中:
在前面我们说过对于实例的方法是放到原型上的

class Person{
    //属性放在了this上面
    constructor(name,age) {
        this.name = name;
        this.age = age;
        this.address='广州市'
    }
    /* 普通方法 */
    /* 方法被放到了Person的原型对象上,不会多次被创建 */
    eating () {
        console.log(this.name+'吃饭');
    }
    //类的访问器方法
    get address() {
        console.log('拦截访问操作');
        return this._address
    }
    set address(newaddress) {
        /* console.log('拦截设置操作'); */
        this._address = newaddress;
    }
}
上面的代码就相当于下面Person构造函数的代码
// function Person(name, age) {
//     this.name = name;
//     this.age = age;
//     this.address='广州市'
// }
// Person.prototype.eating =function() {
//     console.log(this.name+'吃饭');
// }
var p1 = new Person('wyh', 18);//new 这个类就是调用了consuructor并执行new的四部曲
p1.eating();
console.log(Person.__proto__ === Function.prototype);//类本质就是构造函数的语法糖

原型链:
我们在查找属性时,如果自身没有就去自己的__proto__上去找,而不是在prototype上去找
注意:Object.__proto__是Function.prototype
Function原型图解
一些忘了的东西。。。_第1张图片

继承

1.直接继承
Son.prototype = Father.prototype
这样会污染父级的prototype
2.原型链继承
Son.prototype = new Father()
无法传参
3.借助构造函数继承

function Son(name, age) {
    /* 把name, age参数传入Father并执行 */
    Father.call(this, name, age);
    this.names = 'son'
}

Father被调用了两次
4.原型式继承
es5

function creatobject(o) {
    function Fn() { };
    Fn.prototype = o;
    return new Fn();
}

es6
Object.create()
5.寄生式继承
就是给原型式继承套了个壳子

var obj = {
    running: function () {
        console.log('running');
    }
}
//寄生式继承就是给原型式继承套了个壳子
function creatStudent(obj) {
    var stu = Object.create(obj);//原型式继承
    //增强这个对象然后返回
    stu.study = function () {
        console.log("studying");
    }
    stu.name='123'
    return stu
}
var p1 = creatStudent(obj);

6.寄生组合式继承

function Inhert(son, father) {
    //寄生,解决了修改子类原型父类原型也会被修改
    son.prototype = Object.create(father.prototype)
}
function Person(name, age) {
    this.name = name;
    this.age = age
}
Person.prototype.eat = function () {
    console.log('eating');
}
function Son(name, age) {
    //组合,解决了父类构造函数调用两次
    Person.call(name, age)
}
Inhert(Son, Person)
let p = new Son('wyh', 123)
console.log(p.eat());

强引用和弱引用:

简单来说:强引用的不会被垃圾回收机制回收了,但是 弱引用可是会回收的 ;弱引用并不会屏蔽 垃圾回收机制

Proxy:

const obj = {
    name: 'wyh',
    age:18
}
/* 在ES6中,新增了一个Proxy类,这个类从名字就可以看出来,是用于帮助我们创建一个代理的:
也就是说,如果我们希望监听一个对象的相关操作,那么我们可以先创建一个代理对象(Proxy对象);
 之后对该对象的所有操作,都通过代理对象来完成,代理对象可以监听我们想要对原对象进行哪些操作 */
/* const objProxy = new Proxy(obj, {});
console.log(objProxy.name);//wyh
objProxy.name = 'kobe';
console.log(obj.name);//kobe */
/* 我们可以重写Proxy里面的捕获器 */
const objProxy = new Proxy(obj, {
    //获取值时的捕获器,target是目标对象obj,key是被设置的属性
    get: function (target, key) {
        console.log(`监听到${key}属性被访问值`);
        console.log(target);
        console.log(key);
        return target[key];
    },
    //设置值的捕获器,
    set: function (target, key, newvalue) {
        console.log(`监听到${key}属性被设置值`);
        target[key] = newvalue;
    }
})
console.log(objProxy.name);
objProxy.name = 'aaa';

Reflect
它主要提供了很多操作JavaScript对象的方法,有点像Object中操作对象的方法;
这是因为在早期的ECMA规范中没有考虑到这种对 对象本身 的操作如何设计会更加规范,所以将这些API放到了Object上面;
但是Object作为一个构造函数,这些操作实际上放到它身上并不合适;
Reflect.get(target, propertyKey[, receiver])
获取对象身上某个属性的值,类似于 target[name]。 n Reflect.set(target, propertyKey, value[, receiver])
将值分配给属性的函数。返回一个Boolean,如果更新成功,则返回true。

Proxy结合Reflect

const obj = {
    name: 'wyh',
    age:18
}
const objproxy = new Proxy(obj, {
    get: function (target, key) {
        /* 参数与proxy参数意义一样 */
        return Reflect.get(target,key)
    },
    set: function (target, key,newval) {
        return Reflect.set(target,key,newval)
    }
})
objproxy.name = 'w';
console.log(objproxy.name);

Vue2响应式原理核心:Object.defineProperty()
Vue3响应式原理核心:new Proxy()

Pomise:
resolve里可以传promise,如果传入的是promise,那么当前的promise的状态会由传入的promise决定
如果resolve传入的是一个对象,并且该对象实现了then方法,那么也会执行该方法,并且后续状态由该方法确定
同一个promise可以多次调用then方法,且都会执行

then方法本身就返回一个promise,所以.then方法可以被多次链式调用,但是第二次调用then方法不是原来的promise,而是第一次.then方法返回的promise

const obj = {
    name: 'wyh',
    age:18
}
const promise = Promise.resolve(obj)
console.log(promise);
/* 相当于const promise1 = new Promise((res, rej) => {
    res(obj)
})
promise1.then(res => {
    console.log(res);
}) */

//reject同理
 

‘30’%8=6
6是数字不是字符串,在字符串与数字运算时,除了+号会拼接字符串,其余符号会将字符串隐式转换成数字运算
![]结果是false
一些忘了的东西。。。_第2张图片
对于任何对象,即使是值为 false 的 Boolean 对象,当将其传给 Boolean 函数时,生成的 Boolean 对象的值都是 true

let myFalse = new Boolean(false);   // false
let g = new Boolean(myFalse);       // true
let myString = new String("Hello");
let s = new Boolean(myString);      // true

js中函数是一等公民,所以预编译时会被提到最前面。

      function a(){
          var a=2;
          function a(){
              console.log(1);
          }
          return a
          function a(){
              console.log(3);
          }
      }
   console.log(a());//2

typeof
一些忘了的东西。。。_第3张图片
typeof的返回值是字符串
例:typeof typeof typeof null =’String‘
async做了一件什么事情?
async将你的函数返回值转换为promise对象,不需要显式地返回promise对象,async关键字自动将函数的返回值变为promise对象。
浏览器的事件循环
浏览器的事件循环维护着两个任务队列
事件循环会不断的从任务队列中取出对应的事件(回调函数)来执行;
宏任务队列:ajax、setTimeout、setInterval、DOM监听、UI Rendering等
微任务队列:Promise的then回调queueMicrotask()、 process.nextTick,Mutation Observer API等
那么事件循环对于两个队列的优先级是怎么样的呢?
1.main script中的代码优先执行(编写的顶层script代码);
2.在执行任何一个宏任务之前(不是队列,是一个宏任务),都会先查看微任务队列中是否有任务需要执行
也就是宏任务执行之前,必须保证微任务队列是空的;
如果不为空,那么就优先执行微任务队列中的任务(回调);
例:
先打印1,然后执行await的函数,再打印2,再打印3

async function a(){
    console.log(1);
    await 函数2
    console.log(3);
}a()
console.log(2);

setTimeout后面带时间的话要看执行速度了,下面代码先打印了28个2后打印1然后在一直打印2
例:

setTimeout(() => {
    console.log(1);
},300)
setInterval(() => {
    console.log(2);
})

什么是模块化开发呢?
事实上模块化开发最终的目的是将程序划分成一个个小的结构;
这个结构中编写属于自己的逻辑代码,有自己的作用域,不会影响到其他结构;
这个结构可以将自己希望暴露的变量、函数、对象等导出给其结构使用;
也可以通过某种方式,导入另外结构中的变量、函数、对象等
Commonjs:
exportsmodule.exports可以负责对模块中的内容进行导出;
require函数可以帮助我们导入其他模块(自定义模块、系统模块、第三方库模块)中的内容;
模块第一次被引用(require),被引用的模块里的代码就会被执行一次
并且先执行里面的代码,在第五行代码执行完之前,后面的代码都不会执行,commonjs加载过程是同步的
require做的就是把module.exports对象存的内存地址拿到并作为返回值,不是exports对象的内存地址
node中有一个类Module,每一个js文件都是他的实例,node源码写了module.exports=exports,
这一句代码是在最开始的,他俩指向的是同一个对象,如果给module.exports赋值一个新对象,后面改exports不会影响module.exports,
因为本质是module.exports在导出,而这时候module.exports也不指向原来那个对象了
a.js

module.exports = {
    name: 'qqq',
    age: 222,
}
exports.age=1111

b.js

const {age}= require('./foo');
console.log(age);//1111

模块在被第一次引入时,模块中的js代码会被运行一次
模块被多次引入时,会缓存,最终只加载(运行)一次

CommonJS规范缺点:
CommonJS加载模块是同步的,会阻塞后面代码加载。
ES Module
ES Module 加载js文件是在编译时加载,并且是异步的。
编译时时加载,意味着import不能和运行时相关的内容放在一起使用:
使用es-module要在script加上type=“module”

<!-- 使用ES-module会默认开启严格模式 -->
<!-- type="module"表示模块化 -->
<script src="./index.js" type="module"></script>
<!-- 因为上面的js文件加载是异步的,所以不会阻塞后面的js代码  -->
<script src="./normal.js"></script>

导入方式:

//导入方式一:普通导出
/* import { a, name } from './module/b.js'; */
//导入方式二:导出变量起别名
/* import { names as name, aa as a } from './module/b.js'; */
//导入方式三:
//wyh作为一个对象将导出的变量作为他的属性
//import 不能放到js逻辑代码里面,除非写成函数
import * as wyh from './module/b.js';
import abcd from './module/b.js';
console.log(wyh.name, wyh.a);
console.log(abcd);
let flag = true;
if (flag) {
    //它的返回值是一个promise
    import('./module/b.js').then((res) => {
    console.log(res);
    }).catch(() => {
    console.log('error');
})
}

导出方式:

const name = 'code';
function a() {
    console.log(name);
}
//导出方式一:
/* export const name = 'wyh';
export function a() {
    console.log(name);
} */

//导出方式二:在{}统一导出
/* 注意{}大括号不是对象,{放置要导出变量的引用} */
export {
name,
a
}
//导出方式三:{}导出时,可以给变量起别名
/* export {
    name as name1,
    a as  a1
} */

//默认导出,不用指定名字,可以在导入时自己指定名字,每个文件里面只能有一个默认导出
export default function foo() {
    console.log('foo');
}

Cookie
Cookie可以分为内存Cookie和硬盘Cookie
内存Cookie由浏览器维护,保存在内存中,浏览器关闭时Cookie就会消失,其存在时间是短暂的;
硬盘Cookie保存在硬盘中,有一个过期时间,用户手动清理或者过期时间到时,才会被清理;
document.cookie不能获得服务器设置的cookie,如果document.cookie=‘age=18’,
这样表示加了一个age:18的cookie,
想要删除服务器的cookie,那么设置document.cookie='服务器同名的cookie;max-age=0’前提是http-only:true直接让他过期就删除了
子域和路径不同,如果原域名是abc.com,那么子域是xxx.abc.com,路径path是abc/music.com

cookie和session的缺点:
1.不是每个请求都需要携带cookie,浪费用户流量
2.cookie使用明文传输,不安全
3.大小有限制,4kb以内,对于复杂的需求不方便转递
//最大的两个弊端
4.对于浏览器外的其他客户端,必须手动的设置cookie和session
5.对于分布式系统和服务器集群中如何保证其他系统也可以正确的解析session
服务器集群有多个服务器接收请求,当a服务器发下去一个session,当用户带着这个session请求时,如果这时请求的是b服务器那么怎么正确解析

事件对象
target:返回触发事件的元素;
currentTarget:返回绑定事件的元素
addEventListener第三个参数默认是false,表示采用事件冒泡,改为true表示采用事件捕获

回流与重绘:
重绘(repaint 或 redraw):当盒子的位置、大小以及其他属性,例如颜色、字体大小等 都确定下来之后,浏览器便把这些原色都按照各自的特性绘制一遍,将内容呈现在页面 上。重绘是指一个元素外观的改变所触发的浏览器行为,浏览器会根据元素的新属性重 新绘制,使元素呈现新的外观。 触发重绘的条件:改变元素外观属性。如:color,background-color 等
重排必定会引发重绘,但重绘不一定会引发重排
回流(重构/回流/reflow):当渲染树中的一部分(或全部)因为元素的规模尺寸,布局, 隐藏等改变而需要重新构建, 这就称为回流(reflow)。每个页面至少需要一次回流,就是 在页面第一次加载的时候
两者的关系:回流必定导致重绘,但重绘不一定导致回流

浏览器渲染步骤:
解析HTML,生成DOM树,解析CSS,生成CSSOM树
将DOM树和CSSOM树结合,生成渲染树(Render Tree)
Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小)
Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素
Display:将像素发送给GPU,展示在页面上。

防抖与节流:
防抖:当事件触发时,相应的函数并不会立即触发,而是会等待一定的时间
防抖的应用场景:输入框中频繁的输入内容,搜索或者提交信息

节流:如果这个事件会被频繁触发,那么节流函数会按照一定的频率来执行函数
节流的应用场景:用户频繁点击按钮操作;

Object.entries()

const obj = {
    name: 'wyh',
    age:18
}
/* 每一个键值对都是通过数组包裹 */
console.log(Object.entries(obj));//[ [ 'name', 'wyh' ], [ 'age', 18 ] ]

Object.keys()返回的是一个数组

const obj={
    name:'wyh',
    sge:'18'
}

console.log(Object.keys(obj));//['name', 'sge']

对象遍历用for (const key in 对象)或者Object.keys(对象)Object.values(对象)
数组遍历用for(var index in 数组){}或者 数组.forEach((value,index,array)=>{})或者for (const index in 数组)

//forEach() 遍历对象类型数组
const info = [
  {id: 1000, name: 'zhangsan'},
  {id: 1001, name: 'lisi'},
  {id: 1002, name: 'wangwu'}
]
info.forEach( function(item) {
  console.log(item.id + '---' + item.name);
})

for of不能遍历对象,遍历数组就是简化的for循环

var arr=[11,22,33]
for(var key of arr){
    console.log(key);//11,22,33
}

Vue生命周期:
Vue 实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模板、挂载 Dom、渲染→更新→渲染、销毁等一系列过程,我们称这是 Vue 的生命周期。通俗说就 是 Vue 实例从创建到销毁的过程,就是生命周期。 每一个组件或者实例都会经历一个完整的生命周期,总共分为三个阶段:初始化、运行 中、销毁。 实例、组件通过 new Vue() 创建出来之后会初始化事件和生命周期,然后就会执行 beforeCreate 钩子函数,这个时候,数据还没有挂载呢,只是一个空壳,无法访问到数据 和真实的 dom,一般不做操作 挂载数据,绑定事件等等,然后执行 created 函数,这个时候已经可以使用到数据,也 可以更改数据,在这里更改数据不会触发 updated 函数,在这里可以在渲染前倒数第二次 更改数据的机会,不会触发其他的钩子函数,一般可以在这里做初始数据的获取 接下来开始找实例或者组件对应的模板,编译模板为虚拟 dom 放入到 render 函数中准备 渲染,然后执行 beforeMount 钩子函数,在这个函数中虚拟 dom 已经创建完成,马上就 要渲染,在这里也可以更改数据,不会触发 updated,在这里可以在渲染前最后一次更改 数据的机会,不会触发其他的钩子函数,一般可以在这里做初始数据的获取 接下来开始 render,渲染出真实 dom,然后执行 mounted 钩子函数,此时,组件已经出 现在页面中,数据、真实 dom 都已经处理好了,事件都已经挂载好了,可以在这里操作 真实 dom 等事情… 当组件或实例的数据更改之后,会立即执行 beforeUpdate然后 Vue 的虚拟 dom 机制会 重新构建虚拟 dom 与上一次的虚拟 dom 树利用 diff 算法进行对比之后重新渲染,一般不 做什么事儿 当更新完成后,执行 updated,数据已经更改完成,dom 也重新 render 完成,可以操作 更新后的虚拟 dom 当经过某种途径调用$destroy 方法后,立即执行 beforeDestroy,一般在这里做一些善后工 作,例如清除计时器、清除非指令绑定的事件等等 组件的数据绑定、监听…去掉后只剩下 dom 空壳,这个时候,执行 destroyed,在这里做 善后工作也可以。

  beforeCreate() {
                console.log(this.msg);
                //这是我们遇到的第一个生命周期函数,表示实例完全被创建出来之前,会执行它
                //注意:在beforeCreate执行的时候,data和methods中的数据还没有初始化
            },
            //*重要
            created() {
                //这是第二个生命周期函数
                //在created中,data和methods已经被创建好了
                console.log(this.msg);
            },
            beforeMount() {
                //这是第三个生命周期函数,表示模板已经在内存中编辑完成,但是尚未渲染到页面中去
                console.log(document.querySelector('p').innerText);
            },
            //*重要
            mounted() {
                //这是第四个生命周期函数,表示内存中的模板已经被真实的挂载到了页面中
                //这个函数执行完成后就表示整个vue实例被创建完成,开始进行运行阶段
                console.log(document.querySelector('p').innerText);
            },
            //运行函数
            beforeUpdate() {
                //这个函数发生的时机是data被更新后执行的
                //beforeUpdate执行时输出的数据是未更新前的数据,此时data数据是最新的
                console.log(document.querySelector('p').innerText);
                console.log(this.msg);
            },
            updated() {
                //update执行时输出的数据是更新后的数据,此时页面与data中的数据已经保持同步了
                console.log(document.querySelector('p').innerText);
                console.log(this.msg);
            },
            //销毁阶段
            /* 当执行 beforeDestroy钩子函数的时候, Vue实例就已经从运行阶段,进入到了销毁阶段;
            当执行beforeDestroy 的时候,实例身上所有的data和所有的methods,
            以及过滤器、指....都处于可用状态,此时,还没有真正执行销毁的过程*/
            beforeDestroy() { },
            /* 当执行到destroyed函数的时候,组件已经被完全销毁了,此时,
            组件中所有的数据、方法、指令、过滤器.... 都已经不可用*/
            Destroyed() { }

父子组件生命周期顺序:
加载渲染过程
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted。

更新过程
父beforeUpdate->子beforeUpdate->子updated->父updated
销毁过程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

rem,em
当使用 rem 单位,他们转化为像素大小取决于页根元素的字体大小,即 html 元素的字体大小。例如,根元素的字体大小为 16px,那么 10rem 就等同于 1016=160px
在这里插入图片描述
当使用 em 单位的时候,像素值是将 em 值乘以使用 em 单位的元素的字体大小。例如一 个 div 的字体为 18px,设置它的宽高为 10em,那么此时宽高就是 18px
10em=180px。
.test{width: 10em; height: 10em; background-color: #ff7d42; font-size: 18px; }一定要记住的是,em 是根据使用它的元素的 font-size 的大小来变化的,而不是根据父 元素字体大小。有些元素大小是父元素的多少倍那是因为继承了父元素中 font-size 的设 定,所以才起到的作用。
click 的 300ms 延迟问题(双击放大问题)
FastClick是 FT Labs 专门为解决移动端浏览器 300 毫秒点击延迟问题所开发的一个 轻量级的库

前端工程化

Babel 的原理是什么?
babel 的转译过程也分为三个阶段,这三步具体是: 解析 Parse: 将代码解析生成抽象语法树( 即 AST ),即词法分析与语法分 析的过程
转换 Transform: 对于 AST 进行变换一系列的操作,babel 接受得到 AST 并通过 babel-traverse 对其进行遍历,在此过程中进行添加、更新 及移除等操作
生成 Generate: 将变换后的 AST 再转换为 JS 代码, 使用到的模块是 babel-generato
webpack
webpack 是一个前端模块化方案,更侧重模块打包,我们可以把开发中的所有资源(图 片、js 文件、css 文件等)都看成模块,通过 loader(加载器)和 plugins(插件)对资源 进行处理,打包成符合生产环境部署的前端资源。
watch 和计算属性有什么区别?
通俗来讲,既能用 computed 实现又可以用 watch 监听来实现的功能,推荐用 computed, 重点在于 computed 的缓存功能 computed 计算属性是用来声明式的描述一个值依赖了其它的值,当所依赖的值或者变量 改变时,计算属性也会跟着改变; watch 监听的是已经在 data 中定义的变量,当该变量变化时,会触发 watch 中的方法。
watch:监测的是属性值, 只要属性值发生变化,其都会触发执行回调函数来执行一系列操作。里面函数的参数是newvalue和oldvalue,比如搜索,可以监听用户输入然后在watch发请求查询。
computed:监测的是依赖值,依赖值不变的情况下其会直接读取缓存进行复用,变化的情况下才会重新计算。里面有get和set方法

计算属性不能执行异步任务,计算属性必须同步执行。也就是说计算
属性不能向服务器请求或者执行异步任务
。如果遇到异步任务,就交给侦听属性。
侦听器如果想监听对象内部属性的改变,需配置deep属性

 data() {
        return {
          info: { name: "why", age: 18, nba: {name: 'kobe'} }
        }
      },
      watch: {
        // 默认情况下我们的侦听器只会针对监听的数据本身的改变(内部发生的改变是不能侦听)
        // info(newInfo, oldInfo) {
        //   console.log("newValue:", newInfo, "oldValue:", oldInfo);
        // }

        // 深度侦听/立即执行(一定会执行一次)
        info: {
          handler: function(newInfo, oldInfo) {
            console.log("newValue:", newInfo.nba.name, "oldValue:", oldInfo.nba.name);
          },
          deep: true, // 深度侦听
          // immediate: true // 立即执行
        }
      },

Vue 双向绑定原理
Vue 数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的。利用了 Object.defineProperty() 这个方法重新定义了对象获取属性值(get)和设置属性值(set)。
vue 在 created 和 mounted 这两个生命周期中请求数据有什么区别
看实际情况,一般在 created(或 beforeRouter) 里面就可以,如果涉及到需要页面加载 完成之后的话就用 mounted。 在 created 的时候,视图中的 html 并没有渲染出来,所以此时如果直接去操作 html 的 dom 节点,一定找不到相关的元素 而在 mounted 中,由于此时 html 已经渲染出来了,所以可以直接操作 dom 节点,(此 时 document.getelementById 即可生效了)。
什么是 WebSocket?
WebSocket 是 HTML5 中的协议,支持持久连续,http 协议不支持持久性连接。
状态码:
100 Continue 继续。客户端应继续其请求
301与302
详细来说,301和302状态码都表示重定向,就是说浏览器在拿到服务器返回的这个状态码后会自动跳转到一个新的URL地址,这个地址可以从响应的Location首部中获取(用户看到的效果就是他输入的地址A瞬间变成了另一个地址B)——这是它们的共同点。他们的不同在于。301表示旧地址A的资源已经被永久地移除了(这个资源不可访问了),搜索引擎在抓取新内容的同时也将旧的网址交换为重定向之后的网址;302表示旧地址A的资源还在(仍然可以访问),这个重定向只是临时地从旧地址A跳转到地址B,搜索引擎会抓取新的内容而保存旧的网址。
使用301跳转的场景:
1)域名到期不想续费(或者发现了更适合网站的域名),想换个域名。
2)在搜索引擎的搜索结果中出现了不带www的域名,而带www的域名却没有收录,这个时候可以用301重定向来告诉搜索引擎我们目标的域名是哪一个。
3)空间服务器不稳定,换空间的时候。
304 是对客户端有缓存状况下服务端的一种响应。
第一次返回200
一些忘了的东西。。。_第4张图片
第二次F5刷新访问 304
一些忘了的东西。。。_第5张图片

(1)400 状态码:请求无效
前端提交数据的字段名称和字段类型与后台的实体没有保持一致 前端提交到后台的数据应该是 json 字符串类型,但是前端没有将对象 JSON.stringify 转 化成字符串
(2)401 状态码:当前请求需要用户验证
(3)403 状态码:服务器已经得到请求,但是拒绝执行

401和403的区别

401
状态码401标识认证失败,表示请求没有被认证或者认证失败。
通常由web服务器返回,而不是web应用。
场景:token失效、token缺失、token伪造,导致服务端无法识别身份。
403
状态码403表示授权失败,通常表示用户通过了身份验证,但缺少权限对给定的资源进行访问或者操作。
通常由web应用返回。
场景:用户登录成功,但是无权进行读写操作。

Cookie、sessionStorage、localStorage 的区别
共同点:都是保存在浏览器端,并且是同源的
Cookie:cookie 数据始终在同源的 http 请求中携带(即使不需要),即 cookie 在浏览器 和服务器间来回传递。而 sessionStorage 和 localStorage 不会自动把数据发给服务器,仅 在本地保存。cookie 数据还有路径(path)的概念,可以限制 cookie 只属于某个路径下, 存储的大小很小只有 4K 左右。 (key:可以在浏览器和服务器端来回传递,存储容量 小,只有大约 4K 左右) sessionStorage:仅在当前浏览器窗口关闭前有效,自然也就不可能持久保持,localStorage: 始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据
GET 和 POST 的区别
get 参数通过 url 传递,post 放在 request body 中。
get 请求在 url 中传递的参数是有长度限制的,而 post 没有。
get 比 post 更不安全,因为参数直接暴露在 url 中,所以不能用来传递敏感信息。
get 请求只能进行 url 编码,而 post 支持多种编码方式
GET 产生一个 TCP 数据包;POST 产生两个 TCP 数据包。
对于 GET 方式的请求,浏览器会把 http header 和 data 一并发送出去,服务器响应 200 (返回数据); 而对于 POST,浏览器先发送 header,服务器响应 100 continue,浏览器再发送 data,服 务器响应 200 ok(返回数据)。

csrf和xss
CSRF:跨站请求伪造,可以理解为攻击者盗用了用户的身份,以用户的名义发送了恶 意请求
防御方式的话:使用验 证码,检查 https 头部的 refer,使用 token
XSS:跨站脚本攻击,是说攻击者通过注入恶意的脚本,在用户浏览网页的时候进行攻 击,比如获取 cookie,或者其他用户身份信息
防御的话为 cookie 设置 httpOnly 属性,对用户的输入进行检查,进行特殊字符过滤。

csrf和xss具体流程及防护措施

csrf:
1、用户C打开浏览器,访问安全网站A,输入用户名和密码请求登录网站A.
2、在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录A成功,可以正常发送请求到网站A。
3、比如你的一个网站中有个 img 标签,src 指向的是微博关注某人的接口,
那么当用户访问你的网站时,就会在微博上关注那个人,而且这个操作用户是不知情的。
因为 img src 发出的跨域请求,也是会携带 cookie 的,所以如果用户在微博登录过,
那么就会带有微博的登录授权。同理,如果是其他操作,可能也存在这种漏洞,比较危险的情况就是付款。(当浏览器发送请求且浏览器存在 Cookie 时,浏览器会自动在请求头携带上 Cookie 数据)比如修改用户密码的接口(同源策略是浏览器拦截的,但是接口已经执行了
xss:分为反射型和存储型,反射型XSS的脚本被解析的地方是浏览器,而存储型XSS的脚本被解析的地方是服务器,一般情况下,反射型XSS在搜索框啊,或者是页面跳转啊这些地方,而存储型XSS一般是留言,或者用户存储的地方。反射型一般只会执行一次,非持久,而存储型是将代码存到数据库中,每次进行都会执行一次,是持久化的。

cookie的安全策略

Secure属性
Secure Cookie机制指的是设置了secure标志的cookie。Secure Cookie仅在https层面上安全传输,如果是http请求,就不会带上这个cookie。
这样能降低重要的cookie被中间人截获的风险。
不过,也不是说可以万无一失。因为secure cookie对于客户端脚本来说是可读可写的,可读就意味着secure cookie能被盗取,可写意味着能被篡改,所以还是存在一定的风险。
Cookie的HttpOnly属性,指浏览器不要在除HTTP(和 HTTPS)请求之外暴露Cookie。
HttpOnly属性
一个有HttpOnly属性的Cookie,不能通过非HTTP方式来访问,例如通过调用JavaScript(例如,引用document.cookie),因此,不可能通过跨域脚本(一种非常普通的攻击技术)来偷走这种Cookie。Facebook 和 Google 正在广泛地使用HttpOnly属性。
Same-Site属性
当用户从http://a.com发起http://b.com的请求也会携带上Cookie,而从http://a.com携带过来的Cookie称为第三方Cookie。
为了防止CSRF(Cross-site request forgrey)攻击,可以使用SameSite属性。

行内元素在垂直方向的 padding 和 margin 会失效
visibility=hidden, opacity=0,display:none区别
参考回答: opacity=0,该元素隐藏起来了,但不会改变页面布局,并且,如果该元素已经绑定一些 事件,如 click 事件,那么点击该区域,也能触发点击事件的
visibility=hidden,该元素 隐藏起来了,但不会改变页面布局,但是不会触发该元素已经绑定的事件
display=none, 把元素隐藏起来,并且会改变页面布局,可以理解成在页面中把该元素删除掉一样
浮动清除
方法一:使用带 clear 属性的空元素 在浮动元素后使用一个空元素如< div class=“clear”>,并在 CSS 中赋 予.clear{clear:both;}
方法二:使用 CSS 的 overflow 属性 给浮动元素的容器添加 overflow:hidden
方法三:使用 CSS 的:after 伪元素

块级元素不会继承父标签宽高
background-color 设置的背景颜色会填充元素的 content、padding、border 区域。
闭包就是能够读取其他函数内部变量的函数
mouseenter:当鼠标移除元素本身(不包含元素的子元素)会触发事件,也就是不会冒 泡,对应的移除事件是 mouseleave
new操作
new 操作符新建了一个空对象,这个对象原型指向构造函数的 prototype,执行构造函数 后返回这个对象
垃圾清除
一些忘了的东西。。。_第6张图片
一些忘了的东西。。。_第7张图片

异步加载js用async和defer
常用的数组方法有哪些?
concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。
find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined 。 findIndex() 方法返回数组中满足提供的测试函数的第一个元素的索引。否则返回-1。 includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,
否则返回false。 indexOf() 方法返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。
(通常用它判断数组中有没有这个元素)
join() 方法将一个数组(或一个类数组对象)的所有元素连接成一个字符串并返回这个字符串。
如果数组只有一个项目,那么将返回该项目而不使用分隔符。
pop() 方法从数组中删除最后一个元素,并返回该元素的值。此方法更改数组的长度。
push() 方法将一个或多个元素添加到数组的末尾,并返回该数组的新长度。
shift() 方法从数组中删除第一个元素,并返回该元素的值。此方法更改数组的长度。
unshift() 方法将一个或多个元素添加到数组的开头,并返回该数组的新长度(该方法修改原有数组)。
splice() 方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内
容。此方法会改变原数组。
由被删除的元素组成的一个数组。如果只删除了一个元素,则返回只包含一个元素的数组。如果没有删
除元素,则返回空数组。
reverse() 方法将数组中元素的位置颠倒,并返回该数组。该方法会改变原数组。
sort() 方法用原地算法对数组的元素进行排序,并返回数组。默认排序顺序是在将元素转换为字符串,然后比较它们的Unicode代码单元值序列时构建的
every() 方法测试一个数组内的所有元素是否都能通过某个指定函数的测试。它返回一个布尔
值。
filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。
forEach() 方法对数组的每个元素执行一次提供的函数。
some() 方法测试是否至少有一个元素可以通过被提供的函数方法。该方法返回一个Boolean类型
的值。
常用的字符串方法有哪些?
charAt() 方法从一个字符串中返回指定位置的字符。
concat() 方法将一个或多个字符串与原字符串连接合并,形成一个新的字符串并返回。
includes() 方法用于判断一个字符串是否包含在另一个字符串中,根据情况返回 true 或 false。
indexOf() 方法返回调用它的 String 对象中第一次出现的指定值的索引,从 fromIndex 处进行搜
索。如果未找到该值,则返回 -1。
match() 方法检索返回一个字符串匹配正则表达式的的结果。
padStart() 方法用另一个字符串填充当前字符串(重复,如果需要的话),以便产生的字符串达到给定的
长度。填充从当前字符串的开始(左侧)应用的。 (常用于时间补0)
replace() 方法返回一个由替换值( replacement )替换一些或所有匹配的模式( pattern )后的新字符串。模式可以是一个字符串或者一个正则表达式,替换值可以是一个字符串或者一个每次匹配都要调用的回调函数。

const str = 'abcca'
//要求去除所有c
console.log(str.replace(/c/g, '')); 'aba'

原字符串不会改变。
slice() 方法提取某个字符串的一部分,并返回一个新的字符串,且不会改动原字符串。
split() 方法使用指定的分隔符字符串将一个 String 对象分割成字符串数组,以将字符串分隔为
子字符串,以确定每个拆分的位置。
substr() 方法返回一个字符串中从指定位置开始到指定字符数的字符。
trim() 方法会从一个字符串的两端删除空白字符。在这个上下文中的空白字符是所有的空白字符
(space, tab, no-break space 等) 以及所有行终止符字符(如 LF,CR)。

Vue中v-for循环中key的作用
首先Vue在编译时会将templete转化成一个一个Vnode,形成一个VnodeTree,这个VnodeTree就是虚拟Dom树,然后进行真实的渲染生产真实dom树,在修改数据后,如果有key值,使用有key值的diff算法进行比较,没有key就用没有key的diff算法比较。只不过有key的diff算法效率高,diff算法就是先比较虚拟dom和真实dom的类型,然后类型一样的话再比较key,如果key也相等,那么继续比较,直到出现不同然后break出while循环。

offsetWidth计算的是盒子宽加padding加border的值,但无单位
函数声明
function a(){}
函数表达式
var a=function(){}
函数预编译
1.在函数被调用时,为当前函数产生AO对象
2.查找形参和变量声明作为AO对象的属性名,值为undefined
3.使用实参的值改变形参的值
4.查找函数声明,作为AO对象的属性名,值为function

webpack配置proxy解决开发中的跨域

proxy: {
      "/api": {
        target: "http://localhost:8888",
        pathRewrite: {
          "^/api": ""
        },
        secure: false,
        changeOrigin: true
      }
    }
axios.get("/api/moment").then(res => {
  console.log(res);
}).catch(err => {
  console.log(err);
})

相当于请求
http://localhost:8888/moment接口
箭头函属call bind apply绑定无效
箭头函数如果是独立函数调用还是看原来的this

express和koa框架他们的核心其实都是中间件:
但是他们的中间件事实上,它们的中间件的执行机制是不同的,特别是针对某个中间件中包含异步操作时;
所以,接下来,我们再来研究一下express和koa中间件的执行顺序问题;
express采用的是callback,koa采用的是async,这样在执行上express的callback中就天然不支持异步的处理,在express中处理异步可能不是你想要的执行顺序。在这里,就有了koa的经典:洋葱模型。
洋葱模型就是意思中间件代码处理的方式:
如果代码是同步的,在第一个中间件里面的next调用后,会执行下一个中间件,如果在下一个中间件里面还有next,就继续执行。。。
直到最后一个中间件执行完后返回到倒数第二个中间件的next处,执行倒数第二个中间件next后面的代码,然后逐层回退再响应结果,
如果是异步的,设置async和await后也可以让代码遵循洋葱模型

子组件使用父组件方法

<div id="app">
    <coml @sonfn='father'></coml>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
    var app = new Vue({
        el: '#app',
        components: {
            coml: {
                template: `

这是子组件

`
, created() { this.$emit('sonfn') }, methods: { use() { this.$emit('sonfn') } }, }, }, methods: { father() { console.log('father'); } }, })

子组件使用父组件的数据

      <div id="app">
    <coml :son='parent'></coml>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
    var app=new Vue({
        el:'#app',
        data:{
            parent:'这是父组件的数据'
        },
        components:{
            /* 默认子组件无法访问到父组件中data的数据和methods中的方法 */
            coml:{
                template: '

这是子组件,{{son}}

'
, /* 只有在props数组中定义才能使用,props里面的数据都是只读的,不能被修改 */ props:{ son:{ type:String, default(){ return 'a' } } }, } } }) </script>

prop补充
父组件传子组件可以传函数,这样可以让子组件调用父组件的函数。
当prop的默认值是Object类型要写成函数,原因跟data是函数一样
prop中如果数据是propInfo这种大小写类型,在子组件传值时写成prop-info,因为html是不区分大小写的。

对象引用的题目

function foo(num) {
    num.name = 'wwwww'//通过people找到name并修改
    num = 'fs'//这个直接让num指向'fs',但是people依旧指向那个对象
}
var people = { name: 'zs', age: '15', arr: [1, 2, 3] }
foo(people)
console.log(people);
console.log(people.name);

axios的post请求传的是json数据,服务器端可能要from类型数据。
数组字符串方法比较
数组
splice(start, deleteCount, item1, item2……)
start参数 开始的位置
deleteCount要截取的个数
后面的items为要添加的元素
如果deleteCount为0,则表示不删除元素,从start位置开始添加后面的几个元素到原始的数组里面。
返回值为由被删除的元素组成的一个数组。如果只删除了一个元素,则返回只包含一个元素的数组。如果没有删除元素,则返回空数组。
这个方法会改变原始数组,数组的长度会发生变化

const arr3 = [1, 2, 3, 4, 5, 6, 7, "f1", "f2"];
const arr4 = arr3.splice(2, 3) // 删除第三个元素以后的三个数组元素(包含第三个元素)
console.log(arr3); // [1, 2, 6, 7, "f1", "f2"]; 原始数组被改变

const arr5 = arr3.splice(2, 0, "wu", "leon"); 
// 从第2位开始删除0个元素,插入"wu","leon"
console.log(arr3); // [1, 2, "wu", "leon", 6, 7, "f1", "f2"]; 原始数组被改变

slice(start,end)
从start 开始截取到end,但是不包括end,不改变原数组

const arr = [1, 2, 3, 4, 5]
console.log(arr.slice(1, 4)) // [2, 3, 4]
console.log(arr) // [1, 2, 3, 4, 5]

字符串:
slice同数组
substr()和substring()
substr同数组splice但是无添加功能,substring同slice方法,只是不能接受负数
const str = ‘ABCDEFGHIJKLMN’
console.log(str.substr(2, 9)) // 输出 ‘CDEFGHIJK’
console.log(str.substring(2, 9)) // 输出 ‘CDEFGHI’

数组every / some方法区别
返回一个布尔值。当我们需要判定数组中的元素是否满足某些条件时,可以使用every / some。这两个的区别是,every会去判断判断数组中的每一项,而 some则是当某一项满足条件时返回。

// every
const foo = [5,1,3,7,4].every((item, index) => {
    console.log(`索引:${index},数值:${item}`)
    return item > 2
})
console.log(foo)
// every 打印:
// 索引:0,数值:5
// 索引:1,数值:1
// false

// some
const foo = [5,1,3,7,4].some((item, index) => {
    console.log(`索引:${index},数值:${item}`)
    return item > 2
})
console.log(foo)
// some 打印:
// 索引:0,数值:5
// true

map()
map即是 “映射”的意思 ,原数组被“映射”成对应新数组。
map:支持return,相当与原数组克隆了一份,把克隆的每项改变了,也不影响原数组。

const foo = [5,1,3,7,4].map((item,index) => {
    console.log(`索引:${index},数值:${item}`)
    return item + 2
})
console.log(foo)
// 打印结果:
// 索引:0,数值:5
// 索引:1,数值:1
// 索引:2,数值:3
// 索引:3,数值:7
// 索引:4,数值:4
// [7, 3, 5, 9, 6]

reduce()
reduce 从左到右将数组元素做“叠加”处理,返回一个值。

const foo = [5,1,3,7,4].reduce((total, cur) => {
    console.log(`叠加:${total},当前:${cur}`)
    return total + cur
})
console.log(foo)
// 打印结果:
// 叠加:5,当前:1
// 叠加:6,当前:3
// 叠加:9,当前:7
// 叠加:16,当前:4
// 20

Object对象的两个遍历 Object.keys() 与 Object.getOwnPropertyNames()
他们都是遍历对象的属性,也是接受一个对象作为参数,返回一个数组,包含了该对象自身的所有属性名。但Object.keys不能返回不可枚举的属性;Object.getOwnPropertyNames能返回不可枚举的属性。

对SSR的理解?
SSR也就是服务端渲染,也就是将Vue在客户端把标签渲染成HTML的工作放在服务端完成,然后再把htmI直接返回给客户端
SSR的优势:
●更好的SEO
●首屏加载速度更快
SSR的缺点:
●开发条件会受到限制,服务器端渲染只支持beforeCreate和created两个钩子;
●当需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于Node.js的运行环境;
●更多的服务端负载。
顾名思义,服务端渲染就是在浏览器请求页面URL的时候,服务端将我们需要的HTML文本组装好,并返回给浏览器,这个HTML文本被浏览器解析之后,不需要经过 JavaScript 脚本的执行,即可直接构建出希望的 DOM 树并展示到页面中。这个服务端组装HTML的过程,叫做服务端渲染

vue的性能优化有哪些?
(1)编码阶段
●如果需要使用v-for给每项元素绑定事件时使用事件代理
●SPA页面采用keep-alive缓存组件
●在更多的情况下,使用v-if替代v-show
●key保证唯一
●使用路由懒加载、异步组件
●防抖、节流
●第三方模块按需导入
●图片懒加载
(2)SEO优化
●服务端渲染SSR
(3)打包优化
●压缩代码
●Tree Shaking/Scope Hoisting
●使用cdn加载第三方模块

Vue请求数据
尽量不要再mounted里面执行接口请求,因为如果我们实现SSR服务端渲染功能的时候,是没有mounted这个钩子函数的,可是created这个钩子函数所有场景下都有,所以在这个里面接口请求是最佳的

hash模式下,仅hash符号之前的url会被包含在请求中,后端如果没有做到对路由的全覆盖,也不会返回404错误;history模式下,前端的url必须和实际向后端发起请求的url一致,如果没有对路由进行处理,将返回404错误。

vue history模式刷新404原因

原因分析:
因为在history模式下,只是动态的通过js操作window.history来改变浏览器地址栏里的路径,并没有发起http请求,但是当我直接在浏览器里输入这个地址的时候,就一定要对服务器发起http请求,但是这个目标在服务器上又不存在,所以会返回404
解决方法:

module.exports = {
publicPath:/, //这个必须,引入静态资源需要从根路径引入,否则会找不到静态资源
devServer: {
// history模式下的url会请求到服务器端,但是服务器端并没有这一个资源文件,就会返回404,所以需要配置这一项
historyApiFallback: {
index:/index.html’ //与output的publicPath
},
},
}

路由params和query的区别?
params是路由的一部分,必须要有。query是拼接在url后面的参数,没有也没关系。
params一旦设置在路由,params就是路由的一部分,如果这个路由有params传参,但是在跳转的时候没有传这个参数,会导致跳转失败或者页面会没有内容。
比如:跳转/router1/:id

  < router-link :to="{ name:'router1',params: { id: status}}" >正确< /router-link>
  < router-link :to="{ name:'router1',params: { id2: status}}">错误< /router-link>

0.1 + 0.2 !=0.3 使用什么方法使其相等
Number.EPSILON属性
ES6 在 Number 对象上面,新增了一个极小的常量 Number.EPSILON ,它表示 1 和大于 1 的最小浮点数之间的差,相当于 2 的 -52 次方

  function numbersequal(a, b) {
    return Math.abs(a - b) < Number.EPSILON
  }
  var a = 0.1 + 0.2;
  var b = 0.3
  console.log(numbersequal(a, b));//true

跟mouseenter搭配鼠标离开mouseleave 同样不会冒泡

//函数声明
function a() { }
//函数表达式
var b = function () { }

localstorage和cookie在localhost的a.html设置后,在b.html可以访问到
sessionstorage不会

html5的新特性

语义化标签(header,nav,footer,main),方便月度维护,有利于seo。
新增选择器querySelector
localStorage和SessionStorage
video和audio

堆和栈的区别

堆的存储空间更大,栈的速度更快
堆是无序存储,可根据引用直接获取

事件循环:主线程从任务队列中读取执行事件,这个过程是循环的,这个过程称为事件循环。

一些忘了的东西。。。_第8张图片
typeof null = 'object’是js历史遗留的bug

JWT实现Token机制

JWT生成的Token由三部分组成:
header
alg:采用的加密算法,默认是 HMAC SHA256(HS256),采用同一个密钥进行
加密和解密;
typ:JWT,固定值,通常都写成JWT即可;
会通过base64Url算法进行编码;
payload
携带的数据,比如我们可以将用户的id和name放到payload中;
默认也会携带iat(issued at),令牌的签发时间;
我们也可以设置过期时间:exp(expiration time);
会通过base64Url算法进行编码
signature
设置一个secretKey,通过将前两个的结果合并后进行HMACSHA256的算法;
HMACSHA256(base64Url(header)+.+base64Url(payload), secretKey);
但是如果secretKey暴露是一件非常危险的事情,因为之后就可以模拟颁发token,
也可以解密token;

生成公私钥

我们可以使用openssl来生成一对私钥和公钥
私钥颁发token,公钥解密token

webpack和vite的区别
webpack会先打包,然后启动开发服务器,请求服务器时直接给予打包结果。
而vite是直接启动开发服务器,请求哪个模块再对该模块进行实时编译。
由于现代浏览器本身就支持ES Module,会自动向依赖的Module发出请求。vite充分利用这一点,将开发环境下的模块文件,就作为浏览器要执行的文件,而不是像webpack那样进行打包合并。

webpack的流程
从 entry 文件开始,调用 loader 对模块进行转译处理,然后调用 JS 解释器(acorn)将内容转 化为 AST 对象,然后递归分析依赖,依次处理全部文件。

什么是CDN加速?
CDN全称是内容分发网络。它主要用于加速静态资源,CDN在全国有很多节点服务器,当访问cdn连接时,CDN服务器会从就近的cdn节点服务器获取相应静态资源。
原理:CDN依靠部署在各地的边缘服务器 , 通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。
CDN 的工作原理 就是将源站的资源缓存CDN各个节点上,当请求命中了某个节点的资源缓存时,立即返回客户端,避免每个请求的资源都通过源站获取,避免网络拥塞、缓解源站压力,保证用户访问资源的速度和体验。

js是单线程的,那异步是怎么实现的呢?
js是单线程的,但是浏览器是多线程的,js的异步操作基本都是通过浏览器完成的子线程完成的。

MVC和MVVM的区别?
MVC是后端语言的一种设计方案,M(model)代表数据库,V代表视图层(html,css)C代表controller,是负责将model数据交给view层展示的。
MVVM是前端的一种设计模式,M代表数据,V代表视图,vm代表视图模型层,通过数据改变视图。

created=>beforemount干了什么?
开始渲染虚拟dom

封装的vue组件
具名插槽和props
什么是路由懒加载?
路由懒加载就是通过异步的方式加载路由组件(代码分包的一种方式),加载首页的时候不用对其他组件加载,用到再加载。

nextTick作用?
延迟代码执行,直到dom更新后再执行

commonjs和esmodule的区别
commonjs加载是同步的,所以必须等到相应的模块加载完毕,
在会执行后续代码,ESModule的import是异步加载模块
commonJS输出的是值的拷贝,ES Module输出的是值的引用

let a='123,321,456'
console.log(a.split(','));//['123', '321', '456']
let b='123';
console.log(b.split(''));//['1','2','3']

发布订阅和观察者模式的区别?
发布订阅模式是由事件中心调度。订阅者和发布者互不干扰。
观察者模式是观察者和目标直接进行交互。
一些忘了的东西。。。_第9张图片
vue2与vue3区别
1.vue3速度更快,体积更小
2.vue3出现composition Api 其中的setup出现代替了vue2中的data,method,computed属性,并且代替了vue2中的beforecreate和created生命周期函数(setup在beforecreate前就会执行)
3.vue2和vue3的diff算法存在差异,vue2的diff是当data中的数据发生改变后会生成一整颗虚拟dom树,通过对比新旧整颗虚拟dom树将变化渲染上去,vue3是将变量赋值flag1,不变的值赋值flag,只会对比变动的值,因此比vue3性能更好,例如常量就不需要进行对比。vue3采用的是最长递增子序列的算法。
4.VUE3 支持碎片(Fragments), 组件支持多个根节点。不需要在组件内使用一个总的 div 来包裹着。
5.vue2与vue3的响应式原理有区别,vue2采用Object.defineproperty()和发布订阅模式,vue3采用proxy和发布订阅模式,vue2中Object.defineproperty()不能检测整个对象,并且对数组下标索引改变也不能监听,而proxy可以完美的监听到整个对象或数组的数据改变。
6. 生成 Block tree
Vue.js 2.x 的数据更新并触发重新渲染的粒度是组件级的,单个组件内部 需要遍历该
组件的整个 vnode 树。在 2.0 里,渲染效率的快慢与组件大小成正相关:组件越大,
渲染效率越慢
。并且,对于一些静态节点,又无数据更新,这些遍历都是性能浪费。
Vue.js 3.0 做到了通过编译阶段对静态模板的分析,编译生成了 Block tree。 Block
tree 是一个将模版基于动态节点指令切割的嵌套区块
,每个 区块内部的节点结构是固
定的,每个区块只需要追踪自身包含的动态节点。所以,在 3.0 里,渲染效率不再与模
板大小成正相关,而是与模板中动态节点的数量成正相关

关系型数据库和非关系型数据库的区别
关系型数据库表和表,表和字段存在关系,但是面对增删改查海量数据却有些无力
非关系型数据库表和表,表和字段不存在关系,每条数据都是单独存在,对增删改查海量数据很迅速,但是没有强大的业务体系。

vue 里面会写 scoped原理?
会在生成后的 DOM 里面加一个 [v-data-XXXX] 随机函数值

说一说vue的渲染流程吧
new Vue,执行初始化
挂载$mount方法,通过自定义Render方法、template、el等生成Render函数
通过Watcher监听数据的变化
当数据发生变化时,Render函数执行生成VNode对象
通过patch方法,对比新旧VNode对象,通过DOM Diff算法,添加、修改、删除真正的DOM元素

v-if 和 v-for 的优先级
v-for优先级高

call可以直接写多个参数,apply需要用数组方式传递
localStorage在同源a页面设置后可以在同源b页面获取到,sessionStorage不行。(非同源都不行)

字符串的api都改变不了原字符串

为什么在类中使用extends后要先在contructor中调用super()?

这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。

ES5 的继承的实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。

因此子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有super方法才能调用父类实例。

class写的代码默认都是严格模式下的代码

所以打印this不是window,而是undefined

为什么vuex中不能直接修改state的状态

不经过mutations不能直接修改state中的数据,因为state是实时更新的,如果直接修改state中的数据是异步操作,当state异步还没有执行完,state的数据就有可能发生变化,会导致程序出问题,所以必须通过mutations限制state不允许异步操作

为什么mutation不能写异步代码

Vuex中状态更新的途径是mutation,异步操作通过 Action 来提交 mutation实现,这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

每个mutation执行完成后都会对应到一个新的状态变更,这样devtools就可以打个快照存下来,然后就可以实现 time-travel 了。如果mutation支持异步操作,就没有办法知道状态是何时更新的,无法很好的进行状态的追踪,给调试带来困难。

BFC

直译为 块级格式化上下文 ,把BFC理解成一块独立的渲染区域,BFC看成是元素的一种属性, 当元素拥有了BFC属性后,这个元素就可以看做成隔离了的独立容器。容器内的元素不会影响容器外的元素.
作用:清除浮动和解决margin重叠的问题
当元素设置了 overflow 样式且值部位 visible 时,该元素就构建了一个 BFC,BFC 在
计算高度时,内部浮动元素的高度也要计算在内,也就是说技术 BFC 区域内只有一个
浮动元素,BFC 的高度也不会发生塌缩,所以达到了清除浮动的目的

闭包

闭包就是能够读取其他函数内部变量的函数

我们会发现类中的extends实际采用的也是寄生组合继承方式。

移动端click300ms延迟源于当属双击缩放(double tap to zoom),这也是会有上述 300 毫秒延迟的主要原因。

redux的缺点

1.一个组件所需要的数据,必须由父组件传过来,而不能像 flux 中直接从 store 取。
2.当一个组件相关数据更新时,即使父组件不需要用到这个组件,父组件还是会重新
render,可能会有效率影响,或者需要写复杂的 shouldComponentUpdate 进行判断。

了解 shouldComponentUpdate 吗?

React 虚拟 dom 技术要求不断的将 dom 和虚拟 dom 进行 diff 比较,如果 dom 树比价
大,这种比较操作会比较耗时,因此 React 提供了 shouldComponentUpdate 这种补丁
函数,如果对于一些变化,如果我们不希望某个组件刷新,或者刷新后跟原来其实一
样,就可以使用这个函数直接告诉 React,省去 diff 操作,进一步的提高了效率。

为什么建议setState的第一个参数是callback而不是一个对象呢?

React 为了优化性能,有可能会将多个 setState() 调用合并为一次更新。
因为this.props和this.state 可能是异步更新的,你不能依赖他们的值计算下一个state(状态)。以下面的代码为例:

this.setState({
counter: this.state.counter + this.props.increment,
});
我们并不能通过上述代码得到想要的值,为了弥补这个问题,使用另一种 setState() 的形式,接受一个函数。这个函数将接收前一个状态作为第一个参数,应用更新时的 props 作为第二个参数,代码如下:

this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));

一个图片 url 访问后直接下载怎样实现?

请求的返回头里面,用于浏览器解析的重要参数就是 OSS 的 API 文档里面的返回 http
头,决定用户下载行为的参数。
下载的情况下:
1, x-oss-object-type:
Normal
2. x-oss-request-id:
598D5ED34F29D01FE2925F41
3. x-oss-storage-class:
Standard

关于响应式布局

响应式布局方案解析

Promise的优点在哪里?即传统异步处理方案与Promise的区别在哪?

1.回调函数的指定时机更灵活
2.支持链式调用,解决回调地狱问题

1.回调函数的指定时机
异步操作实际上是在主体main函数中,回调函数只是处理返回的结果
传统异步回调函数必须在创建asnycFunc时就指定,此时main函数中异步操作还没执行
而Promise可以在主体main函数中异步操作执行完毕后拿到结果并保存起来,此时结果不会消失,可以在任意时刻使用结果指定回调函数并执行
传统异步指定回调函数:

let success=function(res){}  //成功的回调函数
let error=function(err){} //失败的回调函数
function asyncFunc(main函数,success,error)
{
  //主函数操作完成选择调用success或error
  ...所有的操作都必须在asyncFunc这一个函数中
}

Promise指定回调函数:

let promise = new Promise((reslove,reject)=>{
  //main函数
  setTimeout(() => {
    reslove('我是返回的结果')
  }, 1000); //模拟异步操作
}) 
//promise本体主函数执行完操作会拿到结果并保存起来

那么我要什么时候指定回调函数呢??
想什么时候都可以,甚至可以加一个10s的定时器再指定毁掉函授
setTimeout(() => {
  promise.then(res=>{
    console.log(res);
  },err=>{
    console.log(err);
  })
}, 5000);
//我是返回的结果

2.回调地狱与链式调用
问题一:很多人都知道有回调地狱,但不知道回调地狱到底是什么
见案例
问题二:为什么Promise能链式调用
原因:promise.then()执行完毕的返回值是一个新的Promise实例
传统异步方案的回调地狱:
整个异步函数asyncFunc内部多次嵌套使用回调函数,一层回调函数内部再来调用下一个回调函数,下一层接着下一层…

//这里默认Promise的状态全部为fulfilled
let success1=function(res1){}  //成功的回调函数1
let success2=function(res2){}  //成功的回调函数2
let success3=function(res3){}  //成功的回调函数3
let success4=function(res4){}  //成功的回调函数4
let success5=function(res5){}  //成功的回调函数5
...
function asyncFunc(main函数,success1)
{
 ...
 监听异步操作maim执行完毕并拿到结果res1之后
 success1(res1){
   监听操作拿到结果res2...
   success2(res2){
     监听操作拿到结果res3...
     success3(res4){
       监操拿果res4...
       回调函数4(res5){
       ...
       }
     }
   }
 }
}

promise方法总结

all
全为fufilledall,all才为fulfilled res为res[ ]
有一个为rejected,all就为rejected err为出现的第一个err
传入数组出现非promise实例,全部当做fulfilled的res处理
race
无论fufilled或rejected,返回最快获得res/err的promise
any
第一个fufilled,返回最快获得res的promise
allSettled
当所有promise结束后,返回一个包含每个promise状态和结果的数组
比如

let p1=new  Promise((reslove,reject)=>{
  setTimeout(() => {
    reslove(1)
  }, 1000);
})
let p2=Promise.resolve(2)
let p3=Promise.resolve(3)
let p4=Promise.resolve(4)
let p5=Promise.reject(5)

Promise.allSettled([p1,p2,p3,p5]).then(res=>{
  console.log(res);
},err=>{
  console.log(err);
})

Promise.allSettled([p5,p1,p2,p3]).then(res=>{
  console.log(res);
},err=>{
  console.log(err);
})

一些忘了的东西。。。_第10张图片

节流和防抖的应用场景

节流
监听事件:鼠标移动
执行函数:输出坐标
防抖
监听事件:表单输入
执行函数:搜索

call方法的性能高于apply

Object.prototype.toString.call()

对于 Object.prototype.toString() 方法,会返回一个形如 “[object XXX]” 的字符串。
但是,大多数对象,toString() 方法都是重写了的,这时,需要用 call() 或 Reflect.apply() 等方法来调用。
由于typeof和instanceof都有缺陷,所以版本答案是Object.prototype.toString.call
如果需要通用检测数据类型,可以采用Object.prototype.toString.call( ),调用该方法,统一返回格式“[object Xxx]” 的字符串

for和forEach的区别

for中 return break continue 生效
forEach中不能使用这三个
打断迭代可以try{}catch(err){}

0.1+0.2不等于0.3的原因

总结一下主要是以下几个原因:

  1. JavaScript的浮点数运算是会先转换成二进制,再进行运算(运算时才会,单纯的输出小数不会出现精度丢失的情况)
  2. 十进制转换二进制遵循*2顺取整直至1的原则,部分小数会陷入循环
  3. 二进制会从第一个1后开始向后保留52位有效数字且最后一位遵循0舍1入的原则–因此也有了最大安全整数2*53-1 1+52位=53位

null和undefined区别

1.typeof的结果不同
2.==和= ==的结果不同(Number()转换后的结果不同)

typeof NaN=Number

因为Nan属于数值型

BigInt出现的原因:

Number类型最大安全整数为2*53-1,此后的数字会出错/不准确

tree-shaking的原理

ES6 Module引入进行静态分析,故而编译的时候正确判断到底加载了那些模块
静态分析程序流,判断那些模块和变量未被使用或者引用,进而删除对应代码

ESM和CJS的区别

1.ESM的import是引用,CJS的require是拷贝
2.ESM引入的变量无法修改,CJS引入的可以
3.ESM加载资源是异步的,CJS加载资源是同步的(所以CSJ无法在浏览器中使用)

node中的事件循环

timers:定时器
pending callbacks:挂起到下一循环的IO回调
idle,prepare: 仅在内部使用(无需关注)
poll:轮询阶段
check:setimmediate
close callbacks:eg:socket.on(‘close’, … )
一些忘了的东西。。。_第11张图片

一些忘了的东西。。。_第12张图片

Javascript 之《函数传参到底是值传递还是引用传递》

1.如果是引用传递

var name = 'JS';
function changeName(name){
    name = 'Javascript';  
    console.log('name changed: '+name)  
}
changeName(name); // name changed: Javascript
console.log(name); // 'JS'

为什么 name 没有变呢?

如果是引用传递的话,name 应该是会发生变化的。这么说应该是值传递?

也许你下意识也觉得没变是正常的,但这是因为你的经验告诉你的。我们暂且按下不表,看看下一种可能性。
2.如果是值传递

var obj = {name: 'JS'};
function changeObjName(obj){
    obj.name = 'Javascript';
    console.log('Obj.name changed: '+obj.name);  
}
changeObjName(obj); // Obj.name changed: Javascript
console.log(obj); // {name: 'Javascript'}

为什么这一次传入的参数被改变了?不是值传递吗?这么说是引用传递了?

那上面的例子又是怎么回事?开始一头雾水了…
3.到底是值传递还是引用传递??

其实在 Javascript 的世界中,函数传参并不是一定是一种传参方式,主要依据传入的参数而定。主要有两种情况:

1. 如果是值类型

基本数据类型的变量基本上都是值类型,例如字符串,数值,布尔值等。

值类型的传参是值传递,函数内对这种类型参数的修改不会影响到原来传入的值。

2. 如果是引用类型

复合数据类型如对象,数组等都是引用类型。

引用传参是引用传递,函数内对这种类型参数的修改都会影响到原来传入的值。

4.深入函数传参的机制

其实函数接受参数之前,会先在函数作用域内创建一个局部变量,并把传入的参数赋予创建的局部变量,如果值类型传递,就是重新开辟一块内存存储传入变量的值,并把新创建的变量指向此内存地址,因此对此变量的修改并不会影响到外部变量的值;
如果传入的不是值类型,则仅仅是对变量进行引用赋值操作,只是把新创建的局部变量指向外部变量的地址,因此对局部变量的修改就相当于对外部变量的修改。

一道题目

js创建Ao对象的时机是调用函数前,但是要明确一点,创建ao对象时,是看函数所在位置,不是看调用位置

var length = 10;
function fn() {
    return this.length+1;
}
var obj = {
    length: 5,
    test1: function () {
        return fn();insta
    }
};
console.log(obj.test1())

instanceof能判断对象是不是一个对象还是数组,但是判断不了一个数组是不是对象

let arr = []
const obj = {}
 
 
console.log(arr instanceof Array)  // 输出结果为true
console.log(arr instanceof Object) // 还是会输出true
 
console.log(obj instanceof Array)  // 输出结果为false
console.log(arr instanceof Object) // 输出true

微信扫码登录流程

1.用户打开自己的手机微信并扫描这个二维码,并提示用户是否确认登录;如果接到状态码201(服务器创建新资源成功),表示客户端扫描了该二维码。(每次访问二维码会创建一个全局唯一的 ID)
2.手机上的 微信 是登录状态,用户点击确认登录后,手机上的微信客户端将微信账号和这个扫描二维码得到的 ID 一起提交到服务器;
3. 服务器将这个 ID 和用户 的微信号绑定在一起,并通知网页版微信,这个 ID 对应的微信号为此用户 ,网页版微信加载用户 的微信信息,至此,扫码登录全部流程完成;

require的原理及查找顺序

require做的就是把module.exports对象存的内存地址拿到并作为返回值
查找顺序:
首先,在核心模块中查找
然后,在当前目录中查找
最后,在node_modules中查找

v-if和v-for的优先级

源码在进行if判断的时候,v-for是比v-if先进行判断
最终结论:v-for优先级比v-if高
永远不要把 v-if 和 v-for 同时用在同一个元素上,带来性能方面的浪费(每次渲染都会先循环再进行条件判断

spa页面的首屏渲染时间怎么优化?

通过DOMContentLoad来计算出首屏时间

document.addEventListener('DOMContentLoaded', (event) => {
    console.log('first contentful painting');
});

解决方案:
静态资源本地缓存(采用HTTP缓存或者localstorage,设置Cache-Control,Last-Modified,Etag等响应头)
减小入口文件体积,做分包(路由懒加载)
开启GZip压缩

懒加载和动态路由的区别
一些忘了的东西。。。_第13张图片
一些忘了的东西。。。_第14张图片

axios的实现

class Axios {
    constructor() {

    }

    request(config) {
        return new Promise(resolve => {
            const {url = '', method = 'get', data = {}} = config;
            // 发送ajax请求
            const xhr = new XMLHttpRequest();
            xhr.open(method, url, true);
            xhr.onload = function() {
                console.log(xhr.responseText)
                resolve(xhr.responseText);
            }
            xhr.send(data);
        })
    }
}

history报404的解决方案

通过nginx将任意页面都重定向到 index.html,把路由交由前端处理

tree shaking的原理

Tree shaking是基于ES6模板语法(import与exports),主要是借助ES6模块的静态编译思想,在编译时就能确定模块的依赖关系,以及输入和输出的变量

Tree shaking无非就是做了两件事:

编译阶段利用ES6 Module判断哪些模块已经加载
判断那些模块和变量未被使用或者引用,进而删除对应代码
ES6模块的静态编译思想的两大特点:
import 命令会被 JavaScript 引擎静态分析,优先于模块内的其他内容执行。
export 命令会有变量声明提前的效果。

ts中any unknowm never的区别

any 是任意值类型,定义为 any 类型的变量允许被赋值为任意类型。
变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型,也就是说声明为 any 类型和写 JS 没啥区别
never 类型表示的是那些永不存在的值的类型。例如一个抛出异常的函数,一个永远不会返回的函数的返回值类型
所以 any 和 unknown 的区别就是:
二者都是可以赋值给任意类型的, any 会绕过类型检查,直接可用;而 unkonwn 则必须要在判断完它是什么类型之后才能继续使用

为什么Vue中的v-if 和v-for不建议一起用?

v-for优先级高于v-if,如果连在一起使用的话会把v-if给每一个元素都添加上,重复运行于每一个v-for循环中,会造成性能浪费
解决:
可以将v-if写在v-for的外层

为什么this.$nextTick内部优先使用微任务模拟?

由于是宏任务因此比较消耗资源。

map和object的区别

key的区别

map可以将任何数据类型作为key,而Object,只能使用String或者Symbol。
一个 Map的键可以是任意值,包括函数、对象或任意基本类型。

key的顺序

Map 中的 key 是有序的。因此,当迭代的时候, Map 对象以插入的顺序返回键值。Object 的键是无序的

频繁删除键值对的区别

Map: 在频繁增删键值对的场景下表现更好。
Object: 在频繁添加和删除键值对的场景下未作出优化。

require的查找规则

一些忘了的东西。。。_第15张图片
一些忘了的东西。。。_第16张图片
一些忘了的东西。。。_第17张图片

sort函数

sort() 方法用于对数组的元素进行排序,并返回数组。默认排序顺序是根据字符串Unicode码点。
语法:arrayObject.sort(sortby);参数可选。规定排序顺序。必须是函数。
注:如果调用该方法时没有使用参数,将按字母顺序对数组中的元素进行排序,说得更精确点,是按照字符编码的顺序进行排序。要实现这一点,首先应把数组的元素都转换成字符串(如有必要),以便进行比较。

如果想按照其他标准进行排序,就需要提供比较函数,该函数要比较两个值,然后返回一个用于说明这两个值的相对顺序的数字。比较函数应该具有两个参数 a 和 b,其返回值如下:
若 a 小于 b,在排序后的数组中 a 应该出现在 b 之前,则返回一个小于 0 的值。
若 a 等于 b,则返回 0。
若 a 大于 b,则返回一个大于 0 的值。

对象和map和weakmap的区别

对象的键必须是字符串,map的键可以是任意类型的,Map 是为了解决对象中的 key 只能为字符串的缺陷,weakmap和map的区别是Map 的键可以是任意类型,WeakMap 只接受对象作为键(null除外),不接受其他类型的值作为键。 WeakMap 中,每个键对自己所引用对象的引用都是弱引用,在没有其他引用和该键引用同一对象,这个对象将会被垃圾回收(相应的key则变成无效的),所以,WeakMap 的 key 是不可枚举的。
一些忘了的东西。。。_第18张图片

nextTick原理

浅层原理
当下次视图更新之后再调用回调函数拿到数据
兼容性处理,优先使用promise.then 优雅降级(兼容处理就是一个不断尝试的过程,谁可以就用谁。
Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。
个人理解:
微任务顺序:process.nextTick.promise.then()
宏任务顺序:0ms定时器 setimmediate 需要花费时间读取的io回调 有延迟的定时器
poll轮询阶段较为复杂,暂不讨论

vue-sfc 文件a 文件b p 命名冲突为什么没有?

答案:打包-webpack打包原理-module-chunk LIFE(打包的产物-立即执行函数的形式)

useRef和createRef区别

相同点:都可以用作获取dom
不同点:createRef会在组件每次渲染的时候重新创建,useRef只会在组件首次渲染时创建
一些忘了的东西。。。_第19张图片
在类组件中我们想要获取子组件的dom或者获取html元素,可以使用createref
一些忘了的东西。。。_第20张图片
在函数组件中我们想要获取html元素的dom可以使用createref或者useref,但是想要获取子组件dom使用useref结合forwardRef获取

import React, {
  forwardRef,
  useRef,
  useImperativeHandle,
  useState,
} from "react";

const Child = forwardRef((props, ref) => {
  const [count, setCount] = useState(0);

  useImperativeHandle(ref, () => ({
    count: count,
    changeCount() {
      setCount(count + 1);
    },
  }));

  return (
    <div>
      <p>子组件count: {count}</p>
    </div>
  );
});

function App() {
  const tabref = useRef(null);
  
  function abc() {
    tabref.current.changeCount()
  }
  return (
    <div className="App">
      <button onClick={abc}>11111111</button>
      <Child ref={tabref}></Child>
    </div>
  );
}

export default App;

利用useref解决闭包陷阱

useref当更新current值时并不会re-render,这是与useState不同的地方,useRef 不仅仅是用来管理 DOM ref 的,它还相当于 this , 可以存放任何变量.
一些忘了的东西。。。_第21张图片

在count为6的时候, 点击 show alert , 再继续增加 count , 弹出的值为 6, 而非 10.
为什么不是界面上 count 的实时状态?
当我们更新状态的时候,React 会重新渲染组件, 每一次渲染都会拿到独立的 count 状态, 并重新渲染一个 handleAlertClick 函数. 每一个 handleAlertClick 里面都有它自己的 count .在我们在count为6的时候, 点击 show alert时,这个alert引用的是count为6的count,不是最新的count,是闭包陷阱。
想要显示实时的count解决方案:
一些忘了的东西。。。_第22张图片
因为 useRef 每次都会返回同一个引用, 所以在 useEffect 中修改的时候 ,在 alert 中也会同时被修改. 这样子, 点击的时候就可以弹出实时的 count 了.

git reset 和 git revert使用详解

git revert和 reset都能撤回特定提交v1的内容,但git revert是通过在新的提交里对提交v1的内容进行反向操作来实现撤销,所以git revert不会改变提交历史;git reset 通过版本回退到特定提交v1-1(v1的前一次提交),来直接撤销掉v1这次提交,从而撤回提交v1的内容,所以git reset会改变提交历史。
文章链接
一般我们reset 就够用了,记得使用
git push -f origin master。经过测试IDEA,你用IDEA图形化git push上去,默认它会发现,你本地的master revert 的版本小于master,它默认会让你拉。
所以,注意这里手动使用命令
git push -f origin master,这样你远端仓库也就revert到了你指定的版本,成功回滚!

总结: 对于已经push到远端的情况,本地执行git reset ,此时本地文件已经回滚到你指定版本,但是服务器仍然没有改变,需要继续远程回滚: git push -f 执行,远程回滚成功。

cdn的技术原理

(1)用户未使用CDN缓存资源的过程:

浏览器通过DNS对域名进行解析(就是上面的DNS解析过程),依次得到此域名对应的IP地址
浏览器根据得到的IP地址,向域名的服务主机发送数据请求
服务器向浏览器返回响应数据

(2)用户使用CDN缓存资源的过程:

对于点击的数据的URL,经过本地DNS系统的解析,发现该URL对应的是一个CDN专用的DNS服务器,DNS系统就会将域名解析权交给CNAME指向的CDN专用的DNS服务器
CND专用DNS服务器将CND的全局负载均衡设备IP地址返回给用户
用户向CDN的全局负载均衡设备发起数据请求
CDN的全局负载均衡设备根据用户的IP地址,以及用户请求的内容URL,选择一台用户所属区域的区域负载均衡设备,告诉用户向这台设备发起请求
区域负载均衡设备选择一台合适的缓存服务器来提供服务,将该缓存服务器的IP地址返回给全局负载均衡设备
全局负载均衡设备把服务器的IP地址返回给用户
当用户访问相同资源时,用户向该缓存服务器发起请求,缓存服务器响应用户的请求,将用户所需内容发送至用户终端。
CNAME(意为:别名):在域名解析中,实际上解析出来的指定域名对应的IP地址,或者该域名的一个CNAME,然后再根据这个CNAME来查找对应的IP地址。
一些忘了的东西。。。_第23张图片
场景:使用CDN进行静态资源的缓存,比如js、css、图片等

React的事件机制

<div onClick={this.handleClick.bind(this)}>点我</div>

React并不是将click事件绑定到了div的真实DOM上,而是在document处监听了所有的事件,当事件发生并且冒泡到document处的时候,React将事件内容封装并交由真正的处理函数运行。这样的方式不仅仅减少了内存的消耗,还能在组件挂在销毁时统一订阅和移除事件。
除此之外,冒泡到document上的事件也不是原生的浏览器事件,而是由react自己实现的合成事件(SyntheticEvent)。因此如果不想要是事件冒泡的话应该调用event.preventDefault()方法,而不是调用event.stopProppagation()方法。
一些忘了的东西。。。_第24张图片
JSX 上写的事件并没有绑定在对应的真实 DOM 上,而是通过事件代理的方式,将所有的事件都统一绑定在了 document 上。这样的方式不仅减少了内存消耗,还能在组件挂载销毁时统一订阅和移除事件。
另外冒泡到 document 上的事件也不是原生浏览器事件,而是 React 自己实现的合成事件(SyntheticEvent)。因此我们如果不想要事件冒泡的话,调用 event.stopPropagation 是无效的,而应该调用 event.preventDefault。

实现合成事件的目的如下:

合成事件首先抹平了浏览器之间的兼容问题,另外这是一个跨浏览器原生事件包装器,赋予了跨浏览器开发的能力;
对于原生浏览器事件来说,浏览器会给监听器创建一个事件对象。如果你有很多的事件监听,那么就需要分配很多的事件对象,造成高额的内存分配问题。但是对于合成事件来说,有一个事件池专门来管理它们的创建和销毁,当事件需要被使用时,就会从池子中复用对象,事件回调结束后,就会销毁事件对象上的属性,从而便于下次复用事件对象。

你可能感兴趣的:(javascript,前端)