前端最全面经(2022校招),持续更新...

2021年10月26日更新

URL和URI的区别:
1. URL是一种具体的URI,它是URI的一个子集
2. URI和URL都定义了资源是什么
3. URL不仅唯一标识资源,而且还提供了定位该资源的信息。
4. URI 是一种语义上的抽象概念,可以是绝对的,也可以是相对的,而URL则必须提供足够的信息来定位,是绝对的

a标签的target:
1. _blank:在新窗口打开被链接的文档
2. _self:默认,在当前页面打开
3. parent: 在父框架集中打开被链接文档( 会在父窗口或者包含来超链接引用的框架的框架集中打开链接。)
4. _top: 在整个窗口打开被链接文档 ( 会清除所有被包含的框架,并打开链接。)

href和src的区别:
1. href是引用,表示当前页面和它相关联
2. src是引入,是页面的一部分

行内元素:a,span,i,b,img
块元素:div,li,p,h1~h6

DocType的作用:
声明位于文档最前面,告诉浏览器应该用什么样的方式来渲染页面
严格模式(标准模式):以浏览器支持的最高标准执行
混杂模式:向后兼容,防止浏览器无法兼容页面

标准模式为什么可以简写:
HTML5 不基于SGML,因此不需要对DTD进行引用,但是需要doctype来规范浏览器的行为。
而HTML4.01基于SGML,所以需要对DTD进行引用,才能告知浏览器要用什么类型来渲染页面

深拷贝实现的方式:
浅拷贝只复制指向对象的指针,而不复制对象本身,新旧对象使用的是同一块内存深拷贝创建了另外一个一模一样的对象
浅拷贝:

var a = [1,2,3,4];var b = a;

深拷贝:不可能拷贝原型
在这里插入图片描述
事件委托:

function delegate(element, eventType, selector, fn) {
    // 对传过来的元素添加事件监听
    element.addEventListener(eventType, e => {
        // el是想要被监听的对象
        let el = e.target
        // 用来判断当前DOM节点是否能完全匹配对应的CSS选择器规则,返回值是Boolean
        while (!el.matches(selector)) {
            if (element === el) {
                el = null
                break
            }
            el = el.parentNode
        }
        el && fn.call(el, e, el)  
    })
    return element
}

重绘和回流:
回流:
* 当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow)。
* 每个页面至少需要一次回流,就是在页面第一次加载的时候,这时候是一定会发生回流的,因为要构建render tree。
* 在回流的时候,浏览器会使渲染树中受到影响的部分失效,并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分到屏幕中,该过程成为重绘。

重绘:
* 当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就叫称为重绘。

区别:
* 回流必将引起重绘,而重绘不一定会引起回流
* 页面布局和几何属性改变时就需要回流
* 回流比重绘的代价要更高
* 回流的花销跟render tree有多少节点需要重新构建有关系

优化:
* 为了解决这个问题,可以采用cssText累加的方法

渲染流程有四个主要步骤:
1. 解析HTML生成DOM树 - 渲染引擎首先解析HTML文档,生成DOM树
2. 构建Render树 - 接下来不管是内联式,外联式还是嵌入式引入的CSS样式会被解析生成CSSOM树,根据DOM树与CSSOM树生成另外一棵用于渲染的树-渲染树(Render tree),
3. 布局Render树 - 然后对渲染树的每个节点进行布局处理,确定其在屏幕上的显示位置
4. 绘制Render树 - 最后遍历渲染树并用UI后端层将每一个节点绘制出来

link和@import的区别:
* link是HTML的方式,@import是css的方式
* link最大限度支持并行下载,@import过多嵌套导致串行下载,有时会导致文档样式暂时失效
* 浏览器对link的支持早于@import,可以使用@import对老浏览器隐藏样式

css的引入方式:
1. 行内样式
2. 内部样式表
3. 外部样式表

  • 链接式:
  • 导入式: @import url(“css文件路径”);

JS的引入方式:
1. 内嵌式<script></script>
2. 外部引入<script src="demo01.js" type="text/javascript"></script>
3. JavaScript前缀引入:<a href="javascript:alert('123')">点击我</a>

flex布局和float布局的区别:
相同点:都会脱离文本流弹性布局主要作用是,设置父元素内的多个块元素的排列顺序以及分布方式。
弹性布局与浮动相比,不但可以实现多个块元素共存于一行,而且对父元素没有不好的影响,同时实现子元素等距分隔,或等距环绕并不需要人为的计算。弹性布局会自动计算。浮动元素使用的时候一定要清除浮动

flex布局内部元素的属性:
1. order可以给它一个整数值,在显示上会从小到大排列,可以是负值
2. flex-grow:1(控制长胖) 是能有多宽有多宽(将多余的空间分配开),它也可以是其他的值,如果两个元素中,一个是1,一个是2,那么多余的空间按1:2分配,如果其他的item都不写,只给一个写了flex-grow:1,那就把所有的剩余空间都给这个元素,默认值是0,永远不会变胖
3. flex-shrink:1(控制变瘦),有它的时候在空间不够时,这个元素缩小,值越大缩的越快,如果是0时,就是永远不会变瘦,默认值是1,要瘦大家一起瘦,用于在空间不够时让谁缩小
4. flex-basis:控制基准宽度,默认是auto,不常用
5. align-self:flex-end(flex-start);可以让某一个元素跑到下面

flex:1所有元素等宽flex:auto按照自己的内容来占据大小

CSS语义化
命名:
header 头部/页眉;l
ogo 标志;
nav/sub_nav 导航/子导航;
banner 横幅广告;
main/content 主体/内容;
container/con 容器;
wrapper/wrap 包裹(类似于container)
menu 菜单;sub_menu/second_menu 子菜单/二级菜单;
list 列表;section 分区/分块(类似于div);
article 文章;
aside 侧边栏/广告;
footer 页脚/底部;
title/sub_title 标题/副标题;
news 新闻;
hot 热点;
pro 产品(product);
company 公司;
msg/info 信息(message)/消息;
ads 广告(advertisements);
icon 小图标;copyright 版权;
contact_us 联系我们;
friend_link 友情链接;
tel 联系电话(telephone);
address 地址;

css display 与 opacity 和 visibility的区别:

  1. opacity=0不会改变页面布局
  • 实际上是元素的透明度为0。
  • 子元素 opacity:1 是无效的,元素仍旧无法显示。
  • 绑定的事件仍旧可以触发。
  1. visibility=hidden 不会改变页面布局
  • 使元素不可见。
  • 子元素设置 visibility:visible; 后,子元素会显示,但是父元素不会显示。
  • 绑定的事件不能触发。
  1. display:none 会改变页面布局
  • 元素彻底消失,脱离文档流。
  • 子元素跟随父元素被隐藏,并且无法单独显示。
  • 绑定的事件也无法触发。
  • 无论如何,DOM节点还是存在的,仍旧可以用 js 操作。

text-overflow和overflow的区别:

  • text-overflow:clip/ellipsis;   属性规定当文本溢出包含元素时发生的事情。
  • overflow:hidden; 隐藏所有在超过盒子设定宽度范畴外的内容。

CSS单位:
* rem:依赖根元素HTML的font-size的相对单位
* em:依赖父元素字体大小的相对单位
* px:像素
* mm
* cm
* vw:视口宽度 1vw视口宽度的1%
* vh:视口高度
* vmin:vw和vh中最小的那个,相对于vmax

Position有哪些取值:
1. relative: 脱离文档流,但其在文本流中的位置依然存在。相对于自己本身在正常文档流中的位置进行定位
2. absolute: 脱离文档流,但与relative的区别是其在正常流中的位置不再存在。 绝对定位的元素可以设置外边距(margin),且不会与其他边距合并。相对于最近一级定位不为static的父元素进行定位
3. static(默认值)
4. fixed:会脱离文档流, 相对于浏览器窗口进行定位。
5. sticky:粘滞定位,兼容很差

z-index生效:
只适用于已经定位的元素,即拥有relative,absolute,或者fixed的元素

媒体查询的缺点:
如果在浏览器大小改变时,需要改变的样式太多,那么多套样式代码会很繁琐。媒体查询做不到全面兼容,而且维护和开发成本高。
不换行:
white-space:nowrap;

折叠规则:
两个或多个毗邻的普通流中的块元素垂直方向上的margin会折叠
当一个父元素包含 一个子元素时,它们的上下外边距会发生折叠。
元素自身(空元素)的margin-bottom和margin-top相邻时也会折叠

避免外边距折叠:
* inline-block元素的外边距 不会折叠
* float元素的外边距 不会折叠
* 绝对定位(absolute、fixed)元素的外边距 不会折叠
* 对于父元素与子元素的外边距折叠,除了采用上面的3种方法,还可以为父元素添加overflow:hidden\auto,但由于overflow:hidden\auto会自动计算其元素高度,所以父元素高度会变高。

svg和canvas的区别:
* svg绘制出来的每一个图形的元素都是独立的DOM节点,能够方便绑定事件或用来修改,canvas输出的是一整块画布
* svg输出的图形是矢量图形,可以改参数来自由的放大或者缩小,不会产生锯齿,canvas输出的是标量的画布,就像一张图片一样,放大会失真产生锯齿

伪类和伪元素的区别:
伪类:用于当元素处于某种状态时为其添加对应的样式(:hover/:focus/:link)
伪元素:用于创建一些不存在于DOM树中的元素,为其添加样式(::before/::after)

跨域怎么解决:
同源:如果两个URL的协议、端口、域名完全一致,那么这两个URL就是同源的
跨域:两个URL不同源,但是一个URL可以访问另一个URL中的数据
方法一:CORS
response.setHeader("Access-Control-Allow-Origin", "http://localhost:9990");
方法二:JSONP
给不支持CORS跨域的浏览器使用,比如说IE。给它专门建一个js文件,我们通过构造src形如xxx.com/friends.json?callback=xxxx<script>标签请求这个JS文件,会执行一个回调,回调里面就是想要的数据。
* 优点就是兼容IE,可以跨域;
* 缺点是由于是script标签,所以它读不到AJAX那么精确的状态,不知道状态码,响应头,只知道成功或者失败,只能发GET请求。

rollup和webpack区别
webpack更适合打包组件库,应用程序之类的应用rollup更适合打包纯js的类库webpack拆分代码,按需加载rollup所有资源放在一个地方,一次性加载,利用tree-shaking的特性来剔除项目中未使用的代码,减少冗余,但是webpack2也在逐渐支持

tree-shaketree-shaking原理:
* ES6 Module引入进行静态分析,故而编译的时候正确判断到底加载了那些模块
* 静态分析程序流,判断那些模块和变量未被使用或者引用,进而删除对应代码
* Tree Shaking在去除代码冗余的过程中,程序会从入口文件扫描所有的模块依赖,以及模块的子依赖,而后将它们链接起来造成一个“形象语法树”(AST)。随后,运行所有代码,查看哪些代码是用到过的,做好标记。再将“形象语法树”中没有用到的代码“摇落”。经验这样一个过程后,就去除了没有用到的代码。

webpack中的loader和plugin的作用是什么
webpack是一个模块打包器, 根据每个模块文件之间的依赖关系将所有的模块打包(bundle)起来
loader就是加载器(用来加载某些资源文件)
plugin就是一个插件(就是用来加强功能,就是扩展webpack功能)
loader一般情况下是一对一plugin一般是多对一,也有一对多的,但是比较少见
1. 给webpack一个js文件,它通过一个内置的babel-loader的东西将这个js文件放到webpack中,然后webpack就会输出一个另外的js文件
2. 同样也会引入一个css,它通过style-loader和css-loader两个loader将css文件变成一个style标签(这个标签也是JS生成的,所以在某种程度上来讲它也是一个JS),还有一种情况,它可以通过一个插件MIniCssExtractPlugin将n个CSS文件变成一个CSS文件。也有其他的loader,postcss-loader,scss-loader,less-loader
3. HTML是0个或者一个HTML文件通过HtmlWebpackPlugin这个插件来生成一个新的Html文件,这个HTML文件和 之前的区别就是它自动引入了上面的css和js,它也有loader,markdown-loader,pug-loader
4. 图片loader,url-loader,file-loader。 url-loader实际是在file-loader的基础上进行封装,并且对于设定 limit 大小的图片和字体文件,自动进行base64的转换;

webpack如何按需加载代码:
import(文件路径)就可以实现按需加载,它是一个promise,所以后面可以.thenwebpack如何提高构建速度
1. 使用Tree-shaking和Scope Hoisting来剔除多余代码
2. 使用happypack实现多线程构建(使用多核CPU进行打包),js和webpack都是默认单核的
3. 通过DllPlugin来对那些我们引用但是绝对不会修改的npm包来进行预编译,再通过DllReferencePlugin将预编译的模块加载进来。

webpack 转义出的文件过大怎么办:
1. CommonsChunkPlugin提取通用模块文件,这些模块即使过大也没有关系,可以禁用环缓存
2. 对js,css,图片进行压缩
3. 使用import进行按需加载

DOM和BOM:
1、DOM,文档对象模型(Document Object Model)。2、DOM是 W3C(万维网联盟)的标准,DOM定义了访问HTML和XML文档的标准。W3C DOM由以下三部分组成:
* 核心DOM - 针对任何结构化文档的标准模型
* XML DOM - 针对 XML 文档的标准模型
* HTML DOM - 针对 HTML 文档的标准模型

BOM 是浏览器对象模型。BOM 是为了操作文档出现的接口,就是为了控制浏览器的行为而出现的接口。

Symbol:
用于创建一个独一无二的值, 一个全局唯一的值。
Symbol函数前不能使用new命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象。Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述Symbol 值不能与其他类型的值进行运算,会报错。Symbol 值可以显式转为字符串。另外,Symbol 值也可以转为布尔值,但是不能转为数值。

let s2 = Symbol('尚硅谷'); 
let s3 = Symbol('尚硅谷'); 
console.log(s2 === s3) // false
let s4 = Symbol.for('尚硅谷') 
let s5 = Symbol.for('尚硅谷') 
console.log(s4 === s5) // true

call、 apply、bind三者的用法和区别
* call、apply、bind都是改变this指向的方法
* 三者第一个参数都是this要指向的对象,如果如果没有这个参数或参数为undefined或null,则默认指向全局window。
* call、apply修改this指向并立即执行,bind返回一个新的函数,不会立即执行
* call接受逗号分隔的无限多个参数列表,apply则是接受数组作为参数。 apply和call是一次性传入参数,而bind可以分为多次传入。

setTimeout和setInterval:
setTimeout在执行一次之后就不会再继续执行,setInterval没隔一段时间就会执行。

 window.clearTimeout();//清除
 function func(){
 console.log("print")
 } 
 //定时任务
 var interval = setInterval(func, 2000); //启动,func不能使用括号
 clearInterval(interval );
 //停止
 interval = setInterval(func, 2000); //重新启动即可

promise错误处理的三种方法:
1. then(resolve,reject); then方法中第二个回调,是失败时候做的失败时候做的事
2. 使用catch捕获错误.catch,如果最后一个catch有错误,会无限catch
3. finally捕获,不论成功还是失败,finally中的内容一定会执行,可以在finally中做一些收尾的工作,用法就是.finally

函数防抖和函数节流:
函数防抖:简单点来说就是带着一起做,一段时间内会等,然后带着一起做

function debounce(fn, delay) {
    let timerId = null
    return function() {
        if (timerId) { window.clearTimeout(timerId) }
        timerId = setTimeout(() => {
            fn.apply(this, arguments)
            timerId = null
        }, delay)
    }
}

函数节流:就是一段时间执行完一次之后,就不会执行第二次

function throttle(fn, delay) {
    let canUse = true
    return function() {
        if (canUse) {
            fn.apply(this, arguments)
            canUse = false
            setTimeout(() => {
                canUse = true
            }, delay)
        }
    }
}

想要打印出0-5:

for (let i = 0; i < 6; i++) {
    setTimeout(() => {
        console.log(i)
    }, 1000)
}

//或者
let i
for (i = 0; i < 6; i++) {
    ! function(j) {
        setTimeout(() => {
            console.log(j)
        }, 1000)
    }(i)
}

原型链和继承:
当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它的__proto__隐式原型上查找,即它的构造函数的prototype,如果还没有找到就会再在构造函数的prototype的__proto__中查找,这样一层一层向上查找就会形成一个链式结构,我们称为原型链。Object.prototype.proto === null对象.proto===其构造函数的.prototypeObject.prototype是所有对象的(直接或间接)的原型所有函数都是有Fuction构造的

  1. 基于原型的继承:
function Parent(name1) {
    this.name1 = name1
}
parent.prototype.pMethod = () => {
    console.log(this.name1)
}

function Child(name1, name2) {
    Parent.call(this, name1)
    this.name2 = name2
}
Child.prototype.__proto__ = Parent.prototype
Child.prototype.cMethod = () => {
    console.log(this.name2)
}
  1. 基于class的继承:
class Parent {
    constructor(name1) {
        this.name1 = name1
    }
    pMethod() {
        console.log(this.name1)
    }
}
class Child extends Parent {
    constructor(name2, name1) {
        super(name1)
        this.name2 = name2
    }
    cMethod() {
        console.log(this.name2)
    }
}

不要使用 class,写一个 Person 构造函数

function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.sayHi = function() {
    console.log('你好,我叫' + this.name);
}
let person = new Person('frank', 18)
person.name === 'frank' // true
person.age === 18 // true
person.sayHi() // 打印出「你好,我叫 frank」

请用 class 再实现一次上面的功能

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    sayHi() { console.log('你好,我叫' + this.name); }
}
let person = new Person('frank', 18)
person.name === 'frank' // true
person.age === 18 // true
person.sayHi() // 打印出「你好,我叫 frank」

constructor 是一种用于创建和初始化class创建的对象的特殊方法。使用与运算符判断奇数还是偶数
* 偶数&1 = 0
* 奇数&1 = 1

使用~,>>,<<,>>>,|来取整
* console.log(~~ 6.83) //6 因为位运算符不支持小数
* console.log(6.83 >> 0) //6
* console.log(6.83 << 0) //6
* console.log(6.83 | 0) //6
* console.log(6.83 >>> 0) //6

使用^来交换a b的值
* [a,b] = [b,a]
* a = a^b
* b = b^a
* a = a^b

.join()把数组转为字符串,通过括号中的内容进行分割

target和currentTarget区别:
e.target可以用来实现事件委托,该原理是通过事件冒泡(或者事件捕获)给父元素添加事件监听,e.target指向引发触发事件的元素,如上述的例子中,e.target指向用户点击的li,由于事件冒泡,li的点击事件冒泡到了ul上,通过给ul添加监听事件而达到了给每一个li添加监听事件的效果,而e.currentTarget指向的是给绑定事件监听的那个对象,即ul,从这里可以发现,e.currentTarget=this返回true,而e.target=this返回false。e.currenttarget和e.target是不相等的。
currentTarget始终是监听事件者,而target是事件的真正发出者。

事件冒泡、事件捕获和事件委托:
事件委托依靠的就是事件冒泡和捕获事件冒泡会从当前触发的事件目标一级一级往上传递,依次触发,直到document为止。事件捕获会从document开始触发,一级一级往下传递,依次触发,直到真正事件目标为止如果元素被阻止冒泡了,千万别去用事件委托的方式监听事件,因为事件委托的原理是利用事件冒泡,当冒泡被阻止,就无法监听(addEventListener)了。

addEventListener有三个参数:
* 第一个是事件类型:click,touchstart
* 事件函数,就是在事件发生时要执行的函数
* true/false,true是捕获,false是冒泡

使用stopPropagation阻止事件冒泡数组排序:

//使用递归
let sort = (numbers) => {
    if (numbers.length > 2) {
        let index = minIndex(numbers)
        let min = numbers[index]
        numbers.splice(index, 1)
        return [min].concat(sort(numbers))
    } else {
        return numbers[0] < numbers[1] ? numbers : numbers.reverse()
    }
}

let minIndex = (numbers) => numbers.indexOf(min(numbers))

let min = (numbers) => {
    if (numbers.length > 2) {
        return min([numbers[0], min(numbers.slice(1))])
    } else {
        return Math.min.apply(null, numbers)
    }
}
// 选择排序最终代码


let sort = (numbers) => {
    for (let i = 0; i < numbers.length - 1; i++) {
        let index = minIndex(numbers.slice(i)) + i
        if (index !== i) {
            swap(numbers, index, i)
        }
    }
    return numbers
}


let swap = (array, i, j) => {
    let temp = array[i]
    array[i] = array[j]
    array[j] = temp
}
let minIndex = (numbers) => {
    let index = 0
    for (let i = 1; i < numbers.length; i++) {
        if (numbers[i] < numbers[index]) {
            index = i
        }
    }
    return index
}
// 快速排序
let quickSort = arr => {
    if (arr.length <= 1) {
        return arr;
    }
    let pivotIndex = (arr.length >> 1); //小于或等于指定数字的最大整数
    let pivot = arr.splice(pivotIndex, 1)[0]; //基准拿出来
    let left = [];
    let right = [];
    for (let i = 0; i < arr.length; i++) {
        if (arr[i] < pivot) {
            left.push(arr[i])
        } else {
            right.push(arr[i])
        }
    }
    return quickSort(left).concat(
        [pivot], quickSort(right))
}
// 归并排序(一半一半的排)
let mergeSort = arr => {
    let k = arr.length
    if (k === 1) {
        return arr
    }
    let left = arr.slice(0, Math.floor(k / 2))
    let right = arr.slice(Math.floor(k / 2))
    return merge(mergeSort(left), mergeSort(right))
}
let merge = (a, b) => {
    if (a.length === 0) return b
    if (b.length === 0) return a
    return a[0] > b[0] ? [b[0]].concat(merge(a, b.slice(1))) : [a[0]].concat(merge(a.slice(1), b))
}
// 计数排序(先找出最大的和最小的,开辟长度为(max-min+1)的空间)
let countSort = arr => {
    let hashTable = {},
        max = 0,
        result = []
    for (let i = 0; i < arr.length; i++) { // 遍历数组
        if (!(arr[i] in hashTable)) {
            hashTable[arr[i]] = 1
        } else {
            hashTable[arr[i]] += 1
        }
        if (arr[i] > max) {
            max = arr[i]
        }
    }
    for (let j = 0; j <= max; j++) { // 遍历哈希表
        if (j in hashTable) {
            for (let i = 0; i < hashTable[j]; i++) {
                result.push(j)
            }
        }
    }
    return result
}

如何实现数组去重?

  1. 老方法:
for (let i = 0; i < array.length - 1; i++) {
    let item = array[i]
    for (let j = i + 1; j < array.length - 1; j++) {
        if (array[j] === item) {
            array.splice(j, 1)
            j--;
        }
    }
}

最大的缺点浪费性能,每一个元素都和其他的元素比较了一遍,时间复杂度不友好

  1. 使用Set
let item = [...new Set(array)] 

…扩展运算符
缺点:ES6毕竟是新出的,所以会考虑兼容问题

  1. 使用Map
function noRepeat(arr) {
    let hashMap = new Map()
    let result = new Array()
    for (let i = 0; i < arr.length - 1; i++) {
        if (hashMap.has(arr[i])) {
            hashMap.set(arr[i], false)
        } else {
            hsahMap.set(arr[i], true)
            result.push(arr[i])
        }
        return result
    }
}

这种方法不但可以做到去重,还可以知道是哪些元素重复了
缺点:不能过滤掉重复的Object

  1. 双指针方法
var removeDuplicates = function(nums) {
    let slow = 1,fast = 1
    while(fast<nums.length){
        if(nums[fast-1] !== nums[fast]){
            nums[slow] = nums[fast]
            slow++
        }
        fast++
    }
    return nums.slice(0,slow)
};

创建对象的几种方式:
1. new Object()
2. 字面式创建:let person = {name:‘kaku’}
3. 工厂模式:上面两种方法在使用同一个接口创建多个对象的时候,会产生大量的重复代码
4. function createPerson(name,age,family) {
var o = new Object();o.name = name;o.age = age;o.family = family;o.say = function(){alert(this.name);}return o;}
5. 构造函数模式:function Person(name,age){this.name = name; this.age = age}
6. 原型模式:function Person(){} Person.prototype.name = ‘lu’; Person.prototype.age = 22

Arguments 对象:

  • arguments 是一个对应于传递给函数的参数的类数组对象
  • arguments对象不是一个 Array 。它类似于Array,但除了length属性和索引元素之外没有任何Array属性。例如,它没有 pop 方法。但是它可以被转换为一个真正的Array:
  • Array.from(arguments)
  • [].slice.call(arguments)
  • Array.prototype.call(arguments)
  • […arguments]

new一个对象的时候发生了什么:
1. 新生成一个对象
2. 链接到原型obj.proto= Object.prototype
3. 绑定this
4. 返回新对象

new的实现:

function newGenerater(fn, ...args) {
    const result = new Object();
    result.__proto__ = fn.prototype;
    const res = fn.call(result, ...args);
    return (typeof res === 'object' && res !== null) ? res : result;
}

js中this的指向:
1. fn()this => window/global
2. obj.fn()this => obj
3. fn.call(xx)this => xx
4. fn.apply(xx)this => xx
5. fn.bind(xx)this => xx
6. new Fn()this => 新的对象
7. fn = ()=> {}this => 外面的 this

箭头函数和普通函数的区别:

  • 箭头函数没有this,而是捕获其所在上下文的this来作为自己的this
  • 箭头函数相当于匿名函数,是不能作为构造函数的,所以不能new
  • 箭头函数没有原型属性
  • 箭头函数不能当做generater函数,不能使用yield关键字
  • 箭头函数不绑定arguements,可以使用…操作符来解决

generater:
看起来像一个函数,但是它可以返回多次,使用function*定义,调用的时候可以使用.next()方法,每次遇到yield x;就返回一个对象{value:x,done:true/false},done表示这个generater是否已经执行结束,也可以使用for…of来循环迭代generater对象,这种方式不需要自己判断done,它就像是一个可以记住执行状态的函数

Promise、Generater(可以用作抽奖环节)、Async三者的区别:
async和generator函数主要就是为了解决异步的并发调用使用的 ,直接将参数从then里取出来,相比promise的链式调用,传参更加方便,异步顺序更加清晰在调用一个Generator函数后,并不会立即执行其中的代码,函数会返回一个Generator对象,通过调用对象的next函数,可以获得yield/return的返回值。Generator与async function都是返回一个特定类型的对象:
1. Generator: 一个类似{ value: XXX, done: true }这样结构的Object
2. Async: 始终返回一个Promise,使用await或者.then()来获取返回值

Generator是属于生成器,一种特殊的迭代器,用来解决异步回调问题感觉有些不务正业了。。 而async则是为了更简洁的使用Promise而提出的语法,相比Generator + co这种的实现方式,更为专注,生来就是为了处理异步编程。

手写Ajax:

let request = new XmlHttpRequest()
request.open('GET', "/xxx")
request.onreadystatechange = () => {
    if (request.readyState === 4 && request.status === 200) {
        console.log(request.reponse)
    }
}

readyState的状态:
0 - (未初始化)
1 - (载入)已调用open()方法,正在发送请求
2 - (载入完成)send()方法执行完成,已经接收到全部响应内容
3 - (交互) 正在接受服务器返回的数据
4 - (完成)响应内容解析完成,可以在客户端调用了

中断Ajax请求:
1. 设置超时时间让ajax自动断开
2. 核心是调用XMLHttpRequest对象上的abort方法 request.abort()

不可用abort方法来作为终止对服务器的请求操作,只有当做在前端页面立刻停止执行ajax成功后的方法,因为你执行abort方法后,ajax很可能已经对服务端发送了请求,只是还未返回回馈信息而已。

JS垃圾回收机制:
所有全局变量都不是垃圾,因为在任何地方都可能用到它window的对象也不会被回收TypeScript的never类型是什么:never类型是任何类型的子类型,也可以赋值给任何类型;

ts相比js的优点:

  • TS是JS的超集合
  • 静态类型语言
  • 强类型语言
  • 类型约束,因此更可控、更容易重构、更适合大型项目、更容易维护

EventLoop:
(只有node.js中有这个概念)就是事件循环,实际上是指阶段的转移这个过程。是指浏览器或者node的一种解决JS单线程运行时不会被阻塞的一种机制,就是我们经常使用的异步原理比如说Ajax发起了一个请求,js它只是通知C++去做这件事情,但是从请求到响应肯定需要时间,js不会干等着,会继续执行下面的代码,采用的是一种轮询的机制,但是js在执行下面的代码,所以不是js轮询的,做轮询这件事情的是C++(浏览器核心轮询机制就是用C++写的),总要有一个东西在等着结果,可能是操作系统,等到之后给了浏览器,也可能是浏览器自己在等,只要知道不是js自己在等就好了

EventLoop阶段
1. timers(setTimeout) 计时器实际上是在指定多久以后可以执行某个回调函数,而不是指定某个函数的确切执行时间。当指定的时间达到后,计时器的回调函数会尽早被执行。如果操作系统很忙,或者 Node.js 正在执行一个耗时的函数,那么计时器的回调函数就会被推迟执行。
2. I/O callbacks
3. idle prepare
4. poll (轮询阶段) 可能会停留一段时间, 1. 如果发现计时器的时间到了,就绕回到 timers 阶段执行计时器的回调。 然后再,执行 poll 队列里的回调。
5. check(setImmediate) 这个阶段允许开发者在 poll 阶段结束后立即执行一些函数。如果 poll 阶段空闲了,同时存在 setImmediate(fn2) 【它没有参数】任务,event loop 就会进入 check 阶段。
6. close callbacks

node.js肯定会有两句话,第一句是开启eventloop(),第二句执行JS,两个那个在前是不确定的,因为开启eventloop()是需要时间的,执行JS也要开启一个v8的引擎,也需要时间,谁快谁慢不确定,如果有一个setTimeout(),那么setTimeout()和timer谁先执行是不确定的,遇到setTimeout(fn,1000)时,会将fn放到timers的队列中,并且记录要在第1000ms时执行fn,然后js就继续去做自己的事情去了,eventloop会进入pool阶段开始等,等到1000ms时,立马到check阶段再到timer阶段去执行fn,pool的等待一般来说是有最长时间的,如果这个最长时间到了之后就会到check,到timers,再回到pool继续等,所以事件循环它大部分是在pool阶段等待process.nextTick()这个重要的异步API没有出现在任何一个阶段里,因为从技术上来讲并不是event Loop的一部分。实际上,不管event loop当前处于哪个阶段,nextTick都是在当前阶段后就执行了。nextTick的意思就是当前阶段之后就马上执行event loop在浏览器对应只有两个阶段,一个是宏任务,一个是微任务

chrome中setTimeout,setImmediate,setInterval,I/O,UI渲染是宏任务,
.then,nextTi,Promises,queueMicrotask,MutationObserver微任务
但是如果有new Promise(fn)这个fn是马上执行的,不会放到那个队列里面,相当于写fn

forEach跳出循环

  • forEach中不能使用continue和break
  • forEach中使用return语句的作用只能跳出当前循环,并不能跳出整个循环。
  • 如果需要跳出整个循环,需要throw抛异常
  • 使用arr.some()或者arr.every()替代, some()当内部return true时跳出整个循环, every()当内部return false时跳出整个循环
  • foreach循环时循环对象(数组、集合)被锁定,不能对循环对象中的内容进行增删改操作。

foreEach()方法:

  • 针对每一个元素执行提供的函数。

map()方法:

  • 创建一个新的数组,其中每一个元素由调用数组中的每一个元素执行提供的函数得来。

for in 和for of的区别:
for in遍历数组输出的下标,for of遍历数组输出的是值for-in结构通常对 Array(数组)和Object(对象)都可以使用,而for-of结构在对Object(对象)使用时会报错。for in遍历Object拿到的是键for of遍历Map拿到的是其中的每一项,也可以遍历set

Object和Map的区别:
Object 只能使用字符串类型(现在还有Symbol)的值作为键Map 可以使用任意类型的值作为键Object 可以通过 Object.keys、Object.value、Object.entries、for…in等遍历拿到键或者值Map可以通过 this.keys、this.values、this.entries、for…of等遍历拿到键或者值

object的方法:

  • .assign()
  • .keys()
  • .values()
  • .is(value1,value2)比较两个值是否相同

map的方法:

  • set()
  • get()
  • .has()
  • .clear()
  • delete(key)
  • .size

WeakMap的方法:

  • get()
  • set()
  • has()
  • delete()

Map和WeakMap的区别:
1. Map对象的键可以是任何类型,但WeakMap对象中的键只能是对象引用
2. WeakMap不能包含无引用的对象,否则会被自动清除出集合(垃圾回收机制)。
3. 由于WeakMap的成员随时可能被垃圾回收机制回收,成员的数量不稳定,所以没有size属性。
4. 没有clear()方法

undefined和null的区别:
undefined表示变量声明但未初始化时的值null表示准备用来保存对象,还没有真正保存对象的值会改变

数组的方法:
1. push()
2. pop()
3. shift()
4. unshift()
5. splice()
6. reverse()
7. forEach()
8. sort()

不会改变数组的方法:
1. concat()
2. slice(start,end):提取字符串的某个部分
3. filter():n变少
4. map():n变n

ES6中新的数组方法:

  • forEach()
  • some()函数中返回true时遍历终止,第一个参数是当前元素,第二个是当前元素索引值,第三个是数组
  • find()返回第一个满足条件元素的索引,如果没有返回-1,第一个参数是每一次迭代查找的数组元素,第二个参数是数组元素索引,第三个是被查找的数组
  • filter()
  • isArray()判断是不是数组

数组reduce()方法:回调函数有四个参数:

  • accumulator 累计器 ——累加器累加回调函数的返回值。
  • currentValue 当前值——处理数组的当前元素。
  • currentIndex 当前索引 (可选)
  • array 数组 (可选)

JS重定向的方式:

  • window.location.href = “xxx”
  • window.history.back(-1);
  • window.navigate("");
  • top.location=’’;

HTTP和TCP的关系:
TCP是传输层,而http是应用层http是要基于TCP连接基础上的,简单的说,TCP就是单纯建立连接,不涉及任何我们需要请求的实际数据,简单的传输。http是用来收发数据,即实际应用上来的。HTTP协议中的数据是利用TCP协议传输的,所以支持HTTP也就一定支持TCP

HTTP属于无状态协议(Stateless)。这表示每一个请求之间是没有相关性的。

Cookie V.S. LocalStorage V.S. SessionStorage V.S. Session

  • Cookie V.S. LocalStorage

      1. 主要区别是 Cookie 会被发送到服务器,而 LocalStorage 不会
      2. Cookie 一般最大 4k,LocalStorage 可以用 5Mb 甚至 10Mb(各浏览器不同)
    
  • LocalStorage V.S. SessionStorage

      1. LocalStorage 一般不会自动过期(除非用户手动清除),
      2. 而 SessionStorage 在会话结束时过期(如关闭浏览器)
    
  • Cookie V.S. Session

      1. Cookie 存在浏览器的文件里,Session 存在服务器的文件里
      2. Session 是基于 Cookie 实现的,具体做法就是把 SessionID 存在 Cookie 里
    

cookie的api:

  • addCookie() 添加cookie
  • getCookie() 获取 cookie
  • getName()  获取cookie的名字
  • getValue()  获取cookie的值
  • setvalue()  设置/修改cookie的值
  • setMaxAge()  设置cookie的最大生存空间
  • setPath()  设置cookie的path

LocalStorage的api:

// 存值
1. localStorage.setItem("name","bonly"); 
2. localStorage["name"]="bonly";
3. localStorage.name="bonly";  
//取值:
1. localStorage.getItem("name");
2. localStorage["name"];
3. localStorage.name;
//移除值
1. localStorage.removeItem("name");
2. delete localStorage["name"];
3. delete localStorage.name
//判断是否具有某个key,hasOwnProperty方法
localStorage.hasOwnProperty("name")

数据库的事务概念以及特性:
事务,简短的说就是一组操作要么全部完成,要么全部不做,绝不允许只做其中的一部分操作。

  • 原子性
  • 一致性
  • 隔离性:并发 【对于任意两个并发的事务A和B,在事务A看来,B要么在A开始之前就已经结束,要么在A结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。】
  • 持久性:对数据库中的数据的改变就是永久性的

Vue的虚拟DOM:
虚拟DOM的最终目标是将虚拟节点渲染到视图上。但是如果直接使用虚拟节点覆盖旧节点的话,会有很多不必要的DOM操作。例如,一个ul标签下很多个li标签,其中只有一个li有变化,这种情况下如果使用新的ul去替代旧的ul,因为这些不必要的DOM操作而造成了性能上的浪费。为了避免不必要的DOM操作,虚拟DOM在虚拟节点映射到视图的过程中,将虚拟节点与上一次渲染视图所使用的旧虚拟节点(oldVnode)做对比,找出真正需要更新的节点来进行DOM操作,从而避免操作其他无需改动的DOM。

其实虚拟DOM在Vue.js主要做了两件事

  • 提供与真实DOM节点所对应的虚拟节点vnode
  • 将虚拟节点vnode和旧虚拟节点oldVnode进行对比,然后更新视图

vm = new Vue({data:myData})做了哪些事情
1. 会让vm称为myData的代理(proxy)
2. 会对myData的所有属性进行监控
3. 监控就是为了防止myData的属性变了,vm不知道
4. vm知道了之后就可以调用

Vue的setup():
执行时组件实例尚未被创建,也就是在解析其他自检选项之前被调用接收两个参数,一个是props,context

  1. props:
  • props是响应式的,不能使用ES6进行解构
  • 如果需要解构可以使用setup函数中的toRefs
  1. context {attrs,slots,emit}
  • context 是一个普通的 JavaScript 对象,它暴露三个组件的 property:context.attrs(非响应式对象)、context.slots(插槽)、context.emit(方法)
  • ref 声明基础数据类型:创建一个响应式的基础类型,只能监听number、String、boolean等简单的类型,该属性一旦发生更改,都会被检测到。
  • watchEffect 监听:通过ref或者reactive去创建多个响应式的值,当任何一个值发生改变的时候,立即触发这个函数。

Vue3和Vue2的区别:

  • 移除了beforeCreate()和Created()
  • 新增了setup().
  • 在剩余六个函数之前加入了on
  • 在2.0中我们需要 在多组件外面在套一个div 作为父标签来防止用户意外创建多根组件时发出的警告, 3.0 中我们可以将外
  • Vue2使用import Vue from ‘vue’,然后使用new Vue()来创建实例,Vue3是import { createApp } from “vue”,通过createApp()来创建实例
  • vue3.x需要引入createRouter创建地址路由。createWebHashHistory对应之前的hash,createWebHistory对应之前的history。
  • vue2.x数据存放在data,方法在methods,通过this去调用。但是在vue3.x这些都没有了,所有的代码全部都在 setup 里面实现,你页面需要哪些方法,就要在当前页引入即可。

vue双向绑定是怎么实现的:
使用v-model实现的,它可以实现我绑定一个变量, 在变量发生改变时UI会改变,UI改变时数据会变化
但实际上v-model是v-bind:value和v-on:input的语法糖v-model的本质:在input中写v-model="user.username"就相当于写:value="user.username" @input="user.username = $event.target.value"如果是自定义的话就直接写成$event修改输入框中的值,通过@input修改data中的值,修改data的值时,通过:value修改页面中的值

computed和watch有什么区别:
computed就是计算属性的意思,watch就是监听的意思
computed是用来计算出一个值的,这个值在调用的时候不需要加括号,可以当属性一样直接使用,它会根据依赖自动缓存,依赖不改变的时候值不会重新计算watch有两个选项,一个是immediate,表示是否在第一次渲染的时候执行这个函数,还有一个值是deep,比如说要监听一个对象的变化,是否要看这个对象里面的变化,在方法中会有两个参数,分别是newvVal和oldValVue

响应式原理:

  • options.data会被Vue监听
  • 会被Vue实例代理
  • 每次对data属性的读写都会被Vue监控
  • Vue会在data变化时更新UI

创建Vue实例时,将data中所有property遍历,通过Object.defineProperty把属性全部转为getter、setter,通过观察者模式(watcher),在调用setter时(修改数据时),通知依赖更新,但是Vue不能检测到对象属性的添加或者删除,这个时候要使用Vue.setvm.$set可以搞定
1. 当一个值被读取时进行追踪, proxy 的 get 处理函数中 track 函数记录了该 property 和当前副作用。
2. 当某个值改变时进行检测, 在 proxy 上调用 set 处理函数
3. 重新运行代码来读取原始值, trigger 函数查找哪些副作用依赖于该 property 并执行它们。
4. target:要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
5. handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。

我们使用了Proxy ,Proxy是一个对象,它包装了另一个对象,并允许你拦截对该对象的任何交互。const p = new Proxy(target, handler)Object.defineProperty只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。

Vue 2.x里,是通过 递归 + 遍历 data 对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象是才是更好的选择,新增的属性还行通过set方法来添加监听,有一定的局限性。

vue3主要采用的Proxy特性,相比之下可以劫持整个对象,并返回一个新的对象

v-show和v-if的区别:

  • v-if是真正的条件渲染,切换过程中条件块内的事件监听和子组件适当的被销毁和重建
  • v-if是惰性的,初始条件为假时什么也不做,直到第一次变为真的时候才会开始渲染条件块
  • v-show使用的是display,总是被渲染,只是是否被显示的问题
  • v-if有切换开销,v-show有渲染开销

VueRouter 你怎么用的?

  1. 背下文档第一句:Vue Router 是 Vue.js 官方的路由管理器。
  2. 说出核心概念的名字和作用:History 模式/导航守卫/路由懒加载
  3. 说出常用 API:router-link/router-view/this.$router.push/this.$router.replace/this.$route.paramsthis.$router.push('/user-admin')this.$route.params

<router-link to="/1"></router-link>用来切换内容
<router-view></router-view>用来显示内容
const router = new VueRouter({})的里面写上mode:"history"就可以切换成history模式
new Vue里面写上router
Vue Router路由的作用就是分发请求,路由表就是存储路径的表

模式:

  1. hash模式:任何情况下都可以用hash来做前端路由,缺点就是SEO不友好(根源就是服务器收不到hash),比如baidu.com/#1,百度不会将#之后的内容发给服务器,这就导致不论访问/#1还是/#2,都会被认为是访问baidu.com,但是这两个页面内容是不一样的,可只有一个url,也就是都会展示默认路由/,在2019年的时候谷歌新出的功能叫hashbang,它可以让谷歌认识#后面的东西,前提是需要在#后面加上感叹号,但实际上也没有什么太大的作用 window.location.hash.substr(1)
  2. history模式:仅在后端将所有前端路由都渲染到同一个页面的时候,且这个页面不能是404,就是不管请求什么,都得到同一个页面的时候可以使用。比如说codesandbox就实现了。缺点就是IE8以下不支持。 使用window.location.pathname来获取链接以后的内容,比如/1,但是这个时候每次点击会自动刷新整个页面,使用window.history.pushState(null,page{href},href)可以像修改哈希一样,不会导致页面的变化。 让所有页面指向同一个页面的原因是用户喜欢刷新,如果没有定位到相应的页面,就会进入到404页面
  3. memory模式:把路径存放到localStorage中,放在了用户看不见的地方,这个模式适合用来做非浏览器,缺点就是没有url,把url复制好想给别人看的时候,进入的不是一样的位置,而是一个初始状态

上面两种都是把路径存放在url上面,一个用的是url的哈希,一个用的是url的路径,不是放在url的就是memory模式,放在哪里并没有关系,只是我们前端会把它放在localStorage中 window.localStorage.getItem(‘xxx’)

路由守卫(导航守卫)是什么?
路由跳转是一个大的过程,这个大的过程分为跳转前中后等等细小的过程,在每一个过程中都有一函数,这个函数能让你操作一些其他的事儿的时机,这就是导航守卫。

  • 全局前置守卫
  • 全局解析守卫
  • 全局后置钩子
  • 路由独享守卫
  • 组件内的守卫

路由懒加载怎么做:
不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件import('./Foo.vue') //返回Promise

watch 和 computed 和 methods 区别是什么?

  1. computed 和 methods 相比,最大区别是 computed 有缓存:如果 computed 属性依赖的属性没有变化,那么 computed 属性就不会重新计算。methods 则是看到一次计算一次。
  2. watch 和 computed 相比,computed 是计算出一个属性(废话),而 watch 则可能是做别的事情(如上报数据)。

生命周期钩子:
要特别说明哪个钩子里请求数据,答案是 mounted

beforeCreate/created/mounted/updated/distroyedVue 如何实现组件间通信?

  1. 父子组件:$emit(‘xxx’,data) $on(‘xxx’,function(){})
  2. 爷孙组件:使用两次 v-on 通过爷爷爸爸通信,爸爸儿子通信实现爷孙通信
  3. 任意组件:使用 eventBus = new Vue() 来通信,eventBus. o n 和 e v e n t B u s . on 和 eventBus. oneventBus.emit 是主要API
  4. 任意组件:使用 Vuex 通信
  5. props
  6. v-model
  7. provide inject

v-bind和v-model的区别:

  • v-bind用来绑定数据和属性以及表达式,缩写为’:’
  • v-model使用在表单中,实现双向数据绑定的,在表单元素外使用不起作用

:model和v-model的区别:
v-model是内置的数据双向绑定。
:model是v-bind:model的缩写,<child :model="msg"></child>这种只是将父组件的数据传递到了子组件,并没有实现子组件和父组件数据的双向绑定。当然引用类型除外,子组件改变引用类型的数据的话,父组件也会改变的。

Vuex 你怎么用的?

  1. 背下文档第一句:Vuex 是一个专为 Vue.js 应用程序开发的状态管理工具
  2. 说出核心概念的名字和作用:State/Getter/Mutation/Action/Module

state => 基本数据
getters => 从基本数据派生的数据
mutations => 提交更改数据的方法,同步!
actions => 像一个装饰器,包裹mutations,使之可以异步。
modules => 模块化Vuex这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。

vite和vue-cli的区别:

  • 使用vue-cli创建项目:vue create demo,使用Webpack打包
  • 使用Vite搭建项目:使用rollup打包,vite的快就是使用浏览器支持的ES Module的方式(即使用import的方式来导入模块)避免开发环境下的打包,从而提升开发速度,支持ES Module的现代浏览器使用script type="module"的方式加载模块代码,使用rollup打包由于不需要使用babel将request转化为require以及一些相应的辅助函数,所以它的打包体积更小
  • vite开发模式下不需要打包可以直接运行
  • vue-cli开发模式下必须对项目打包才可以运行

vue会开启一个测试的服务器,它会拦截浏览器发送的请求,浏览器会想服务器发送请求,获取相应的模块,vite会对浏览器不识别的模块进行处理,比如import单文件组件的时候,就是后缀名是.vue的文件时,会在服务器上对.vue的文件编译,把编译的结果返回给浏览器,所以vite有以下的优点:

  • 快速冷启动:不需要打包
  • 按需编译
  • 模块热更新,并且它的性能和模块总数无关

vue和jquery的区别:
jQuery是使用选择器($)选取DOM对象,对其进行赋值、取值、事件绑定等操作,其实和原生的HTML的区别只在于可以更方便的选取和操作DOM对象,而数据和界面是在一起的。比如需要获取label标签的内容:$("lable").val();,它还是依赖DOM元素的值。

Vue则是通过Vue对象将数据和View完全分离开来了。对数据进行操作不再需要引用相应的DOM对象,可以说数据和View是分离的,他们通过Vue对象这个vm实现相互的绑定。这就是传说中的MVVM。

JavaScript和Java的区别:

  • Java是面向对象的语言,只要是面向对象的语言,就会有三大特性:封装,继承,和多态
  • JavaScript是基于对象的,因为它没有多态的这个概念

MVC、MVVC、MVP的异同:
MVC:

  • 视图(View):用户界面。View 传送指令到 Controller
  • 控制器(Controller):业务逻辑。Controller 完成业务逻辑后,要求 Model 改变状态
  • 模型(Model):数据保存。Model 将新的数据发送到 View,用户得到反馈

MVP:

  1. 各部分之间的通信,都是双向的。
  2. View 与 Model 不发生联系,都通过 Presenter 传递。
  3. View 非常薄,不部署任何业务逻辑,称为"被动视图"(Passive View),即没有任何主动性,而 Presenter非常厚,所有逻辑都部署在那里。

MVVM:
MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。唯一的区别是,它采用双向绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然。Angular 和 Ember 都采用这种模式。

Vue不是MVVM框架:
Model变更触发View更新必须通过ViewModel (Vue实例)。----- set时触发依赖,异步更新模板
而View变更后触发Model也必须通过ViewMode。 ---------利用v-model指令 , input中用户手动输入监听@input事件,更改数据。

以上是MVVM的思想。当然,Vue也是按照这样的设计的,但是vue中添加了一个属性ref,通过ref可以拿到dom对象,通过ref直接去操作视图。
这一点上,违背了MVVM。

在setup函数中,可以使用ref函数,用于创建一个响应式数据,当数据发生改变时,Vue会自动更新UI

setup(){        
	const mycount = ref(2);
	const changeMyCount = ()=>{            
		mycount.value = mycount.value + 2 ;        
	}
}

CDN(内容分发网络)的作用
就是将网站内容发布到最接近用户的边缘节点,使网民可以就近取得所需内容,提高网民访问的速度和成功率,同时还可以保护原网站,解决由于地狱,带宽,运营商接入等问题带来的访问延迟高的问题,有效帮助站点提升访问速度架构主要分为两个部分,中心部分和边缘部分,中心部分负责全局负载均衡,边缘部分指异地节点,CDN分发的载体,有cache和负载均衡器等组成CDN优势:节省成本,稳定安全,操作简单,强大快速CDN还可以用于网站加速,存储分发,视频点播,视频直播

SSR:
服务端渲染就是将组件或页面通过服务器生成HTML字符串,再发送到浏览器,最后将它们混合为客户端上完全交互的应用程序优势:更利于SEO,使用了Vue或者React等框架以后,页面中大部分DOM都是在客户端根据JS动态生成,这时搜索引擎可以获取的内容很少,而使用ssr,服务器端发送到客户端的就是HTML字符串,这样搜索引擎就会获取到重要内容,由于它不依赖于JS文件了,所以更有利于首屏的渲染,减少用户等待时间,用户体验会比较好局限:由于渲染移到了服务端去做,这样服务端的压力就会比较大,尤其是在高并发访问的时候会占用大量的服务端CPU资源

SSL证书加密方式:

  1. 对称秘钥算法:加密解密使用相同的秘钥,IDEA(分组加密),RC4(序列加密)
  • 无需进行密钥交换的场景,如内部系统,事先就可以直接确定密钥
  • 防止明文传输数据被窃取的
  • 加解密速度快,适合数据内容比较大的加密场景
  1. 非对称秘钥算法,又称为公钥加密算法, A发送,B接收,A想确保消息只有B看到,需要B生成一对公私钥,并拿到B的公钥。于是A用这个公钥加密消息,B收到密文后用自己的与之匹配的私钥解密即可。反过来也可以用私钥加密公钥解密。 典型的算法有RSA,DSA,DH;适用于需要密钥交换的场景,如互联网应用,无法事先约定密钥
  2. 散列算法: 是指把文件内容通过某种公开的算法,变成固定长度的值(散列值), 不能从散列值变成原文。 散列变换通常用于验证原文是否被篡改, MD5,SHA,Base64,CRC等。数据库中密码存放

WebSocket协议:
http协议只能是客户端发起请求,服务端进行响应,做不到服务器主动向客户端推送信息,这时就有了WebSocket,属于服务器推送技术的一种,特点是:
1. 建立在 TCP 协议之上,服务器端的实现比较容易。
2. 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
3. 数据格式比较轻量,性能开销小,通信高效。
4. 可以发送文本,也可以发送二进制数据。
5. 没有同源限制,客户端可以与任意服务器通信。
6. 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

  • WebSocket 对象作为一个构造函数,用于新建 WebSocket 实例。var ws = new WebSocket('ws://localhost:8080'); 执行上面语句之后,客户端就会与服务器进行连接。
  • readyState属性返回实例对象的当前状态,共有四种。
  • 实例对象的onopen属性,用于指定连接成功后的回调函数。
  • 实例对象的onclose属性,用于指定连接关闭后的回调函数。
  • 实例对象的onmessage属性,用于指定收到服务器数据后的回调函数。
  • 实例对象的send()方法用于向服务器发送数据。

首先介绍一下HTTP
HTTP 协议,全称Hyper Text Transfer Protocol,超文本传输协议,它是一种规范,完成从客户端到服务器端等一系列运作流程。HTTP不同版本特点

  • 0.9:请求方式只支持GET请求
  • 1.0:请求方式增加了post,put,options等,增加了状态码,用来明确错误类型
  • 1.1:头信息是二进制,数据可以是二进制也可以是文本,增加了持久链接(之后默认都是持久链接)和管道机制
  • 2.0:二进制传输,多路复用,头部压缩

持久连接的优点:

  • 较少的CPU和内存的使用(由于同时打开的连接的减少了)
  • 允许请求和应答的HTTP管线化
  • 降低拥塞控制(TCP连接减少)
  • 减少了后续请求的延迟(无需再进行握手)
  • 报告错误无需关闭TCP连接

保证持久链接使用的是cookie和session,请求头部中有一个字段叫做keep-alive,http无状态(对每一次交互是没有记忆能力的)就是因为每次的请求响应都是一次TCP的请求和响应,这一次和下一次信息是不共享的,让它具有记忆能力就产生了cookie和session。

http2.0头部压缩:
使用的压缩算法是HPACK, 它使用一份索引表来定义常用的 HTTP Header。把常用的 HTTP Header 存放在表里。请求的时候便只需要发送在表里的索引位置即可。例如 :method=GET 使用索引值 2 表示,:path=/index.html 使用索引值 5 表示。 HPACK 不仅仅通过索引键值对来降低数据量,同时还会将字符串进行霍夫曼编码来压缩字符串大小。

http和https:

  • http超文本传输协议,一种简单的请求-响应协议,是基于TCP的,建立连接后,浏览器和服务器进程就可以通过套接字接口访问TCP。https是以安全为目标的http协议,在http基础上加通过加密传输和身份认证的方式保证了传输过程的安全性
  • http端口号是80,https端口号是443
  • HTTPS 协议需要到数字认证机构(Certificate Authority, CA)申请证书,一般需要一定的费用。
  • HTTP 页面响应比 HTTPS 快,主要因为 HTTP 使用 3 次握手建立连接,客户端和服务器需要握手 3 次,而 HTTPS 除了 TCP 的 3 次握手,还需要经历一个 SSL 协商过程。

客户端信任第三方证书的原因:
假设中间人篡改了证书原文,但是没有CA机构的秘钥,所以也无法得到加密后的签名,也就无法篡改签名,客户端收到证书后发现原文和签名解密后的值不一样,就知道证书被篡改了,证书不可信,就会终止向服务端发送信息上述过程说明证书无法被篡改,我们考虑更严重的情况,例如中间人拿到了 CA 机构认证的证书,它想窃取网站 A 发送给客户端的信息,于是它成为中间人拦截到了 A 传给客户端的证书,然后将其替换为自己的证书。此时客户端浏览器收到的是被中间人掉包后的证书,但由于证书里包含了客户端请求的网站信息,因此客户端浏览器只需要把证书里的域名与自己请求的域名比对一下就知道证书有没有被掉包了。

请求的几种方式:

  • OPTIONS 返回服务器所支持的请求方法
  • GET 向服务器获取指定资源
  • HEAD 与GET一致,只不过响应体不返回,只返回响应头
  • POST 向服务器提交数据,数据放在请求体里
  • PUT 与POST相似,只是具有幂等特性,一般用于更新
  • DELETE 删除服务器指定资源
  • TRACE 回显服务器端收到的请求,测试的时候会用到这个
  • CONNECT 预留,暂无使用

POST 请求的过程:
浏览器请求TCP连接(第一次握手)
服务器答应进行 TCP 连接(第二次握手)
浏览器确认,并发送 POST 请求头(第三次握手,这个报文比较小,所以 HTTP 会在此时进行第一次数据发送)
服务器返回100 Continue响应浏览器发送数据服务器返回 200 OK响应

GET 请求的过程:
浏览器请求 TCP 连接(第一次握手)
服务器答应进行 TCP 连接(第二次握手)
浏览器确认,并发送 GET 请求头和数据(第三次握手,这个报文比较小,所以 HTTP 会在此时进行第一次数据发送)服务器返回 200 OK响应

get和POST的区别:
1. 本质上一样,只是http为了不同的分工而规定的两种请求方式,http的底层是TCP/IP,所以get和post的底层也是TCP/IP

  1. 作用不同:get多用于从服务器获取资源,post一般向服务端提交资源
  2. 参数传递方式不同:get提交的数据在地址栏中可以看见,post提交的数据可以在开发者工具中看到,放到了叫做from data中
  3. 安全性不同:get和post更不安全,它直接暴露在url上,从传输的角度来说,都不安全,因为http在网络上是明文传输的,只要抓包都能获取完整信息,想要安全只能加密传输也就是使用HTTPS
  4. 长度限制不同:get请求,http规范对url的长度没有限制,只是不同的浏览器对它做了限制(一般上url长度不要超过2kb就好)post请求也没有长度限制,限制它的是服务器的处理能力与存储大小,还有就是web容器的限制,比如Tomcat默认2M
  5. 参数数据类型不同:get只接受ASCII字符,post支持更多的编码类型
  6. 缓存机制不同:GET 请求会被浏览器主动cache,而 POST 不会,除非手动设置。GET 请求参数会被完整保留在浏览器历史记录里,而 POST 中的参数不会被保留。GET 在浏览器回退时是无害的,而 POST 会再次提交请求。
  7. 时间消耗不同:get产生一个tcp数据包,post会产生两个数据包(第一次发送人的是header,返回100,第二次发送data,返回200,但是火狐浏览器是一个数据包)

put和post请求的区别:

  • post

用于提交请求,可以更新或者创建资源,是非幂等的    
在用户注册时,每次提交都是创建一个用户账号,此时用post

  • put

用于向指定的url传送更新资源,是幂等的    
还是用户模块,比如修改密码,虽然提交的还是账户名和密码,但是每次提交都只是更新该用户密码,每次请求都只是覆盖原型的值,此时用put幂等:如果每次都是同样的结果

HTTP 缓存有哪几种:

  1. ETag:有请求,通过对比浏览器和服务器资源的特征来决定是否要发送文件内容,协商缓存(304)
    请求资源时,把用户本地该资源的 etag 同时带到服务端,服务端和最新资源做对比。如果资源没更改,返回304,浏览器读取本地缓存。如果资源有更改,返回200,返回最新的资源。
  2. Expires,设置过期时间(绝对时间),但用户可以更改自己本地时间,强缓存( 可能存在客户端时间跟服务端时间不一致的问题, 建议Expires结合Cache-Control一起使用)
  3. CacheControl:max-age = 3600s,是设置过期时长(相对时间),无请求,强缓存(http1.1引入的)属性:
  • no-cache:先发送请求,与服务器确认资源是否被更改
  • no-store:不允许缓存
  • private:只能被单个用户缓存,不允许代理缓存
  • public:可以在任何客户端中缓存

浏览器缓存:
就是http协议定义的缓存机制

浏览器输入url后,发生了什么:
1. 解析url
2. DNS解析
3. 浏览器与网站建立TCP连接(三次握手)
4. 请求和传输数据
5. 浏览器渲染页面

  • 浏览器会解析html源码,然后创建一个 DOM树。
  • 浏览器解析CSS代码,计算出最终的样式数据,形成css对象模型CSSOM。
  • 利用DOM和CSSOM构建一个渲染树(rendering tree)。
  • 浏览器就根据渲染树直接把页面绘制到屏幕上。

http的报文格式:首部内容由以下数据组成:

  • 请求行:请求方法、请求URI、HTTP版本
  • 状态行:标明响应结果的的状态码,原因短语和HTTP版本。
  • 首部字段:一般是通用首部、请求首部、响应首部和实体首部。
  • 其他

报文首部

首部:在客户端和服务器处理时起重要作用的信息几乎都在这边
主体:所需要的用户和资源的信息都在这边

1.1 HTTP请求报文

  • 请求行:方法、URI、HTTP版本
  • 请求头:首部字段(请求首部字段、通用首部字段、实体首部字段)
  • 请求体:发送的数据

1.2 HTTP响应报文

  • 响应行:HTTP版本号、状态码、原因短语
  • 响应头:响应首部字段、通用首部字段、实体首部字段
  • 响应体:响应的数据

报文主体和实体主体的差异

  • 报文是HTTP通信中的基本单位,由8位组字节流组成,通过HTTP通信传输。
  • 实体作为请求或相应的有效何在数据(补充项)被传输,其内容由实体首部和实体主体组成。

协议层数:
应用数据报->传输层报文段->ip成组->数据链路层封装成帧->物理层比特流

TCP和UDP的区别:
1、TCP面向连接 (如打电话要先拨号建立连接); UDP是无连接 的,即发送数据之前不需要建立连接
2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,不重复,不丢失,不失序,且按序到达;UDP尽最大努力交付,即不保证可靠交付Tcp通过校验和,重传控制,序号标识,滑动窗口、确认应答实现可靠传输。如丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。
3、UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。
4.每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信5、TCP对系统资源要求较多,UDP对系统资源要求较少。拥塞控制和流量的区别:拥塞控制往往是一种全局的,防止过多的数据注入到网络中,而TCP连接的断点只要不能收到对方的确认信息,猜想在网络中发生了拥塞,但是并不知道发生在了什么地方,因此,流量控制往往是指点对点通信量的控制,是端到端的问题

TCP拥塞控制的方法:
1. 慢开始
2. 拥塞避免
3. 快恢复
4. 快重传

为什么UDP( 传输控制协议))有时比TCP( 用户数据报协议)更有优势?
UDP以其简单、传输快的优势,在越来越多场景下取代了TCP,如实时游戏。
(1)网速的提升给UDP的稳定性提供可靠网络保障,丢包率很低,如果使用应用层重传,能够确保传输的可靠性。
(2)TCP为了实现网络通信的可靠性,使用了复杂的拥塞控制算法,建立了繁琐的握手过程,由于TCP内置的系统协议栈中,极难对其进行改进。

TCP应用:
文件传输,发送或接收邮件,远程登录UDP应用:即时通信(QQ聊天),在线视频,网络语音通话CPU缓存:CPU与内存之间的临时数据交换器,它的出现是为了解决CPU运行处理速度与内存读写速度不匹配的矛盾——缓存的速度比内存的速度快多了。 缓存往往都是RAM(断电即掉的非永久储存),它们的作用就是帮助硬件更快地响应。

TCP沾包的解决方案:

  • 约定数据包长度,即发送端和接收端约定一样的发送和接收的数据包长度,这样可以清晰的获取到我们需要的数据;
  • 使用分隔符,比如smtp协议就是在发送时,使用\r\n为分隔符,但如果我们要发送的数据中也有\r\n呢,就容易搞混淆,所以不是特别推荐;
  • 在每个数据包的开头利用2个或者4个字节填充整个数据包的长度,这样接收端可以先接收2个或者4个字节,就可以准确的知道真正的数据包是多长,从而正确获取需要的数据;

CPU的三级缓存:

  • 一级缓存:CPU的第一层级的高速缓存,主要当担的工作是缓存指令和缓存数据。(最重要, 就是说在一级缓存中,CPU可以找到需要数据的80%,其他20%可以在二级或三级缓存以及内存中找到。)
  • 二级缓存: CPU的第二层级的高速缓存, 而二级缓存的容量会直接影响到CPU的性能,二级缓存的容量越大越好。
  • 三级缓存: CPU的第三层级的高速缓存,其作用是进一步降低内存的延迟,同时提升海量数据量计算时的性能。和一级缓存、二级缓存不同的是,三级缓存是核心共享的,能够将容量做的很大。成本依次降低

什么是XSS( 跨站脚本攻击),如何预防

  • 正常用户 A 提交正常内容,显示在另一个用户 B 的网页上,没有问题。
  • 恶意用户 H 提交恶意内容,显示在另一个用户 B 的网页上,对 B 的网页随意篡改。

造成 XSS 有几个要点:

  1. 恶意用户可以提交内容
  2. 提交的内容可以显示在另一个用户的页面上
  3. 这些内容未经过滤,直接运行在另一个用户的页面上

预防:
不要让内容原样输出就可以了
1、编码,就是转义用户的输入,把用户的输入解读为数据而不是代码
2、校验,对用户的输入及请求都进行过滤检查,如对特殊字符进行过滤,设置输入域的匹配规则等。

什么是CSRF( 跨站点请求伪造),如何预防
攻击者在用户已经登录目标网站之后,诱使用户访问一个攻击页面,利用目标网站对用户的信任,以用户身份在攻击页面对目标网站发起伪造用户操作的请求,达到攻击目的。

避免方法:

  • CSRF 漏洞进行检测的工具,如 CSRFTester、CSRF Request Builder…
  • 验证 HTTP Referer 字段
  • 添加并验证 token
  • 添加自定义 http 请求头
  • 敏感操作添加验证码
  • 使用 post 请求

操作系统32位和64位的区别:
CPU一次数据处理的能力是32位的还是64位的,涉及到的是处理器的运算位数

  • 要求配置不同: 64位操作系统只能安装在64位电脑上(CPU必须是64位的)。同时需要安装64位常用软件以发挥64位(x64)的最佳性能。
  • 运算速度不同: 64位CPU 通用寄存器 的数据宽度为64位, 处理器一次可提取64位数据
  • 寻址能力不同: 地址使用的是特殊的整数,因此一个ALU(算术逻辑运算器)和寄存器可以处理更大的整数,也就是更大的地址

进程和线程:
1. 定义

  • 进程是系统进行资源分配和调度的一个独立单位。每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。由于进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全。
  • 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。

区别

  • 空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
  • 资源:同一个进程内的线程共享本进程的资源,比如说CPU、内存等等,但是进程之间的资源是相互独立的
  • 调度:线程是处理机调度的基本单位,进程不是

优缺点

  • 线程执行开销小,但不利于资源的管理和保护
  • 进程执行开销大,但利于资源的管理和保护

多进程使用
对资源的管理和保护要求高,不限制开销和效率时时使用

多线程使用
要求效率高,频繁切换,对资源的管理和保护要求不是很高时使用

进程通信方式:
1. 直接交流
2. 间接交流
3. 消息传递
4. 消息队列
5. 先进先出队列
6. 管道
7. 共享内存

CPU内部结构:
寄存器、控制器、运算器

状态码304和301的区别:
1. 301:官方示意永久移动,表示请求的网页被永久移动到了新的位置,服务器返回这个响应会重定向到新的网址
2. 304:未修改,自从上次请求之后,请求的网页未修改过,但是请求者应继续使用原有的位置来进行以后的请求。

在浏览器的性能优化当中,我们为了提升页面的打开速度,经常会将一些不长修改的文件,比如css文件,js文件,以及一些图片文件做缓存,以节省下载速度。

浏览器的缓存有两种模式,一种是强缓存,一种是协商缓存。
强缓存命中时不会向服务器发送请求。
而协商缓存回想浏览器发送请求,但是浏览器不会发送返回的数据,而是读取本地缓存,这时,命中协商缓存的请求返回码就是304,表示使用本地缓存中的文件,不从服务器获取数据。

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值