目录
- 讲下react生命周期
- js的数据类型
- 什么是闭包
- Vue和react的区别
- 性能优化
- 输入URL到页面渲染的整个流程
- 如何处理跨域问题
- 谈谈你对原型链的理解
- 重排和重绘
- 你做过一些什么基础的搭建
- 项目中最难的问题是什么
- HTTPS是如何保证安全的
- 你怎么带实习生的
- 你遇到的最难的技术问题是什么
- 后端设置cors,跨域后,前端怎么设置,后端可以拿到cookie
- 谈一下你对js运行机制的理解
- 浏览器渲染机制
- 前端怎么实现微信的授权
- diff算法原理
- 讲下hooks的使用
- 讲下reactNative
1. 讲下react生命周期
React 16之后有三个⽣命周期被废弃(但并未删除)
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
官⽅计划在17版本完全删除这三个函数,只保留UNSAVE_前缀的三个函数,⽬的是为了向下兼容,但是对于开发者⽽⾔应该尽量避免使⽤他们,⽽是使⽤新增的⽣命周期函数替代它们。
⽬前React16.8+的⽣命周期分为三个阶段,分别是挂载阶段、更新阶段、卸载阶段。
挂载阶段:
- constructor:构造函数,最先被执⾏,我们通常在构造函数⾥初始化state对象或者给⾃定义⽅法绑定this;
- getDerivedStateFromProps:static getDerivedStateFromProps(nextProps, prevState),这是个静态⽅法,当我们接收到新的属性想去修改我们state, 可以使⽤getDerivedStateFromProps
- render:render函数是纯函数,只返回需要渲染的东⻄,不应该包含其它的业务逻辑,可以返回原⽣的DOM、React组件、Fragment、Portals、字符串和数字、 Boolean和null等内容;
- componentDidMount:组件装载之后调⽤,此时我们可以获取到DOM节点并操作,⽐如对canvas,svg的操作,服务器请求,订阅都可以写在这个⾥⾯,但是记得在componentWillUnmount中取消订阅;
更新阶段:
- getDerivedStateFromProps: 此⽅法在更新个挂载阶段都可能会调⽤;
- shouldComponentUpdate:shouldComponentUpdate(nextProps, nextState),有两个参数nextProps和nextState,表示新的属性和变化之后的state,返回⼀个布尔值,true表示会触发重新渲染,false表示不会触发重新渲染,默认返回true,我们通常利⽤此⽣命周期来优化React程序性能;
- render:更新阶段也会触发此⽣命周期;
- getSnapshotBeforeUpdate:getSnapshotBeforeUpdate(prevProps, prevState),这个⽅法在render之后,componentDidUpdate之前调⽤,有两个参数prevProps和prevState,表示之前的属性和之前的state,这个函数有⼀个返回值,会作为第三个参数传给componentDidUpdate,如果你不想要返回值,可以返回null,此⽣命周期必须与componentDidUpdate搭配使⽤;
- componentDidUpdate:componentDidUpdate(prevProps, prevState, snapshot),该⽅法在getSnapshotBeforeUpdate⽅法之后被调⽤,有三个参数prevProps,prevState,snapshot,表示之前的props,之前的state,和snapshot。第三个参数是getSnapshotBeforeUpdate返回的,如果触发某些回调函数时需要⽤到DOM元素的状态,则将对⽐或计算的过程迁移⾄getSnapshotBeforeUpdate,然后在componentDidUpdate中统⼀触发回调或更新状态。
卸载阶段:
-componentWillUnmount:当我们的组件被卸载或者销毁了就会调⽤,我们可以在这个函数⾥去清除⼀些定时器,取消⽹络请求,清理⽆效的DOM元素等垃圾清理⼯作。
总结:
- componentWillMount:在渲染之前执行,用于根组件中的 App 级配置;
- componentDidMount:在第一次渲染之后执行,可以在这里做AJAX请求,DOM的操作或状态更新以及设置事件监听器;
- componentWillReceiveProps:在初始化render的时候不会执行,它会在组件接受到新的状态(Props)时被触发,一般用于父组件状态更新时子组件的重新渲染
- shouldComponentUpdate:确定是否更新组件。默认情况下,它返回true。如果确定在state或props更新后组件不需要在重新渲染,则可以返回false,这是一个提高性能的方法;
- componentWillUpdate:在shouldComponentUpdate返回true确定要更新组件之前件之前执行;
- componentDidUpdate:它主要用于更新DOM以响应props或state更改;
- componentWillUnmount:它用于取消任何的网络请求,或删除与组件关联的所有事件监听器。
. js的数据类型
js中的数据类型主要分为2种:
- 基本数据类型(值类型)
- number(数字)
- string(字符串)
- boolean(布尔)
- null(空)
- undefind(未定义)
- symbol(唯一值) ES6中新增的数据类型(不能被new) 创建唯一值Symbol(10)===Symbol(10):false
- bigint(大数据值) ES6中新增的数据类型(不能被new) Number.MAX_SAFE_INTEGER(最大安全数)
- 引用数据类型
- object(对象)
- Array(数组)
- RegExp(正则)
- Date(日期)
- function(函数)
复杂数据类型存储在堆内存,存储的是地址。当我们把对象赋值给另外一个变量的时候,复制的是地址,指向同一块内存空间,当其中一个对象改变时,另一个对象也会变化。
3. 什么是闭包
MDN上这么说:一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
市面上大多认为:只有上下文不被释放才是闭包。
以下是我的观点:
说到闭包,不得不先提及javascript中函数执行的机制:在javascript语句执行过程中,有一个宿主执行环境,可以称之为执行上下文栈(ECS:Execution Context Stack),其中包含全局执行上下文(ECG:Execution Context Global);而在函数执行语句被执行的时候,比如func();,会临时开辟一个新的执行上下文,可以称之为函数执行上下文(ECF:Execution Context Func);无论哪种执行上下文,其中都包含了变量对象VO(Variable Object),标识了当前执行上下文中所拥有的所有变量;javascript语句执行过程中,依次压入当前语句所开辟的执行上下文,执行完毕后弹出;全局执行上下文ECG比较特殊,它默认被推入执行栈底,伴随整个执行周期,最后才被释放,而函数执行上下文在执行过程中,会被依次压入栈中执行,理论上执行完毕出栈;然而执行栈中存在作用域链的概念,在一个执行栈中可以引用自它之下到栈底的所有执行栈中的活动对象(AO:Active Object,当执行栈创建瞬间原有执行栈中的变量对象VO就成了AO),如果任一执行栈中的活动对象被其他执行栈引用,则该执行栈不能被释放(释放意味着执行栈销毁,变量对象被回收),这就形成了闭包,闭包的形成通常场景是外层函数执行返回结果为函数,并且该函数中引用了外层函数中的变量对象。
4. Vue和react的区别
- 数据监听实现原理不同:
Vue通过getter/setter以及一些函数的劫持,能精确知道数据变化。
React默认是通过比较引用的方式(diff)进行的,如果不优化可能导致大量不必要的VDOM的重新渲染。为什么React不精确监听数据变化呢?这是因为Vue和React设计理念上的区别,Vue使用的是可变数据,而React更强调数据的不可变,两者没有好坏之分,Vue更加简单,而React构建大型应用的时候更棒。
- 数据流的不同
vue 2.x开始进制父子组件的双向数据流,而保留了组件和UI的数据绑定
React一直不支持双向绑定,提倡的是单向数据流,称之为onChange/setState()模式。不过由于我们一般都会用Vuex以及Redux等单向数据流的状态管理框架,因此很多时候我们感受不到这一点的区别了。
- HoC和mixins
Vue组合不同功能的方式是通过mixin,Vue中组件是一个被包装的函数,并不简单的就是我们定义组件的时候传入的对象或者函数。
React组合不同功能的方式是通过HoC(高阶组件)。React最早也是使用mixins的,不过后来他们觉得这种方式对组件侵入太强会导致很多问题,就弃用了mixinx转而使用HoC。高阶组件本质就是高阶函数,React的组件是一个纯粹的函数,所以高阶函数对React来说非常简单。
- 组件通信的区别
Vue中有三种方式可以实现组件通信:
- 父组件通过props向子组件传递数据或者回调,虽然可以传递回调,但是我们一般只传数据;
- 子组件通过事件向父组件发送消息;
- 通过V2.2.0中新增的provide/inject来实现父组件向子组件注入数据,可以跨越多个层级。
React中也有对应的三种方式:
父组件通过props可以向子组件传递数据或者回调;可以通过context进行跨层级的通信,这其实和provide/inject起到的作用差不多。React本身并不支持自定义事件,而Vue中子组件向父组件传递消息有两种方式:事件和回调函数,但Vue更倾向于使用事件。在React中我们都是使用回调函数的,这可能是他们二者最大的区别。
- 模板渲染方式的不同
在表层上,模板的语法不同,React是通过JSX渲染模板。而Vue是通过一种拓展的HTML语法进行渲染,但其实这只是表面现象,毕竟React并不必须依赖JSX。
在深层上,模板的原理不同,这才是他们的本质区别:React是在组件JS代码中,通过原生JS实现模板中的常见语法,比如插值,条件,循环等,都是通过JS语法实现的,更加纯粹更加原生。而Vue是在和组件JS代码分离的单独的模板中,通过指令来实现的,比如条件语句就需要v-if来实现对这一点,这样的做法显得有些独特,会把HTML弄得很乱。
举个例子,说明React的好处:react中render函数是支持闭包特性的,所以我们import的组件在render中可以直接调用。但是在Vue中,由于模板中使用的数据都必须挂在 this 上进行一次中转,所以我们import 一个组件完了之后,还需要在 components 中再声明下,这样显然是很奇怪但又不得不这样的做法。
- 渲染过程不同
Vue可以更快地计算出Virtual DOM的差异,这是由于它在渲染过程中,会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。
React在应用的状态被改变时,全部子组件都会重新渲染。通过shouldComponentUpdate这个生命周期方法可以进行控制,但Vue将此视为默认的优化。
如果应用中交互复杂,需要处理大量的UI变化,那么使用Virtual DOM是一个好主意。如果更新元素并不频繁,那么Virtual DOM并不一定适用,性能很可能还不如直接操控DOM。
- 框架本质不同
Vue本质是MVVM框架,由MVC发展而来;
React是前端组件化框架,由后端组件化发展而来。
- Vuex和Redux的区别
从表面上来说,store注入和使用方式有一些区别。在Vuex中, s t o r e 被 直 接 注 入 到 了 组 件 实 例 中 , 因 此 可 以 比 较 灵 活 的 使 用 : 使 用 d i s p a t c h 、 c o m m i t 提 交 更 新 , 通 过 m a p S t a t e 或 者 直 接 通 过 t h i s . store被直接注入到了组件实例中,因此可以比较灵活的使用:使用dispatch、commit提交更新,通过mapState或者直接通过this. store被直接注入到了组件实例中,因此可以比较灵活的使用:使用dispatch、commit提交更新,通过mapState或者直接通过this.store来读取数据。在Redux中,我们每一个组件都需要显示的用connect把需要的props和dispatch连接起来。另外,Vuex更加灵活一些,组件中既可以dispatch action,也可以commit updates,而Redux中只能进行dispatch,不能直接调用reducer进行修改。
从实现原理上来说,最大的区别是两点:Redux使用的是不可变数据,而Vuex的数据是可变的,因此,Redux每次都是用新state替换旧state,而Vuex是直接修改。Redux在检测数据变化的时候,是通过diff的方式比较差异的,而Vuex其实和Vue的原理一样,是通过getter/setter来比较的,这两点的区别,也是因为React和Vue的设计理念不同。React更偏向于构建稳定大型的应用,非常的科班化。相比之下,Vue更偏向于简单迅速的解决问题,更灵活,不那么严格遵循条条框框。因此也会给人一种大型项目用React,小型项目用Vue的感觉。
5. 性能优化
减少请求资源大小或者次数
- 尽量和并和压缩css和js文件。(将css文件和并为一个。将js合并为一个)
- 原因:主要是为了减少http请求次数以及减少请求资源的大小
- 打包工具:webpack、gulp、grunt
- 尽量所使用的字体图标或者SVG图标来代替传统png图
- 因为字体图标或者SVG是矢量图,代码编写出来的,方大不会变形,而且渲染速度快
- 采用图片的懒加载(延迟加载)
- 目的为了,减少页面第一次加载过程中http的请求次数
- 页面开始加载时不去发送http请求,而是放置一张占位图
- 当页面加载完时,并且图片在可视区域再去请求加载图片信息
- 能用css做的效果,不要用js做,能用原生js做的,不要轻易去使用第三方插件
- 避免引入第三方大量的库。而自己却只是用里面的一个小功能
- 使用雪碧图或者是说图片精灵
- 把所有相对较小的资源图片,绘制在一张大图上,只需要将大图下载下来,然后利用
- 图片定位来讲小图展现在页面中(background-position:百分比,数值)
- 减少对cookie的使用(最主要的就是减少本地cookie存储内容的大小),因为客户端操作cookie的时候,这些信息总是在客户端和服务端传递。如果上设置不当,每次发送一个请求将会携带cookie
- 前端与后端进行数据交互时,对于多项数据尽可能基于json格式来进行传送。相对于使用xml来说传输有这个优势
- 目的:是数据处理方便,资源偏小
- 前端与后端协商,合理使用keep-alive
- 前端与服务器协商,使用响应资源的压缩
- 避免使用iframe
- 不仅不好管控样式,而且相当于在本页面又嵌套其他页面,消耗性能会更大。因为还回去加载这个嵌套页面的资源
- 在基于ajax的get请求进行数据交互的时候,根据需求可以让其产生缓存(注意:这个缓存不是我们常看到的304状态码,去浏览器本地取数据),这样在下一次从相同地址获取是数据时,取得就是上一次缓存的数据。(注意:很少使用,一般都会清空。根据需求来做)
代码优化相关
- 在js中尽量减少闭包的使用
- 原因:使用闭包后,闭包所在的上下文不会被释放
- 减少对DOM操作,主要是减少DOM的重绘与回流(重排)
- 关于重排(回流)的分离读写:如果需要设置多个样式,把设置样式全放在一起设置,不要一条一条的设置。使用文档碎片或者字符串拼接做数据绑定(DOM的动态创建)
- 在js中避免嵌套循环和"死循环"(一旦遇到死循环,浏览器就会直接卡掉)
- 把css放在body上,把js放在body下面
- 让其先加载css(注意:这里关于优化没有多大关系)
- 减少css表达式的使用
- css选择器解析规则所示从右往左解析的。减少元素标签作为对后一个选择对象
- 尽量将一个动画元素单独设置为一个图层(避免重绘或者回流的大小)
- 注意:图层不要过多设置,否则不但效果没有达到反而更差了
- 在js封装过程中,尽量做到低耦合高内聚。减少页面的冗余代码
- css中设置定位后,最好使用z-index改变盒子的层级,让盒子不在相同的平面上
- css导入的时候尽量减少@import导入式,因为@import是同步操作,只有把对应的样式导入后,才会继续向下加兹安,而link是异步的操作
- 使用window.requestAnimationFrame(js的帧动画)代替传统的定时器动画
- 如果想使用每隔一段时间执行动画,应该避免使用setInterval,尽量使用setTimeout
- 代替setInterval定时器。因为setInterval定时器存在弊端:可能造成两个动画间隔时间缩短
- 尽量减少使用递归。避免死递归
- 解决:建议使用尾递归
- 基于script标签下载js文件时,可以使用defer或者async来异步加载
- 在事件绑定中,尽可能使用事件委托,减少循环给DOM元素绑定事件处理函数。
- 在js封装过程中,尽量做到低耦合高内聚。减少页面的冗余代码
- 减少Flash的使用
存储
- 结合后端,利用浏览器的缓存技术,做一些缓存(让后端返回304,告诉浏览器去本地拉取数据)。(注意:也有弊端)可以让一些不太会改变的静态资源做缓存。比如:一些图片,js,cs
- 利用h5的新特性(localStorage、sessionStorage)做一些简单数据的存储,
- 避免向后台请求数据或者说在离线状态下做一些数据展示。
其他优化
- 避免使用iframe不仅不好管控样式,而且相当于在本页面又嵌套其他页面,消耗性能会更大。因为还回去加载这个嵌套页面的资源
- 页面中的是数据获取采用异步编程和延迟分批加载,使用异步加载是数据主要是为了避免浏览器失去响应。如果你使用同步,加载数据很大并且很慢
- 那么,页面会在一段时间内处于阻塞状态。目的:为了解决请求数据不耽搁渲染,提高页面的渲染效率。
- 解决方法:需要动态绑定的是数据区域先隐藏,等数据返回并且绑定后在让其显示延迟分批加载类似图片懒加载。减少第一次页面加载时候的http请求次数
- 页面中出现音视频标签,我们不让页面加载的时候去加载这些资源(否则第一次加载会很慢)
- 解决方法:只需要将音视频的preload=none即可。
- 目的:为了等待页面加载完成时,并且音视频要播放的时候去加兹安音视频资源
- 尽量将一个动画元素单独设置为一个图层(避免重绘或者回流的大小)
- 注意:图层不要过多设置,否则不但效果没有达到反而更差了
- 服务端渲染
- CDN技术
- 骨架屏技术
6. 输入URL到页面渲染的整个流程
首先是DNS查询,如果这一步做了智能DNS解析的话,会提供访问速度最快的IP地址回来。
DNS的作用就是通过域名查询到具体的IP。
因为IP存在数字和英文的组合(IPv6),很不利于人类记忆,所以就出现了域名。你可以把域名看成是某个IP的别名,DNS就是去查询这个别名的真正名称是什么。
在TCP握手之前就已经进行了DNS查询,这个查询是操作系统自己做的。当你在浏览器中想访问www.baidu.com时,会进行一下操作:
- 操作系统会首先在本地缓存中查询IP
- 没有的话会去系统配置的DNS服务器中查询
- 如果这时候还没得话,会直接去DNS根服务器查询,这一步查询会找出负责com这个一级域名的服务器
- 然后去该服务器查询baidu这个二级域名
- 接下来三级域名的查询其实是我们配置的,你可以给www这个域名配置一个IP,然后还可以给别的三级域名配置一个IP
以上介绍的是DNS迭代查询,还有种是递归查询,区别就是前者是由客户端去做请求,后者是由系统配置的DNS服务器做请求,得到结果后将数据返回给客户端。
PS:DNS是基于UDP做的查询,大家也可以考虑下为什么之前不考虑使用TCP去实现。
接下来是TCP握手,应用层会下发数据给传输层,这里TCP协议会指明两端的端口号,然后下发给网络层。网络层中的IP协议会确定IP地址,并且指示了数据传输中如何跳转路由器。然后包会再被封装到数据链路层的数据帧结构中,最后就是物理层面的传输了。
在这一部分中,可以详细说下TCP的握手情况以及TCP的一些特性。
当TCP握手结束后就会进行TLS握手,然后就开始正式的传输数据。
在这一部分中,可以详细说下TLS的握手情况以及两种加密方式的内容。
数据在进入服务端之前,可能还会先经过负责负载均衡的服务器,它的作用就是将请求合理的分发到多台服务器上,这时假设服务端会响应一个HTML文件。
首先浏览器会判断状态码是什么,如果是200那就继续解析,如果400或500的话就会报错,如果300的话会进行重定向,这里会有个重定向计数器,避免过多次的重定向,超过次数也会报错。
浏览器开始解析文件,如果是gzip格式的话会先解压一下,然后通过文件的编码格式知道该如何去解码文件。
文件解码成功后会正式开始渲染流程,先会根据HTML构建DOM树,有CSS的话会去构建CSSOM树。如果遇到script标签的话,会判断是否存在async或者defer,前者会并行进行下载并执行JS,后者会先下载文件,然后等待HTML解析完成后顺序执行。
如果以上都没有,就会阻塞住渲染流程直到JS执行完毕。遇到文件下载的会去下载文件,这里如果使用HTTP/2协议的话会极大的提高多图的下载效率。
CSSOM树和DOM树构建完成后会开始生成Render树,这一步就是确定页面元素的布局、样式等等诸多方面的东西
在生成Render树的过程中,浏览器就开始调用GPU绘制,合成图层,将内容显示在屏幕上了。
- 如何处理跨域问题
跨域主要分3部分:
- 协议相同
- 域名相同
- 端口相同
只要有一个不同,那么就是跨域
// 地址
http://www.baidu.com
协议:http://
域名:www.baidu.com
端口:8080(http) 443(https) 默认端口省略
http://www.baidu.com/login.html // 同源
http://www.baidu2.com/login.html // 不同源,域名不同
http://www.baidu.com:81/login.html // 不同源,端口不同
同源的目的:
目的是为了保护用户信息的安全,防止恶意网站窃取数据,否则Cookie可以共享。有的网站一般会把一些重要信息存放在cookie或者LocalStorage中,这时如果别的网站能够获取获取到这个数据,可想而知,这样就没有什么安全可言了。
限制范围:
- Cookie、LocalStorage和IndexDB 无法读取
- DOM无法获得
- AJAX 请求不能发送
主要这3种方式不行。
方案1:CORS
比较常见的就是nodejs配置CORS允许跨域。
- Access-Control-Allow-Origin
- 字段必传,为*表示允许任意域名的请求。当有cookie需要传递时,需要指定域名。
- Access-Control-Allow-Credentials
- 字段可选,默认为false,表示是否允许发送cookie。若允许,通知浏览器也要开启cookie值的传递。
- Access-Control-Expose-Headers
- 字段可选。如果想要浏览器拿到getResponesHeader()其他字段,就在这里指定。
- Access-Control-Request-Method
- 必须字段,非简单请求时设置的字段,例如PUT请求。
- Access-Control-Request-Headers
- 指定额外的发送头信息,以逗号分割字符串。
module.exports = {
//=>WEB服务端口号
PORT: 3001,
//=>CROS跨域相关信息
CROS: {
ALLOW_ORIGIN: 'http://127.0.0.1:5500',
ALLOW_METHODS: 'PUT,POST,GET,DELETE,OPTIONS,HEAD',
HEADERS: 'Content-Type,Content-Length,Authorization, Accept,X-Requested-With',
CREDENTIALS: true
}
};
app.use((req, res, next) => {
const {
ALLOW_ORIGIN,
CREDENTIALS,
HEADERS,
ALLOW_METHODS
} = CONFIG.CROS;
res.header("Access-Control-Allow-Origin", ALLOW_ORIGIN);
res.header("Access-Control-Allow-Credentials", CREDENTIALS);
res.header("Access-Control-Allow-Headers", HEADERS);
res.header("Access-Control-Allow-Methods", ALLOW_METHODS);
req.method === 'OPTIONS' ? res.send('CURRENT SERVICES SUPPORT CROSS DOMAIN REQUESTS!') : next();
});
方案2:Proxy
现在主流三大框架,react,vue,argular都使用了webpack进行工程化。在本地开发最常见的就是proxy代理,解决跨域。
主要原理是:客户端像服务器请求数据。webpack-dev-server会再本地创建一个web服务,这个服务会和客户端同源。本地服务实际上是一个node服务,它作为一个中间层会帮客户端去像服务端请求数据,然后把数据返回给客户端。
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'production',
entry: './src/main.js',
output: {
filename: 'main.[hash].min.js',
path: path.resolve(__dirname, 'build')
},
devServer: {
port: '3000',
compress: true,
open: true,
hot: true,
proxy: {
'/': {
target: 'http://127.0.0.1:3001',
changeOrigin: true
}
}
},
// 配置WEBPACK的插件
plugins: [
new HtmlWebpackPlugin({
template: `./public/index.html`,
filename: `index.html`
})
]
};
方案3:JSONP
主要原理:link,script这种是不会跨域的。所以,前端代码写一个script src = http://localhoost:80/list?callback=func,把这个链接发送给服务端。但是传递给服务端的函数必须是一个全局的函数。服务端接受到请求后,会把callback这个值,返回给客户端。客户端获取到服务端返回的指定格式字符串。发现其实就是本地的func全局函数执行,并且把数据传递给这个函数。
但是这种方式有一个弊端,那就是只能get请求,而且不安全,只要服务端支持,谁都可以调用。
下面手写一个JSONP的实现
function jsonp(url = "", callback) {
let script;
// 把传递的回调函数挂载到全局上
let name = `jsonp${
new Date().getTime()}`;
window[name] = data => {
// 从服务器获取到了结果
document.body.removeChild(script);
delete window[name];
callback && callback(data);
};
// 处理URL
url += `${
url.includes('?') ? '&' : '?'}callback=${
name}`;
// 发送请求
script = document.createElement('script');
script.src = url;
document.body.appendChild(script);
}
jsonp('http://127.0.0.1:1001/list?lx=1', result => {
console.log(result);
});
jsonp('https://matchweb.sports.qq.com/matchUnion/cateColumns?from=pc', result => {
console.log(result);
});
方案4:nginx反向代理
这是后端需要做的,其实我也不是很熟悉,大致配置方式。
server {
listen 80;
server_name 192.168.161.189;
#charset koi8-r;
#access_log logs/host.access.log main ;
location {
proxy_pass http: // 192.168.161.189:8070;
root html;
index index.html index.html;
}
}
什么是代理?
既然是代理跨域,那么代理(Proxy Server)就是一个很重要的点,这里的代理说的服务器代理,是一种很重要的服务器安全功能,也是一种很常见的设计模式,来隔绝不同的模块,解耦模块。
为什么代理是反理?
nginx就能够把用户的请求分发到空闲的服务器上,然后服务器返回自己的服务到负载均衡设备上,然后负载均衡的设备会讲服务器的服务返回给用户,所以我们并不知道为什么服务的是哪一台服务器发送出来的,这就很好的隐藏了服务器。有一句精辟的话是这么说的:“反向代理就是流量发散状,代理是流量汇聚状。”
方案5:POST MESSAGE
A.html
<iframe id="iframe" src="http://127.0.0.1:1002/B.html" frameborder="0" style="display: none;"></iframe>
iframe.onload = function () {
iframe.contentWindow.postMessage('消息', 'http://127.0.0.1:1002/');
}
//=>监听B传递的信息
window.onmessage = function (ev) {
console.log(ev.data);
}
B.html
window.onmessage = function (ev) {
// console.log(ev.data);
//=>ev.source:A
ev.source.postMessage(ev.data + '@@@', '*');
}
方案6:基于iframe的跨域解决方案1——locaction.hash
原理:也是利用iframe可以在不同域中传值的特点,而location.hash正好可以携带参数,所以利用iframe作为这个不同域之间的桥梁。
A域名页面
var iframe = document.createElement('iframe')
iframe.src = 'http://www.B.com:80/hash.html'
document.body.appendChild(iframe)
window.onhashchange = function () {
//处理hash
console.log(location.hash)
}
B域名页面
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
var res = JSON.parse(xhr.responseText)
console.log(res.msg)
parent.location.href = `http://www.A.com:80/a.html#msg=${
res.msg}`
}
}
xhr.open('GET', 'http://www.B.com:80/json', true)
xhr.send(null)
缺点
- iframe虽然能解决问题,但是安全风险还是比较重要的。
- hash传参处理起来比较麻烦。
方案7:基于iframe的跨域解决方案2——window.name
原理其实是和上面的方法一样,区别在于window.name能够传递2MB以上的数据。
A域名页面
var iframe = document.createElement('iframe')
iframe.src = 'http://www.B.com:80/name.html'
document.body.appendChild(iframe)
var times = 0
iframe.onload = function () {
if (times === 1) {
console.log(JSON.parse(iframe.contentWindow.name))
destoryFrame()
} else if (times === 0) {
times = 1
}
}
// 获取数据以后销毁这个iframe,释放内存;
function destoryFrame() {
document.body.removeChild(iframe);
}
B域名页面
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
window.name = xhr.responseText
location.href = 'http://www.A.com:80/a.html'
}
}
xhr.open('GET', 'http://www.B.com:80/json', true)
xhr.send(null)
等等其他处理方式
8. 谈谈你对原型链的理解
在JavaScript中,每当定义一个对象(函数也是对象)时候,对象中都会包含一些预定义的属性。其中每个函数对象都有一个prototype属性,这个属性指向函数的原型对象。使用原型对象的好处是所有对象实例共享它所包含的属性和方法。
原型链解决的主要是继承问题。
每个对象拥有一个原型对象,通过proto(读音:dunder proto)指针指向其原型对象,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向null( Object.proptotype.proto 指向的是null)。这种关系被称为原型链 (prototype chain),通过原型链一个对象可以拥有定义在其他对象中的属性和方法。
构造函数Parent、Parent.prototype和实例p的关系如下:(p.proto===Parent.prototype)
9. 重排和重绘
重绘(repaint或redraw):当盒子的位置、大小以及其他属性,例如颜色、字体大小等都确定下来之后,浏览器便把这些原色都按照各自的特性绘制一遍,将内容呈现在页面上。重绘是指一个元素外观的改变所触发的浏览器行为,浏览器会根据元素的新属性重新绘制,使元素呈现新的外观。
触发重绘的条件:改变元素外观属性。如:color,background-color等。
重排(重构/回流/reflow):当渲染树中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建, 这就称为回流(reflow)。每个页面至少需要一次回流,就是在页面第一次加载的时候。
10. 你做过一些什么基础的搭建
2019年下半年,2020年上半年,我在公司和同事们开发了一个基础的ui组件库。
11. 项目中最难的问题是什么
首先我讲下,我最近做的一个项目吧!
我最近半年多一直在做一个项目是桌面客户端项目迭代,它是基于electron开发的。因为项目开发的时间比较久,一直在迭代。如果追溯到最开始的时候,应该算是4年前了。2020年之前,项目都是基于jquery+electron+Eui开发的。2020年后,项目使用了新的技术栈,基于react+umi+electron+rxd进行开发。如果说是开发新功能,那么都是基于react开发的,只要需求确定,原型设计都是确定的,有后端配合,业务开发是没有难度的。但是老项目上面改造一个新功能,起初我也觉得不难。后来我在改造中,我就遇到难题了。我遇到最大的问题是,没有后端配合,没有接口文档,没有需求文档。只有一个效果图,关键效果图还和原来的功能效果差距还很大。
于是,我去找产品,找开发要需求文档和接口文档。他们给我的答复是,都好多年了,我也不知道谁开发的,文档也没了。这样子,我就只能靠自己。如果直接开发,肯定会问题很大。我就照着原来的页面,开始点点点,理解原有的逻辑。点击的时候的报文,接口我记录下来,不一致的点,接口缺失的字段,或者没有的接口我都记录下来,然后整理了一份文档。发给了项目经理,产品经理,后端,把相关的人员都发了邮件确认。最后我再按照,我整理的需求,接口,先把可以做的功能开发。开发和领导确认同时进行,当我改造的差不多了,领导也安排好了配合的人员。
12. HTTPS是如何保证安全的
过程⽐较复杂,我们得先理解两个概念
对称加密:即通信的双⽅都使⽤同⼀个秘钥进⾏加解密,⽐如特务接头的暗号,就属于对称加密
对称加密虽然很简单性能也好,但是⽆法解决⾸次把秘钥发给对⽅的问题,很容易被⿊客拦截秘钥。
⾮对称加密:
- 私钥 + 公钥 = 密钥对
- 即⽤私钥加密的数据,只有对应的公钥才能解密,⽤公钥加密的数据,只有对应的私钥才能解密
- 因为通信双⽅的⼿⾥都有⼀套⾃⼰的密钥对,通信之前双⽅会先把⾃⼰的公钥都先发给对⽅
- 然后对⽅再拿着这个公钥来加密数据响应给对⽅,等到到了对⽅那⾥,对⽅再⽤⾃⼰的私钥进⾏解密
⾮对称加密虽然安全性更⾼,但是带来的问题就是速度很慢,影响性能。
解决⽅案:
那么结合两种加密⽅式,将对称加密的密钥使⽤⾮对称加密的公钥进⾏加密,然后发送出去,接收⽅使⽤私钥进⾏解密得到对称加密的密钥,然后双⽅可以使⽤对称加密来进⾏沟通。
此时⼜带来⼀个问题,中间⼈问题:
如果此时在客户端和服务器之间存在⼀个中间⼈,这个中间⼈只需要把原本双⽅通信互发的公钥,换成⾃⼰的公钥,这样中间⼈就可以轻松解密通信双⽅所发送的所有数据。
所以这个时候需要⼀个安全的第三⽅颁发证书(CA),证明身份的身份,防⽌被中间⼈攻击。
证书中包括:签发者、证书⽤途、使⽤者公钥、使⽤者私钥、使⽤者的HASH算法、证书到期时间等。
但是问题来了,如果中间⼈篡改了证书,那么身份证明是不是就⽆效了?这个证明就⽩买了,这个时候需要⼀个新的技术,数字签名。
数字签名就是⽤CA⾃带的HASH算法对证书的内容进⾏HASH得到⼀个摘要,再⽤CA的私钥加密,最终组成数字签名。
当别⼈把他的证书发过来的时候,我再⽤同样的Hash算法,再次⽣成消息摘要,然后⽤CA的公钥对数字签名解密,得到CA创建的消息摘要,两者⼀⽐,就知道中间有没有被⼈篡改了。
这个时候就能最⼤程度保证通信的安全了。
13. 你怎么带实习生的
实习生过来,我一般是让实习生先熟悉下框架。前期的时候,让他们vue的官网或者react的官方文档先熟悉下,每个都敲一遍。然后会给他们安排一个任务,比如使用框架写一个计算器,或者写一个项目中的一个ui图,只要简单把效果功能做出来就可以了。基本的使用会了以后,我一般会让他们参与到项目中来。刚开始也是做一些简单的功能,比如一些查询的功能。还有就是一些简单的bug,比如一些简单的样式,还有一些文案这种bug改下。这些都比较熟以后,一些业务相关的,稍微复杂的也让他们开始上手,但是我也会把关的,会review他们的代码。也不是直接放任不管的方式。一点点给他们增加难度,让他们慢慢进度。
14. 你遇到的最难的技术问题是什么
去年工作的时候,我做了一个项目。项目背景是一个c++的客户端项目改版成web端。前期技术技术选型错误,加上需求上面也没有要求需要兼容ie浏览器,然后就遇到了很多的坑。其中一个问题是,滚动条问题。chrome浏览器是可以使用css对滚动条进行设置样式,但是ie浏览器不能对滚动条进行设置,而且ie浏览器下的滚动条很粗,就体验不好。开始的时候,我不知道ie是不能设置的,我就查了很多资料,怎么对ie的滚动条进行设置,查到的资料进行操作基本上都是对滚动条颜色设置,边框设置。然后我还在看别人的产品,找了很多的产品,大部分ie下也是很粗的,我就想是不是ie不可以。但是在极客时间上看到,他有个导航的滚动条在ie下是满足我们需求的。我就去研究为何这样子,点击dom都没看到css的样式。于是我就想,是不是js实现的。我就去搜索js实现滚动条的效果,找了一些解决的思路,然后按照他说的思路去尝试,中间也失败了很多次,最后还是成功解决了这个问题。
15. 后端设置cors,跨域后,前端怎么设置,后端可以拿到cookie
浏览器默认情况下无法主动跨域向后端发送cookie,如果你要发送cookie给server的话, 就需要将withCredentials设置为true了。
16. 谈一下你对js运行机制的理解
- js作为浏览器脚本语言,它的主要用途是与用户互动,以及操作DOM,因此js是单线程,也避免了同时操作同一个DOM的矛盾问题;
- 为了利用多核CPU的计算能力,H5的Web Worker实现的“多线程”实际上指的是“多子线程”,完全受控于主线程,且不允许操作DOM;
- js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环);
- 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack);
- 如果在微任务执行期间微任务队列加入了新的微任务,会将新的微任务加入队列尾部,之后也会被执行;
17. 浏览器渲染机制
页面之所以能渲染
- 从服务器获取需要渲染的内容(URL解析/DNS/TCP/HTTP…)
- 浏览器基于自己的渲染引擎(例如:webkit/gecko/trident/blink…)开始自上而下加载渲染代码
服务器获取的是文件流
- 浏览器首先把16进制的字节流信息编译为“代码字符串”
- 按照w3c规则进行字符解析,生成对应的Tokens,最后转换为浏览器内核可以识别渲染的DOM节点
- 按照节点最后解析为对应的树DOM树,CSSOM树,Render树
浏览器
- 在css资源还没有请求回来之前,先生成DOM树
- 当所有的css请求回来之后,浏览器按照css的导入顺序,依次进行渲染,最后生成cssom树
- 把DOM树和CSSOM树结合在一起,生成有样式,有结构的render树
- 浏览器按照渲染树,在页面中进行渲染和解析
- 计算元素在设备饰扣的大小和位置布局(layout)或重拍/回流(reflow)
- 根据渲染树以及回流得到的几何信息,得到节点的绝对像素=>绘制/重绘(painting)
link和@import都是导入外部样式(从服务器获取样式文件)
- 遇到link,浏览器会派出一个新的线程(HTTP线程)去加载资源文件,与此同时GUI渲染线程会继续向下渲染代码。不论css是否请求回来,代码都会继续渲染
- 遇到@import,GUI渲染线程会暂时停止渲染,去服务器加载资源文件,资源文件没有返回之前,是不会继续渲染的@import阻碍浏览器的渲染,项目中尽量少用
- 如果是style,GUI直接渲染
正常情况下js也会阻碍GUI的渲染
- js在一般在页面的底部,就是为了确保DOM树生成完才去加载js
- 可能会与defer和async异步管控js的请求
性能优化
- 减少DOM树渲染的时间(HTML层级不要太深,标签语义化…)
- 减少CSSOM树渲染时间(选择器是从右向左解析,所以尽可能减少选择器的层级【less/sass中的层级嵌套虽然好用,但是是一个大坑】)
- 减少HTTP的请求次数和请求大小
- 一般会把CSS放在页面的开始位置(提前请求资源 用link别用@import,对于移动端来讲,如果CSS比较少,尽可能采用内嵌式即可…)
- 为了避免白屏,可以进来第一件事,快速生成一套 loding 的渲染树(前端骨架屏);服务器的SSR骨架屏所提高的渲染是避免了客户端再次单独请求数据,而不是样式和结构上的(首屏处理)
- 把JS放在页面底部以及尽可能使用defer或者async
- CRP性能节点优化
18. 前端怎么实现微信的授权
网页外链跳转的方式:
- 请求后台的接口,会返回一个微信扫码的界面地址,使用js跳转过去即可
- 用户在扫完码点击确定授权后,后台会拿到微信授权用户的信息后,会帮我们跳转到事先给后台重定向的地址页面(这里我是新建了一个空白页用来接收后台返回的数据),在地址后面后台会拼接一个token,我们拿到这个token,去获取用户信息,跳转到首页,即可完成登录
网页内嵌二维码方式:
- 配置好下面相关参数,即可生成微信授权登录/绑定二维码
- 后面的逻辑根据后台返回的数据来处理即可,同方法一步骤二类似
微信公众号网页授权登录:
- 创建一个按钮,点击触发事件,跳转到微信授权页面
- 在重定向的页面拿到微信那边给我们返回的code,会拼接在URL后面,我们拿到这个code再请求后台的接口换取token,完成微信登录
19. diff算法原理
React最为核心的就是Virtual DOM和Diff算法。React在内存中维护一颗虚拟DOM树,当数据发生改变时(state&props),会自动的更新虚拟DOM,获得一个新的虚拟DOM树,然后通过Diff算法,比较新旧虚拟DOM树,找出最小的有变化的部分,将这个变化的部分(Patch)加入队列,最终批量的更新这些 Patch 到实际的 DOM 中。
参考:https://www.cnblogs.com/forcheng/p/13246874.html
20. 讲下hooks的使用
useState、useReducer、useCallback,useEffect,useLayoutEffect、自定义hook、memo等
21. 讲下reactNative
参考:https://www.jianshu.com/p/916181d58174