JSV5
1、vue 双向绑定的原理
通过object.defineProperty()方法来劫持属性的getter和setter,当Observer监听到属性变化时,就会发布消息给订阅者Watcher,指令解析器Compile会对每个节点元素进行扫描和解析,将相关指令对应初始化成订阅者watcher,替换模板的数据或者绑定相应的函数,当订阅者watcher接收到属性变化时,就会执行对应的更新函数,从而更新视图,实现双向绑定。
2、需要使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点。
作用主要是为了高效的更新虚拟DOM
3、使用$nextTick这个回调,让修改后的data值渲染更新到dom元素之后在获取
4、vue-loader是基于webpack的一个的loader,解析和转换 .vue 文件,提取出其中的逻辑代码 script、样式代码 style、以及 HTML 模版 template,再分别把它们交给对应的 Loader 去处理,核心的作用,就是提取,划重点。
5、keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。
6、对vue生命周期的理解?
Vue 实例在被创建时经过一系列的初始化过程。 总共分为8个阶段创建前/后,载入前/后,更新前/后,销毁前/后。
创建前/后: 在beforeCreated阶段,vue实例的挂载元素$el和**数据对象**data都为undefined,还未初始化。在created阶段,vue实例的数据对象data有了,$el还没有。
载入前/后:在beforeMount阶段,vue实例的$el和data都初始化了,但还是挂载之前为虚拟的dom节点,data.message还未替换。在mounted阶段,vue实例挂载完成,data.message成功渲染。
更新前/后:当data变化时,会触发beforeUpdate和updated方法。
销毁前/后:在执行destroy方法后,对data的改变不会再触发周期函数,说明此时vue实例已经解除了事件监听以及和dom的绑定,但是dom结构依然存在。
7、什么是vuex?以及相关属性
Vuex是通过全局注入store对象,来实现组件间的状态共享。
vuex五大核心属性:state,getters,mutations,actions,module
state:存储数据,存储状态;在根实例中注册了store 后,用 this.$store.state 来访问;对应vue里面的data;存放数据方式为响应式,vue组件从store中读取数据,如数据发生变化,组件也会对应的更新。
getters:可以认为是 store 的计算属性,它的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
mutations:包含了很多方法 用于更改 Vuex 的 store 中的状态。
actions:包含任意异步操作,通过提交 mutation 间接更变状态。
module:将 store 分割成模块,每个模块都具有state、mutation、action、getter、甚至是嵌套子模块。
视图通过点击事件,触发mutations中方法,可以更改state中的数据,一旦state数据发生更改,getters把数据反映到视图。
那么actions,可以理解处理异步,而单纯多加的一层。
既然提到了mutions actions这时候 就不得不提commit,dispatch
在vue例子中,通过click事件,触发methods中的方法。当存在异步时,而在vuex中需要dispatch来触发actions中的方法,actions中的commit可以触发mutations中的方法。同步,则直接在组件中commit触发vuex中mutations中的方法。
为什么vuex中要通过mutations修改state,而不是直接修改state?
因为state是实时更新的,mutations无法进行异步操作,而如果直接修改state的话是能够异步操作的,当你异步对state进行操作时,还没执行完,这时候如果state已经在其他地方被修改了,这样就会导致程序存在问题了。所以state要同步操作,通过mutations的方式限制了不允许异步。
Vue-router原理
vue-router通过hash与History interface两种方式实现前端路由,更新视图但不重新请求页面”是前端路由原理的核心之一。
hash模式url里面永远带着#号,我们在开发当中默认使用这个模式。那么什么时候要用history模式呢?如果用户考虑url的规范那么就需要使用history模式,因为history模式没有#号
在history下,是h5推出的新api,你可以自由的修改path,当二跳刷新时,如果服务器中没有相应的响应或者资源,会报404
8、vue2.0和vue3.0区别
8.1.项目目录结构变化
移除了配置文件目录 例如config和build文件夹 配置文件从config/index.js 挪到了vue.config.js
移除了static静态文件夹,新增public文件夹,并把index.html移动到public中
8.2.数据监听
2.0基于object.defineProperty()来挟持属性的getter和setter 当给一个对象新增属性时 这个对象的所有订阅者watcher都会重新运行,而3.0是基于Proxy来按需监听 减少内存占用
proxy其实就是在对目标对象的操作之前提供了拦截,可以对外界的操作进行过滤和改写,修改某些操作的默认行为,这样我们可以不直接操作对象本身,而是通过操作对象的代理对象来间接来操作对象,达到预期的目的~
9、vue组件中的data为何必须是一个function
首先需要创建一个组件构造器,然后注册组件。注册组件的本质其实就是建立一个组件构造器的引用。使用组件才是真正创建一个组件实例。所以,注册组件其实并不产生新的组件类,但会产生一个可以用来实例化的新方式。
vue组件中data值不能为对象,因为对象是引用类型,组件可能会被多个实例同时引用。如果data值为对象,将导致多个实例共享一个对象,其中一个组件改变data属性值,其它实例也会受到影响。
data为函数,通过return 返回对象的拷贝,致使每个实例都有自己独立的对象,实例之间可以互不影响的改变data属性值。
10、computed和watch的区别
如果没有computed对属性进行计算的话 属性计算可能需要用表达式的方式放在模板上执行 这样模板就显得过重 而且在页面中大量使用表达式去处理复杂的逻辑数据时 维护性不友好
10.1 computed是计算一个新的属性 并将其挂载在vue实例上 而watch是监听已经存在且已挂载在实例上的数据
10.2 computed具有缓存性,只有依赖发生变化后,第一次访问computed属性才会计算新值 而watch则是当数据发生变化便会调用执行函数
10.3 computed适用一个数据被多个数据影响,而watch适用一个数据影响多个数据;
computed原理
https://segmentfault.com/a/1190000018821652
11、为何vue数组通过下标方式无法改变视图更新
vue对数组只是observe了每个元素的值 数组的下标并没有被监听 所以通过下标是不能更新视图 而数组方法能够响应式 是因为对数组的方法进行了def操作。可能仅仅只需要改变数组的一个值而已 如果使用下标方式 会对数组的每一项进行监听 当遇到一个数组较大时 对性能是一个比较大的损耗
12、从URL输入到页面展现的过程
1.在浏览器输入url
2.浏览器获取了这个url,当然就去解析了,它先去缓存当中看看有没有,从 浏览器缓存-系统缓存-路由器缓存 当中查看,如果有从缓存当中显示页面,然后没有那就进行步骤三域名解析;
3.域名解析
由DNS服务器来完成将域名解析成对应的服务器IP地址这项工作
4.服务器处理请求 TCP三次握手连接
TCP是互联网中的传输层协议,提供可靠的链接服务,采用三次握手确认一个连接:
浏览器向服务器发送建立连接的请求。
服务器接收到浏览器发送的请求后,想浏览器发送统一连接的信号。
浏览器接受到服务器发出的同意连接的信号后,再次向服务器发出确认连接的信号。
当三次握手返程,TCP客户端和服务端成功的建立连接,就可以开始传输数据了。
5.服务器收到请求
服务器收到浏览器发送的请求信息,返回一个响应头和一个响应体。
6.页面渲染
浏览器收到服务器发送的响应头和响应体,进行客户端渲染,生成Dom树、解析css样式、js交互
13、前端性能优化
1.使用字体图标代替图片
2.html css js 图片等文件的压缩 图片懒加载
3.使用CDN
4.模块按需加载 import require
5.减少重绘 避免使用层级较深的选择器 提升渲染效率
6.减少dom操作 由于DOM操作比较耗时,且可能会造成回流,因此要避免频繁操作DOM,可以批量操作DOM,先用字符串拼接完毕,再用innerHTML更新DOM
14、webpack 构建流程
1.从配置文件或者脚本语句中得出初始化参数
2.通过参数初始化compiler对象,加载配置的插件,开始编译
3.根据配置中的 entry 找出所有的入口文件
4.从入口文件 通过递归处理 对模块进行编译 找到依赖的模块
5.模块编译完成后,得到编译后的内容和彼此的依赖关系
6.根据依赖关系,对模块进行分片组装,转换成单独文件加入到输出列表中
7.确定输出内容的路径和文件名,进行输出
webpack里的loader和plugin
loader是使webpack拥有加载和解析非js文件的能力 对模块的源代码进行转换 可以在import 或"加载"模块时预处理文件 常见的 style-loader css-loader file-loader(把url里面内容转换成我们最终需要使用的那个路径。把图片转移到我们输出的目录,并且把图片更改为另外一种名字或者做一些其他的处理
) url-loader(引入图片资源base64处理 用limit约束 小于某个大小时才base64处理)
plugin可以拓展webpack的功能 比如通过api改变输出结果 使webpack更加灵活
常见的
1. clean-webpack-plugin 配合 webpack -p命令 编译文件时清楚build或dist文件 再生成新的
2.html-webpack-plugin 简单创建html文件 用于服务器访问 同时为输出文件添加哈希值标记,避免老的不变的文件重新加载,避免新修改的文件受缓存影响
3.extract-text-webpack-plugin 主要是为了抽离css样式,防止将样式打包在js中引起页面样式加载错乱的现象。
Gulp和webpack
Gulp 就是为了规范前端开发流程,实现前后端分离、模块化开发、版本控制、文件合并与压缩、mock数据等功能的一个前端自动化构建工具。Gulp侧重于前端开发的 整个过程 的控制管理。
Webpack 是前端资源模块化管理和打包工具。它可以将许多松散的模块按照依赖和规则打包成符合生产环境部署的前端资源。还可以将按需加载的模块进行代码分隔,等到实际需要的时候再异步加载。Webpack更侧重于模块打包。
15、内存泄漏
已经不再使用的内存未能被程序释放,叫内存泄露(memory leak)
原因
1.全局变量
全局对象就是window对象。变量在窗口关闭或重新刷新页面之前都不会被释放
2.闭包
闭包是内部函数总是可以访问其所在的外部函数中声明的参数和变量,可以读取函数内部的变量,然后让这些变量始终保存在内存中。如果在使用结束后没有将局部变量清除,就可能导致内存泄露。
3.事件监听
对同一个事件重复监听,但是忘记移除,会导致内存泄露。
4.其他原因
console.log打印的对象不能被垃圾回收,可能会导致内存泄露。
setInterval也可能会导致内存泄露。
内存泄漏的识别方法
开发者工具Performance勾选 Memory 点击左侧record 访问网页 stop 查看Event Log分析结果
16、new和声明的不同
1.声明的作用域限制在定义类对象的方法中,当方法结束时,类对象也被系统释放了,(安全不会造成内存系统泄漏)。
2.new 创建的是指向类对象的指针,作用域变成了全局,当程序结束时,必须用delete删除,系统不会自动释放,(不注意可能造成内存泄漏)。
3.new的存储空间在堆上 声明的存储空间在栈上
18、vue和react的区别
1.模板渲染方式
Vue是在模板中,通过指令来实现渲染 模板的数据都必须挂在this上进行一次中转 而React是在组件JS代码中,通过原生JS实现模板中的常见语法,比如插值,条件,循环等,都是通过JSX渲染模板
2.渲染过程不同
vue在渲染过程中,会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树 而react在状态被改变时,全部子组件都会重新渲染。不过可以通过shouldComponentUpdate这个生命周期方法进行控制
3.数据
vue是数据可变的,双向绑定,声明式的写法 而react是整体的思路是函数式的,推崇纯组件的方式,数据不可变,单向数据流
19、状态码:
400 Bad Request 语义有误,当前请求无法被服务器理解 或者 请求参数有误
403 Forbidden 服务器已经理解请求,但是拒绝执行它
404 Not Found
502 Bad Gateway 作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。
504 Gateway Timeout
20、防抖与节流
防抖
等待足够的空闲时间后,才执行代码一次 利用定时器实现防抖
节流
一定时间内只执行代码一次 有时间戳和定时器两种简单的实现方式
21、es6新特性
1.let const块级作用域 不会变量提升 var会变量提升
2.for(let item of arr){} 循环数组 可以用break来终止整个循环,或者continute来跳出当前循环,继续后面的循环;
3.反引号创建字符串模板 `your num is ${num}`
4.函数的默认参数 function sayHello2(name='dude'){
console.log(`Hello ${name}`);
}
5.拓展参数
它允许传递数组或者类数组直接做为函数的参数而不用通过apply。
var people=['Wayou','John','Sherlock'];
//sayHello函数本来接收三个单独的参数人妖,人二和人三
function sayHello(people1,people2,people3){
console.log(`Hello ${people1},${people2},${people3}`);
}
//但是我们将一个数组以拓展参数的形式传递,它能很好地映射到每个单独的参数
sayHello(...people);//输出:Hello Wayou,John,Sherlock
// 而在以前,如果需要传递数组当参数,我们需要使用函数的apply方法
sayHello.apply(null,people);//输出:Hello Wayou,John,Sherlock
6.新API
"abcde".contains("cd") // true
"abc".repeat(3) // "abcabcabc"
Object.assign
ES6模块与CommonJS模块的差异
讨论Node加载ES6模块之前,必须了解ES6模块与CommonJS模块的差异,具体的两大差异如下。
CommonJS模块输出的是一个值的复制,ES6模块输出的是值的引用。
CommonJS模块是运行时加载,ES6模块是编译时输出接口。
第二个差异是因为CommonJS加载的是一个对象(即module.exports属性),该对象只有在脚本运行结束时才会生成。而ES6模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
22、filter() let arr2 = arr1.filter(item => item.age > 20);
filter()方法:主要用于过滤筛选数组,数组filter后,返回的结果为新的数组,不会改变原数组的值。
find() 主要用于查找数组的数据,只要查找到一条符合条件的数据,直接返回,不会再继续查找下去。
23、Array.prototype.filter的实现
Array.prototype.filter = function(cb, context){
context = context || this; //确定上下文,默认为this
var len = this.length; //数组的长度
var r = []; //最终将返回的结果数组
for(var i = 0; i < len; i++){
if(cb.call(context, this[i], i, this)){ //filter回调函数的三个参数:元素值,元素索引,原数组
r.push(this[i]);
}
}
return r;
};
24、存储
数据上的生命周期的不同
Cookie 一般由服务器生成,可设置失效时间,如果在浏览器端生成cookie,默认是关闭后失效。
localStorage 除非被永久清除,否则永久保存。
sessionStorage 仅在当前会话会有效,关闭页面或浏览器后被清除
存放数据的大小不同
Cookie 一般为4kb
localStorage 和 sessionStorage 一般为5mb
与服务器端通信不同
Cookie 每次都会携带HTTP头中,如果使用cookie保存过多数据会带来性能问题
localStorage 和 sessionStorage 仅在客户端(即浏览器)中保存,不参与和服务器的通信。
易用性
Cookie 需要程序员自己来封装,原生的cookie接口不够友好
localStorage 和 sessionStorage 原生接口可以接受,可以封装来对Object和Array有更好的支持。
25、缓存
缓存分为
1. 服务端侧如CDN缓存
其目的是通过在现有的Internet中增加一层新的网络架构,将网站的内容发布到最接近用户的网络"边缘",使用户可 以就近取得所需的内容,解决Internet网络拥塞状况,提高用户访问网站的响应速度。从技术上全面解决由于网络带宽小、用户访问量大、网点分布不均等 原因,解决用户访问网站的响应速度慢的根本原因。
2. 客户端缓存就是指浏览器缓存
浏览器缓存分为强缓存和协商缓存
强缓存:浏览器在加载资源时,先根据这个资源的一些http header判断它是否命中强缓存,强缓存如果命中,浏览器直接从自己的缓存中读取资源,不会发请求到服务器。比如某个css文件,如果浏览器在加载它所在的网页时,这个css文件的缓存配置命中了强缓存,浏览器就直接从缓存中加载这个css,连请求都不会发送到网页所在服务器;
协商缓存:当强缓存没有命中的时候,浏览器一定会发送一个请求到服务器,通过服务器端依据资源的另外一些http header验证这个资源是否命中协商缓存,如果协商缓存命中,服务器会将这个请求返回(304),但是不会返回这个资源的数据,而是告诉客户端可以直接从缓存中加载这个资源,于是浏览器就又会从自己的缓存中去加载这个资源;若未命中请求,则将资源返回客户端,并更新本地缓存数据(200)。
强缓存与协商缓存区别:强缓存不发请求到服务器,协商缓存会发请求到服务器。
设置缓存
1. html Meta标签控制缓存 CONTENT="no-cache"
2.HTTP头信息控制缓存是通过Expires(强缓存)、Cache-control(强缓存)、Last-Modified/If-Modified-Since(协商缓存)、Etag/If-None-Match(协商缓存)实现
26、promise弊端
arr.slice(a, b); // 不包括结束位置 不改变原数组的值
arr.splice(a, b, c, d); // 会改变原数组
27、eventloop机制
主线程从“任务队列”中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为event loop(事件循环)
28、$attrs概念: 包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。
$listeners概念:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听。
29、Promise.then() 微任务定义之后便会立即执行 会在当前事件循环末尾中执行,而settimeout宏任务中的任务是在下一次事件循环中执行
30、https和http
http请求头有哪些字段
https相对于http的劣势是,https建立连接需要多次握手,而且还要进行RSA加密解密。http是用明文传输,在传输过程中,我们的信息可能会被篡改,或者会被第三方截取插入一些别的信息,比如说广告植入,这严重影响了用户体验。
HTTPS在传输的过程中会涉及到三个密钥:
服务器端的公钥和私钥,用来进行非对称加密
客户端生成的随机密钥,用来进行对称加密
一个HTTPS请求实际上包含了两次HTTP传输,可以细分为8步。
1.客户端向服务器发起HTTPS请求,连接到服务器的443端口
2.服务器端有一个密钥对,即公钥和私钥,是用来进行非对称加密使用的,服务器端保存着私钥,不能将其泄露,公钥可以发送给任何人。
3.服务器将自己的公钥发送给客户端。
4.客户端收到服务器端的证书之后,会对证书进行检查,验证其合法性,如果发现发现证书有问题,那么HTTPS传输就无法继续。严格的说,这里应该是验证服务器发送的数字证书的合法性,关于客户端如何验证数字证书的合法性,下文会进行说明。如果公钥合格,那么客户端会生成一个随机值,这个随机值就是用于进行对称加密的密钥,我们将该密钥称之为client key,即客户端密钥,这样在概念上和服务器端的密钥容易进行区分。然后用服务器的公钥对客户端密钥进行非对称加密,这样客户端密钥就变成密文了,至此,HTTPS中的第一次HTTP请求结束。
5.客户端会发起HTTPS中的第二个HTTP请求,将加密之后的客户端密钥发送给服务器。
6.服务器接收到客户端发来的密文之后,会用自己的私钥对其进行非对称解密,解密之后的明文就是客户端密钥,然后用客户端密钥对数据进行对称加密,这样数据就变成了密文。
7.然后服务器将加密后的密文发送给客户端。
8.客户端收到服务器发送来的密文,用客户端密钥对其进行对称解密,得到服务器发送的数据。这样HTTPS中的第二个HTTP请求结束,整个HTTPS传输完成。
HTTPS的安全性主要依赖于对数字证书的验证以及非对称加密机制,细看步骤3),客户端具体是如何判断证书的合法性的?
先来看看数字证书都有哪些内容
Issuer--证书的发布机构
发布证书的机构,指明证书是哪个公司创建的(并不是指使用证书的公司)。出了问题具体的颁发机构是要负责的
Valid from,Valid to--证书的有效期
证书的使用期限。过了这个期限证书就会作废,不能使用。
Public key--公钥
刚开始的时候介绍过公钥的概念,用于对消息加密的。
Subject--主题
证书是颁发给谁了,一般是个人或公司名称或机构名称或公司网站的网址。
Signature algorithm--签名所使用的算法
数字证书的数字签名所使用的加密算法,根据这个算法可以对指纹解密。指纹加密的结果就是数字签名。
Thumbprint,Thumbprint algorithm--指纹以及指纹算法(一种HASH算法)
指纹和指纹算法会使用证书机构的私钥加密后和证书放在一起。主要用来保证证书的完整性,确保证书没有修改过。使用者在打开证书时根据指纹算法计算证书的hash值,和刚开始的值一样,则表示没有被修改过。
a)
绕了一大圈,问题回来了,客户端如何检测数字证书是合法的并是所要请求的公司的?
首先应用程序读取证书中的Issuer(发布机构),然后会在操作系统或浏览器内置的受信任的发布机构中去找该机构的证书(为什么操作系统会有受信任机构的证书?先看完这个流程再来回答)。如果找不到就说明证书是水货,证书有问题,程序给错误信息。如果找到了,或用户确认使用该证书。就会拿上级证书的公钥,解密本级证书,得到数字指纹。然后对本级证书的公钥进行数字摘要算法(证书中提供的指纹加密算法)计算结果,与解密得到的指纹对比。如果一样,说明证书没有被修改过。公钥可以放心使用,可以开始握手通信了。
b)
接下来解答操作系统为什么会有证书发布机构的证书?
其实证书发布机构除了给别人发布证书外,自己也有自己的证书。在操作系统安装好时,受信任的证书发布机构的数字证书就已经被微软安装在操作系统中了,根据一些权威安全机构的评估,选取一些信誉很好并且通过一定安全认证的证书发布机构,把这些证书默认安装在操作系统中并设为信任的数字证书。发布机构持有与自己数字证书对应的私钥,会用这个私钥加密所有他发布的证书及指纹整体作为数字签名。
c)
步骤4)中客户端生成随机数并用公钥加密,让服务端用私钥解密来确保对方是否真的持有私钥。但是,黑客也可以发送字符串让服务器用私钥加密,并得到加密后的信息,从而找到规律,导致私钥的安全性下降。如何解决?
服务端并不是真的加密这个字符串,而是把字符串进行hash计算后再进行加密后发送给客户端。客户端收到后再解密这个hash值与原来字符串的hash值对比,从而确定对方是否持有私钥。
d)
在通信的过程中,黑客可以截获加密内容,虽不能理解具体内容,但可以捣乱,修改内容或重复发送该内容,如何解决?
给通信的内容加版本号或随机值,如果接收到版本号或随机值不相同的信息,双方立刻停止通信。若一直捣乱就无法正常通信,因为有人控制了你的路由器,可以针对你。所以一些对于安全性较强的部门来说就不使用公网,而是内部网络,一般不会被破环通信。
跨域
https://www.it610.com/article/1296513648377798656.htm
https://www.cnblogs.com/sdcs/p/8484905.html
1、 通过jsonp跨域
2、 document.domain + iframe跨域
3、 location.hash + iframe
4、 window.name + iframe跨域
5、 postMessage跨域
6、 跨域资源共享(CORS) 普通跨域请求:只服务端设置Access-Control-Allow-Origin即可,前端无须设置,若要带cookie请求:前后端都需要设置。由于同源策略的限制,所读取的cookie为跨域请求接口所在域的cookie,而非当前页。如果想实现当前页cookie的写入,可参考下文:七、nginx反向代理中设置proxy_cookie_domain 和 八、NodeJs中间件代理中cookieDomainRewrite参数的设置。
7、 nginx代理跨域
8、 nodejs中间件代理跨域
9、 WebSocket协议跨域
简单请求和复杂请求
这两种请求的区别主要在于是否会触发CORS(Cross-Origin Resource Sharing)预检请求。
1)简单请求
请求方法是以下三种方法之一:
HEAD
GET
POST
HTTP的头信息修改不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
不能为XMLHttpRequestUpdate注册监听器,XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问
请求中没有使用readableStream对象
复杂请求在正式请求前都会有CORS预检请求,在浏览器中都能看到有OPTIONS请求,用于向服务器请求权限信息的。
复杂请求发送真正的请求前, 会先发送一个方法为OPTIONS的预请求(preflight request), 用于试探服务端是否能接受真正的请求,如果options获得的回应是拒绝性质的,比如404\403\500等http状态,就会停止post、put等请求的发出
axios 都是复杂请求,ajax 可以是简单请求
附带身份凭证的请求
一般而言,对于跨域 XMLHttpRequest或 Fetch 请求,浏览器不会发送身份凭证信息。如果要发送凭证信息,需要设置 XMLHttpRequest 的某个特殊标志位。如果在发送请求的时候,给xhr 设置了withCredentials为true,从而向服务器发送 Cookies,如果服务端需要向客户端也发送cookie的情况,需要服务器端也返回Access-Control-Allow-Credentials: true响应头信息。对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin的值为“*”。这是因为请求的首部中携带了Cookie信息,如果 Access-Control-Allow-Origin的值为“*”,请求将会失败。而将 Access-Control-Allow-Origin的值设置为 http://foo.example(请求源),则请求将成功执行。
判断数据类型:
https://blog.csdn.net/weixin_42259266/article/details/90028388?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control
1. typeof 检测基本数据类型时没有问题,但是当其对引用类型进行检测时,会返回object,这样就无法进行精准的判断,这样也不足为奇,因为所有的对象其原型链最终都指向了object
2.instanceof只能用来检测两个对象是否在一条原型链上,并不能检测出对象的具体类型。
3.constructor 也是存在问题的,比如当我们重写了F的prototype之后,原有的constructor会丢失,此时F的实例对象f的constructor也不会再指向F,这时候的constructor会默认指向Object。
F.prototype = { a: 'test' }; var f = new F(); f.constructor == F;// false,但是f.constructor == Object; // true为什么呢???
因为F.prototype被重新赋值了 {},而{}是new Object()的一个实例对象,因此,new Object()会把其原型上的constructor传递给 {}
因此,为了规范,在重写对象原型时一般都需要重新给constructor赋值,以保证实例对象的类型不被改写。
4.Object.prototype.toString
toString 方法默认返回其调用者的具体类型,更严格的讲是toString运行时,this指向的对象类型。但是需要注意的是,必须要通过Object.prototype.toString这种方法来查找,不能直接使用toString,从原型链的角度讲,所有对象的原型链最终都指向了Object,按照JS变量查找规则,其他对象也是可以直接访问到Object的toString方法的,但是事实上,大部分对象都已经实现了自身的toString方法,这样就可能导致Object的toString被终止查找,所以我们使用call方法来强制执行Object的toString方法。
原型链
因为每个对象和原型都有原型,对象的原型指向原型对象,
而父的原型又指向父的父,这种原型层层连接起来的就构成了原型链。
实例对象与原型之间的链接,叫做原型链。
在js里,继承机制是原型继承。继承的起点是对象的原型(Object prototype)。
一切皆为对象,只要是对象,就会有proto属性,该属性存储了指向其构造的指针。
Object prototype也是对象,其proto指向null。
对象分为两种:函数对象和普通对象,只有函数对象拥有『原型』对象(prototype)。
prototype的本质是普通对象。
Function prototype比较特殊,是没有prototype的函数对象。
new操作得到的对象是普通对象。
当调取一个对象的属性时,会先在本身查找,若无,就根据proto找到构造原型,若无,继续往上找。最后会到达顶层Object prototype,它的proto指向null,均无结果则返回undefined,结束。
由proto串起的路径就是『原型链』。
31、简单实现promise.all
Promise.prototype.all = function (promises) {
return new Promise((resolve, reject) => {
let count = 0;
let result = new Array(promises.length);
for (let i = 0; i < promises.length; i++) {
Promise.resolve(promises[i]).then((res) => {
count++;
result[i] = res;
if (count === promises.length) {
return resolve(result);
}
}).catch((err) => {
reject('err');
});
}
})
}
32、浅拷贝和深拷贝
浅拷贝如Object.assign()的时候如果数据是基本数据类型,那么就如同直接赋值那种,会拷贝其本身,如果除了基本数据类型之外还有一层对象,那么对于浅拷贝而言就只能拷贝其引用,对象的改变会反应到拷贝对象上;但是深拷贝如JSON.parse(JSON.stringify())就会拷贝多层,即使是嵌套了对象,也会都拷贝出来。
实现深拷贝
function extendDeep(parent, child) {
let i,
proxy;
proxy = JSON.stringify(parent);
proxy = JSON.parse(proxy);
child = child || {};
for(i in proxy) {
if(proxy.hasOwnProperty(i)) {
child[i] = proxy[i];
}
}
proxy = null;
return child;
}
33、链接取值
function GetQueryString(name)
{
let reg = new RegExp("(^|&)"+ name +"=([^&]*)(&|$)");
let r = window.location.search.substr(1).match(reg);
if(r!=null)return unescape(r[2]); return null;
}
GetQueryString('open_id')
34、bind函数并手写
Function.prototype.bind
bind() 函数会创建一个新绑定函数,绑定函数与被调函数具有相同的函数体(在 ECMAScript 5 中)。调用绑定函数通常会导致执行包装函数 绑定函数也可以使用new运算符构造:这样做就好像已经构造了目标函数一样。提供的this值将被忽略,而前置参数将提供给模拟函数
bind有如下三个功能点:
改变原函数的 this 指向,即绑定上下文,返回原函数的拷贝
当绑定函数被调用时,bind的额外参数将置于实参之前传递给被绑定的方法。
注意,一个绑定函数也能使用new操作符创建对象,这种行为就像把原函数当成构造器,thisArg 参数无效。也就是 new 操作符修改 this 指向的优先级更高。
Function.prototype.bindFn = function bind(thisArg) {
if (typeof this !== 'function') {
throw new TypeError(this + 'must be a function');
}
// 存储函数本身
var self = this;
// 去除thisArg的其他参数 转成数组
var args = [].slice.call(arguments, 1);
return function () {
// bind返回的函数 的参数转成数组
var boundArgs = [].slice.call(arguments);
// apply修改this指向,把两个函数的参数合并传给self函数,并执行self函数,返回执行结果
return self.apply(thisArg, args.concat(boundArgs));
}
}
35、获取数组最大值的下标
Math.max(...arr)
function getMaxIndex(arr) {
let max = arr[0];
let index = 0;
for (let i = 0; i < arr.length; i++) {
if (max < arr[i]) {
max = arr[i];
index = i;
}
}
return index;
}
36、数组去重
1.ES6的set函数 return [...new Set(arr)]
2.把数组的值转化成对象的属性 function unique(arr) {
let json = {};
let newArr = [];
for (let i = 0; i < arr.length; i++) {
if (!json[arr[i]]) {
newArr.push(arr[i]);
json[arr[i]] = 1;
}
else {
json[arr[i]]++;
}
}
return newArr;
}
37、冒泡排序
function bubbleSort(arr){
for(var i = 0;i<arr.length;i++){
for(var j=arr.length-1;j>i;j--){
if(arr[j]<arr[j-1]){
var temp = arr[j]
arr[j] = arr[j-1]
arr[j-1] = temp
}
}
}
return arr
}
38、快排
var quickSort = function(arr) {
if (arr.length <= 1) { return arr; }
var pivotIndex = Math.floor(arr.length / 2);
var pivot = arr.splice(pivotIndex, 1)[0];
var left = [];
var right = [];
for (var 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));
};
39、求dom树的最大深度
const getDepth = node => {
if (!node.children || node.children.length === 0) {
return 1
}
const maxChildrenDepth = [...node.children].map(v => getDepth(v))
return 1 + Math.max(...maxChildrenDepth)
}
console.log(getDepth(document.documentElement));
怎么去设计一个组件封装?
1、组件封装的目的是为了重用,提高开发效率和代码质量
2、低耦合,单一职责,可复用性,可维护性
3、前端组件化设计思路
display:flex;
flex-wrap:wrap;
justify-content: space-between
常见web安全及防护原理
sql注入原理
就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。
总的来说有以下几点:
1.永远不要信任用户的输入,要对用户的输入进行校验,可以通过正则表达式,或限制长度,对单引号和双"-"进行转换等。
2.永远不要使用动态拼装SQL,可以使用参数化的SQL或者直接使用存储过程进行数据查询存取。
3.永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接。
4.不要把机密信息明文存放,请加密或者hash掉密码和敏感的信息。
XSS原理及防范
Xss(cross-site scripting)攻击指的是攻击者往Web页面里插入恶意 html标签或者javascript代码。比如:攻击者在论坛中放一个看似安全的链接,骗取用户点击后,窃取cookie中的用户私密信息;或者攻击者在论坛中加一个恶意表单,
当用户提交表单的时候,却把信息传送到攻击者的服务器中,而不是用户原本以为的信任站点。
XSS防范方法
首先代码里对用户输入的地方和变量都需要仔细检查长度和对”<”,”>”,”;”,”’”等字符做过滤;其次任何内容写到页面之前都必须加以encode,避免不小心把html tag 弄出来。这一个层面做好,至少可以堵住超过一半的XSS 攻击。
首先,避免直接在cookie 中泄露用户隐私,例如email、密码等等。
其次,通过使cookie 和系统ip 绑定来降低cookie 泄露后的危险。这样攻击者得到的cookie 没有实际价值,不可能拿来重放。
如果网站不需要再浏览器端对cookie 进行操作,可以在Set-Cookie 末尾加上HttpOnly 来防止javascript 代码直接获取cookie 。
尽量采用POST 而非GET 提交表单
如果不需要用户输入 HTML,可以直接对用户的输入进行 HTML 转义
当用户需要输入 HTML 代码时:更好的方法可能是,将用户的输入使用 HTML 解析库进行解析,获取其中的数据。然后根据用户原有的标签属性,重新构建 HTML 元素树。构建的过程中,所有的标签、属性都只从白名单中拿取。
CSRF(Cross-site request forgery)跨站请求伪造:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。
CSRF 可以简单理解为:攻击者盗用了你的身份,以你的名义发送恶意请求,容易造成个人隐私泄露以及财产安全。
如何防范CSRF攻击?
1.添加验证码(体验不好)
2.判断请求的来源:检测Referer(并不安全,Referer可以被更改)
3.使用Token(主流)
Javascript垃圾回收方法
标记清除(mark and sweep)
这是JavaScript最常见的垃圾回收方式,当变量进入执行环境的时候,比如函数中声明一个变量,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”。
垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及被环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量了
引用计数(reference counting)
在低版本IE中经常会出现内存泄露,很多时候就是因为其采用引用计数方式进行垃圾回收。引用计数的策略是跟踪记录每个值被使用的次数,当声明了一个变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加1,如果该变量的值变成了另外一个,则这个值得引用次数减1,当这个值的引用次数变为0的时 候,说明没有变量在使用,这个值没法被访问了,因此可以将其占用的空间回收,这样垃圾回收器会在运行的时候清理掉引用次数为0的值占用的空间。
在IE中虽然JavaScript对象通过标记清除的方式进行垃圾回收,但BOM与DOM对象却是通过引用计数回收垃圾的,
也就是说只要涉及BOM及DOM就会出现循环引用问题。
重排重绘
当DOM的变化引发了元素几何属性的变化,比如改变元素的宽高,元素的位置,导致浏览器不得不重新计算元素的几何属性,并重新构建渲染树,这个过程称为“重排”。完成重排后,要将重新构建的渲染树渲染到屏幕上,这个过程就是“重绘”。
简单的说,重排负责元素的几何属性更新,重绘负责元素的样式更新。而且,重排必然带来重绘,但是重绘未必带来重排。比如,改变某个元素的背景,这个就不涉及元素的几何属性,所以只发生重绘。
h5和小程序的区别
https://www.douban.com/note/707405724/
一、运行环境
1.1 H5 既然是网页,那么依赖的外壳主要是浏览器,因此只要有浏览器,就可以使用。比如手机内置的浏览器,APP 的 web-view 组件,以及小程序提供的 web-view 组件,都可以打开 H5 页面。
1.2 小程序只能依赖微信客户端,也就是说只能在微信里打开。那么,如果你的产品需要通过短信通知用户带上访问地址,就无法用小程序实现了。而 H5 页面,则可以在短信正文中直接用手机内置浏览器打开。
二、系统权限
2.1这里的系统权限,可以理解为隐私级别比较高的,如通讯录,或能调用硬件的,比如蓝牙功能等。从这个角度看,H5 本身可以说几乎是没有什么系统权限的。虽然也有摄像头之类的接口,但是重度依赖浏览器能力,兼容性有限。
2.2.而小程序,由于依赖微信客户端本身,所以微信小程序团队将客户端的很多能力开放给了小程序环境,当然,前提是你给微信也授权了相关的能力,比如允许访问麦克风,允许访问相册等。
三、能力限制
1、前面提到了系统权限层面的差异,其实也是一种能力限制。除此之外,还有一些能力是微信本身的策略限制的,比如 H5 在微信里可以直接分享朋友圈,而小程序目前就只能转发好友或群。对于朋友圈,就只能生成带小程序码的图片发到朋友圈。
2、而对于分享到好友或群,小程序又提供了卡片式的分享界面,看起来很高端,信息也多,并且能追踪用户行为。这一点,H5 又无法做到。
3、再比如支付能力,小程序只支持微信支付,而 H5 里可以选择使用其他支付平台提供的支付方式。
四、用户体验
1、小程序基于微信客户端实现,对解析进行了优化,并且一旦首次打开小程序,可以直接缓存很多资源。因此,在使用小程序时可以明显感觉很流畅,接近原生 APP 的体验。
2、而 H5 本质上还是网页,跟之前在 PC 上浏览网页没区别,每次要请求各种图片样式资源,在浏览器内核里渲染,因此体验会差一些。
微信小程序集成了很多原生APP的组件,从体验和页面流畅度来说,都会比HTML5要优秀很多。 微信小程序相对于HTML5开发来说,除了熟悉API需要学习成本之外,还要学习小程序组件,布局。 行的速度方面,传统HTML5在加载的时候受限于网络环境,需要顺序加载HTML、CSS、JS,然后返回数据,最后渲染页面显示在浏览器中。用户经常需要等待很长时间,体验会受到影响。相比之下,小程序的两个线程:Appservice Thread和View Thread会同时进行、并行加载,甚至Appservice Thread会更早执行,当视图线程加载完,通知Appservice,Appservice 会把准备好的数据用setData的方法返回给视图线程。小程序的这种优化策略,可以减少用户的等待时间、加快小程序的响应速度。
js原型链以及特点
原型链继承和类继承。然后类继承只继承了实例属性,没有原型属性。原型链继承可以继承所有。然后用apply和call怎么继承原型链上的共享属性?通过空函数传值。新建一个空函数C。C实例化后C的实例属性就是空,然后用B的apply/call去继承C,相当于继承了C的实例属性。
typeof null ==== object。 null是一个只有一个值的特殊类型。表示一个空对象引用。
typeof undefined === undefined。 undefined 是一个没有设置值的变量。
二叉树的遍历主要有三种:
(1)先(根)序遍历(根左右) 根节点 左右子树
(2)中(根)序遍历(左根右)
(3)后(根)序遍历(左右根)
JSBridge
让native可以调用web的js代码,让web可以 “调用” 原生的代码。
流程:H5->通过某种方式触发一个url->Native捕获到url,进行分析->原生做处理->Native调用H5的JSBridge对象传递回调。
JsBridge之所以能实现Native与Js相互调用的功能,其核心实现其实就是:
拦截Url
load url("javascript:js_method()");
先说第二点,Native调用Js,通过加载以javascript:开头的url即可实现调用Js的方法。这个很好理解,在web中也是通过这种方式调用Js的方法的。
然后细说下第一点的实现:
向body中添加一个不可见的iframe元素。通过拦截url的方法来执行相应的操作,但是页面本身不能跳转,所以改变一个不可见的iframe的src就可以让webview拦截到url,而用户是无感知的。
拦截url。通过shouldOverrideUrlLoading来拦截约定规则的Url,再做具体操作。
304与200读取缓存的区别
封装api
const promisic =function (n) {
returnfunction(t = {}) {
returnnewPromise((c, r) => {
const s = Object.assign(t, {
success: n => {
c(n)
}, fail: n => {
r(n)
}
});
n(s)
})
}
}
promisic(wx.getStorage)().then(res=>{
console.log(res) //成功
}).catch(err=>{
console.err(err) //失败(可省略)
})
// 倒计时
function countDown(countTime) {
let startTime = new Date().getTime();
let endTime = new Date(countTime).getTime();
let time = endTime - startTime;
let day,hour,minute,seconds;
if (time >= 0) {
day = time/1000/60/60/24;
hour = time/1000/60/60%24;
minute = time/1000/60%60;
seconds = time/1000%60;
this.day = day
setTimeout(countDown, 1000);
}
}
// 数组随机抽取
function getRandomEle(arr, num) {
let newArr = [];
for (let i = num; i > 0; i--) {
let index = Math.floor(Math.random()*arr.length);
newArr.push(arr[index]);
arr.splice(index, 1);
}
return newArr;
}
typeof
https://segmentfault.com/a/1190000018821652
蛇形矩阵
let arr = [
[1,2,3,8],
[4,5,6,9],
[3,4,6,8],
[7,8,9,0]
];
snail = function(arr) {
var result;
while (array.length) {
// Steal the first row.
result = (result ? result.concat(array.shift()) : array.shift());
// Steal the right items.
for (var i = 0; i < array.length; i++)
result.push(array[i].pop());
// Steal the bottom row.
result = result.concat((array.pop() || []).reverse());
// Steal the left items.
for (var i = array.length - 1; i >= 0; i--)
result.push(array[i].shift());
}
return result;
}
let result;
snail = function(arr) {
result = result ? result.concat(arr.shift()) : arr.shift();
for(let i = 0; i < arr.length; i++) {
result.push(arr[i].pop());
}
result = result.concat(arr.pop().reverse());
for(let j = arr.length - 1; j >=0; j--) {
result.push(arr[j].shift());
}
while(arr.length) {
snail(arr);
}
return result;
}
let arr = [
[1,2,3,8],
[4,5,6,9],
[3,4,6,8],
[7,8,9,0]
];
实现EventEmitter类,常见的on off emmit once
class EventEmitter {
constructor() {
this.events = {};
}
on(event, callback) {
if(!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
return this;
}
off(event, callback) {
let callbacks = this.events[event];
this.events[event] = callbacks && callbacks.filter(fn => {
return fn !== callback;
});
return this;
}
emit(event, ...arg) {
let callbacks = this.events[event];
callbacks && callbacks.map(fn => {
fn(...arg);
});
return this;
}
once(event, callback) {
let only = () => {
callback.apply(this, arguments);
this.off(event, only);
}
this.on(event, only);
return this;
}
}
构造一个函数:将dom类数据结构转化成真实的dom
输入参数例子:
element类节点
let json = {
"tag": "div",
"className": ["clearfix", "left"],
"children": [
"hello word",
{...node}
]
}
function creatDom(json) {
let el = document.createElement(json.tag);
if(json.className && json.className.length) {
el.setAttribute('class', json.className.join(' '));
}
json.children.forEach(child => {
let childEle = null;
if(typeof child === 'object') {
childEle = creatDom(child);
}
else {
childEle = document.createTextNode(child);
}
el.appendChild(childEle);
});
return el;
}
document.querySelector('body').appendChild(creatDom(json));
abbbac 用栈消除重复字符串 bbb需要还剩一个b
function foo(str) {
let s = []
const getTop = () => {
return s[s.length - 1]
}
Array.prototype.slice.call(str).forEach(ch => {
let top = getTop()
if (top === ch) {
s.pop()
} else {
s.push(ch)
}
})
return s.join('')
}
foo("abbbac");
css实现钟摆效果
https://juejin.cn/post/6844903942904610830
transform-origin: center top;// 这里指定起始点为居中,靠上
animation: swing 5s;// 指定动画的名称,与整个动画的时间
animation-iteration-count:infinite;//设置无限摆动
animation-timing-function: linear;//指定运行的速度曲线,liner表示线性的,即匀速。
.line{width:20px;height:400px;background: red;margin:50pxauto;transform-origin: center top;animation: swing5s;animation-iteration-count:infinite;animation-timing-function: linear;position: relative;}.ball{width:60px;height:60px;border-radius:30px;background: blue;position: absolute;bottom: -60px;left: -20px;}@keyframesswing {0%{transform:rotate(45deg);}25%{transform:rotate(0deg);}50%{transform:rotate(-45deg);}75%{transform:rotate(0deg);}100%{transform:rotate(45deg);}}
<div class="line"><div class=" ball"></div></div>
function foo(node) {
let queue = [node]
let ret = []
let c = 0
while(queue.length) {
let arr = []
for (let i = 0, len = queue.length; i < len; i++) {
node = queue.shift()
arr.push(node)
if (node.left) {
queue.push(node.left)
}
if (node.right) {
queue.push(node.right)
}
}
ret.push(arr)
}
return ret
}
react虚拟dom 解释一下它的工作原理。
Virtual DOM 是一个轻量级的 JavaScript 对象,它最初只是 real DOM 的副本。它是一个节点树,它将元素、它们的属性和内容作为对象及其属性。 React 的渲染函数从 React 组件中创建一个节点树。然后它响应数据模型中的变化来更新该树,该变化是由用户或系统完成的各种动作引起的。
Virtual DOM 工作过程有三个简单的步骤。
1.每当底层数据发生改变时,整个 UI 都将在 Virtual DOM 描述中重新渲染。
2.然后计算之前 DOM 表示与新表示的之间的差异。
3.完成计算后,将只用实际更改的内容更新 real DOM。
数据如何通过 Redux 流动
使用 React Hooks 好处是啥?
Hooks是 React 16.8 中的新添加内容。它们允许在不编写类的情况下使用state和其他 React 特性。使用 Hooks,可以从组件中提取有状态逻辑,这样就可以独立地测试和重用它。Hooks 允许咱们在不改变组件层次结构的情况下重用有状态逻辑,这样在许多组件之间或与社区共享 Hooks 变得很容易。
首先,Hooks 通常支持提取和重用跨多个组件通用的有状态逻辑,而无需承担高阶组件或渲染 props 的负担。Hooks 可以轻松地操作函数组件的状态,而不需要将它们转换为类组件。
Hooks 在类中不起作用,通过使用它们,咱们可以完全避免使用生命周期方法,例如 componentDidMount、componentDidUpdate、componentWillUnmount。相反,使用像useEffect这样的内置钩子。