1、传统布局和flex布局有什么区别
-
性能上flex允许子元素不设置宽高,而是由算法动态去计算,性能会比设定好宽高的稍慢,但在这个时代大体没有影响;
-
传统布局+flex != 所有布局,除了传统布局和flex布局外还有grid布局、多列布局、双飞翼布局、圣杯布局等多种方法;
-
不要把盒模型和布局混淆了,布局是DOM元素在文档中的位置排布,而模型指的是DOM元素的宽高大小的计算,模型一般由content-box,border-box,padding-box,-webkit-box等,默认为content-box;
-
flex布局成为一个新的W3C标准规范;
-
flex在移动端兼容性:ios8不兼容,android4.4不兼容;
-
-webkit-box:在移动端开发中,所有的浏览器基本上都支持-webkit-box
-
文档流:文档流指元素在文档中的位置由元素在html里的位置决定,块级元素独占一行,自上而下排列;内联元素从左到右排列;
-
脱离文档流的方式:
1)浮动,通过设置float属性;
2)绝对定位:通过设置position:absolute;
3)固定定位:通过设置position:fixed;
2、vue的双向绑定原理是怎么实现的(描述什么时候监听变化,什么时候触发变化)
- 一个vue对象实例化的过程中会生成一个Observer对象,Observer通过Object.defineProperty里面的getter和setter实现数据的变化;
- vue.js对模板做编译会解析生成一个个指令对象,每个指令对象都会关联一个watcher,通过watcher监听数据变化触发setter方法;
3、如何比较两个颜色的相似程度
- 首先讲颜色拆分成r/g/b三个值,如果是字符串的颜色如#aabbff或者rgb(255, 123, 100)可以用正则表达式取出对应的r/g/b的值,对于16进制字符串,可以使用parseInt('0xaa')转10进制整数。
- 对于两个颜色,可以使用距离Math.sqrt((r1-r2)(r1-r2) + (g1-g2)(g1-g2) + (b1-b2)(b1-b2))进行比较,距离近则相似。当然也可以用Math.hypot(r1-r2, g1-g2, b1-b2)来简化上述运算。
4、一个单页面应用,有6张页面,F、E、A、B、C、D。页面ABCD构成了一个冗长的用户验证过程。目前A、B、C对应用户验证过程的第1步,第2步,第3步。页面F是首页,E是某张业务相关页面。用户到达页面E后,系统发现用户没有认证,触发验证流程,到达页面A。然后开始A——>B-->C-->D流程。页面D是验证结果页面(验证成功页面)。请问,如果到达页面D后,如何让用户点击返回可以返回页面F,而忽略中间流程(注:用户可能根本没有到达过F,比如微信分享直接进入了E)
这个问题初一看是对单页面路由架构的考察,也是一个很好的引入问题,可以考察非常多方面。比如说:如何实现页面切换动画?A、B、C都是表单的话,如何缓存用户输入完成的表单数据?...回到问题,因为history api 提供了push/pop/replace三种操作,无论是其中的任何一种都无法实现上述的效果。一个路由系统,首先要监听浏览器地址的变化,然后根据变化渲染不同的页面。
- 在页面到达D后,关闭对路由变化页面渲染的监听。
- 路由系统要进行多次pop,可以用history.go(-n)实现;
- 路由栈清空到只剩下一张页面时,将这张页面替换为F;
- Push一张页面D。如果在HTML上有一个类似『轮播图』的设计,就是每一张页面是一张轮播图,将轮播图设置成只有『F』和『D』;
- 恢复路由监听。这个问题的另一个考点是,在上述完整的计算过程中,需要知道当前历史纪录中的页面数,页面数可以通过localStorage实现,在ls中维护一个变量,每次push的时候+1,并写入history.state.
5、一个无序数组中,怎么找到最大的子序列?
- 最简单也最暴力的解法:首先列出 所有的子序列,然后找出其中和最大的 即可;实现思路:一个记录当前最大值的变量maxSum;一个 子序列开始和结束的游标 变量;一个 当前子序列的和 的暂存变量,我们称之为 currentSum 或者 tmpSum (下文中 使用currentSum)找到所有的 子序列, 我们可以通过两层循环的方式来解决
1)第一层循环 i 从 0 ~ length -1;
第二层循环 j 从 i ~ length - 1;
这样的循环里 就可以找到所有的子序列了
下一步 我们是要计算出所有子序列的和
最简单的办法 就是 第三层循环从 i ~ j 累加求出和 然后求出来的每个和 和 maxSum 去比较,如果比maxSum 大 就替换
伪代码: maxSum = maxSum < currentSum ? currentSum : maxSum;
三层循环结束后 maxSum就是我们要 求的解
return maxSum即可
这个算法的时间复杂度是O(n^3);
2)简化解法:我们在第二层循环中,我们已经知道 当前的 i/j 之前的方法是在第三层的循环中 计算 i ~ j的和
现在 我们在第二层中 在进入第二层之前 我们重置一下currentSum
第一次循环 是 i ~ i 当前我们就把i的值 记录到currentSum去跟maxSum 对比 然后 maxSum = maxSum < currentSum ? currentSum : maxSum;
第二次循环 是 i ~ i + 1 我们就把当前的 i + 1 累加到currentSum 这时候的currentSum就是 i ~ i +1 的值,再去跟maxSum去比 然后 maxSum = maxSum < currentSum ? currentSum : maxSum;
以此类推
第二层的循环中 就可以计算出 以当前 i 开头的子序列中 最大的子序列是多少
现在我们看回到 第一层循环i 的取值 是从 0 ~ length -1 那么我们是不是 可以找到 i 从 0 ~ length -1 所有的子序列中和最大的
伪代码思路:
第一层 i (0~ length -1)
currentSum = 0;
第二层 j (i ~ length -1)
currentSum 累加
maxSum = maxSum < currentSum ? currentSum : maxSum;
return maxSum;
算法的时间复杂度是O(n^2)
3) demo数组:[-2, 1, -3, 4, -1, 2, 1, -5, 4]
首先我们可以简单的简化一下这个数组 把相邻的同 正负的数字合起来,因为同符号的连续数 一定会同时存在在最大子序列里
比如[-1, -2, -3, 1, 2, 13]那跟[-6, 16]是没有区别的
[-2, 1, -3, 4, -1, 2, 1, -5, 4] ==> [-2, 1, -3, 4, -1, 3, -5, 4]
然后 我们从头开始看
-2 这是第一个元素
那么 我们认为 当前的 最大子序列和 就是 -2
然后 发现了一个正数 1
那我们可以确定 -2 一定不包含在 我们的最大子序列中
也就是说 数组开头 如果是负数 可以忽略过去
现在 我们的数组 变成了 [1, -3, 4, -1, 3, -5, 4]
同理 结尾的如果是 负数 也不需要考虑
现在我们的数组 变成了[1, -3, 4, -1, 3, -5, 4]
我们继续 现在 第一个元素是 1 最大和 是 1
然后下一个数是 -3
那么 -3 对 1 这个数,起到了阻断作用 也就是说 -3 把 前面所有正数 积累的能量都磨平了 甚至还变成了一个负数
那么 -3 我们称之为 一个阻断
当前的 最大和 还是 1
现在 我们到了 4
那么现在的最大值 就是4
我们继续向下看
下个数字是 -1 之前最大的和 是 4
加起来之后是 3 影响并不大
我们继续带着他 向后看
下一个 是个正数 3
也就是 4 -1 3 这样的情况
我们是不是可以认为这个 -1 虽然降低了 和 但是 他连接了左右的正数 让我们当前的 最大值 变成了 6 更新最大值 继续看
下一个是 -5
同理 之前的 6 + -5 和 还是 1 也没有阻断 我们去看看 后边 有没有一个大数 拯救我们
后边一个数 是 4
加上 我们刚才记录的1 和是 5 最后还是没有挑战成功 所以 最大的和 还是之前的 6
公式:nums是我们的源数组 nums[i]就是我们的当前元素currentMax[i]记录 我们以i结尾的子序列里 最大的一个子序列 那么 currentMax[i] = max(currentMax[i-1] + nums[i], nums[i])
这个公式被称之为 状态转移公式 我们的这种解法 称之为 动态规划解法 简称:PD
然后我们去遍历currentMax这个数组里 里边的最大值 就是我们要找的 最大值
伪代码:
var maxSubArray = function(nums) {
// 初始化源数组,初始化An为结束的最大值
let A = nums;
let dp = [];
let maxSum = A[0];
dp[0] = A[0];
for (let i = 1; i < A.length; i++) {
// 状态转移公式
dp[i] = max(A[i], dp[i-1] + A[i])
maxSum = dp[i] > maxSum ? dp[i] : maxSum;
}
return maxSum;
}
function max(a, b) {
return a > b ? a : b;
}
复制代码
6、函数防抖和函数节流的应用场景和原理
-
函数节流场景:
1)例如:实现一个原生的拖拽功能(如果不用H5 Drap和Drop API),我们就需要一路监听mousemove事件,在回调中获取元素当前位置,然后重置dom的位置。不加以控制,每移动一定像素而发出的回调数量是会非常惊人的,回调中又伴随着DOM操作,继而引发浏览器的重排和重绘,性能差的浏览器可能会直接假死。 2)这时,我们就需要降低触发回调的频率,比如让它500ms触发一次或者200ms,甚至100ms,这个阀值不能太大,太大了拖拽就会失真,也不能太小,太小了低版本浏览器可能会假死,这时的解决方案就是函数节流【throttle】。 3)函数节流的核心就是:让一个函数不要执行得太频繁,减少一些过快的调用来节流 函数去抖场景:
- 对于浏览器窗口,每做一次resize操作,发送一个请求,很显然,我们需要兼容resize事件,但是和mouseover一样,每缩小(或者放大)一次浏览器,实际上会触发N多次的resize事件,这时的解决方案就是节流【debounce】。
- 函数去抖的核心就是:在一定时间段的连续函数调用,只让其执行一次 整体函数总结一下:
- 对于按钮防点击来说的实现:一旦开始一个定时器,只要定时器还在,不管你怎么点击都不会执行回调函数,一旦定时器结束并设置为null,就可以再次点击了。
- 对于延时执行函数来说的实现:每次调用防抖动函数都会判断本次调用和之前的时间间隔,如果小于需要的时间间隔,就会重新创建一个定时器,并且定时器的延时为设定时间减去之前的时间间隔,一旦时间到了,机会执行相应的回调函数。 节流 防抖动和节流本质是不一样的,防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行。
-
underscore节流函数,返回函数连续调用时,func执行频率限定为 次/ wait
-
@param {function} func 回调函数
-
@param {number} wait 表示时间窗口的间隔
-
@param {object} options 如果想忽略开始函数的调用,传入{leading:false}
-
如果想忽略结尾函数的调用,传入{trailling: false}
-
两者不能共存,否则函数不能执行
-
@return {function} 返回客户调用函数
_.throttle = function(func, wait, options) {
var context, args, result;
var timeout = null;
// 之前的时间戳
var previous = 0;
// 如果 options 没传则设为空对象
if (!options) options = {};
// 定时器回调函数
var later = function(){
// 如果设置了 leading,就将 previous 设为0
// 用于下面函数的第一个 if 判断
previous = options.leading === false ? 0 : _.now();
// 置空一是为了防止内存泄露,二是为了下面的定时器判断
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
}
return function() {
// 获得当前时间戳
var now = _.now();
// 首次进入前者肯定为 true
// 如果需要第一次不执行函数,就将上次时间戳设为当前的
// 这样在接下来计算 remaining 的值时会大于0
if (!previous && options.leading === false) previous = now;
// 计算剩余时间
var remaining = wait - (now - previous);
context = this;
args = arguments;
// 如果当前调用已经大于上次调用时间 + wait
// 或者用户手动调了时间
// 如果设置了 trailling,那么第一次会进入这个条件
// 还有一点,你可能会觉得开启了定时器那么应该不会进入这个if 条件了,其实还是会进入的,因为定时器的延时并不是
// 准确的时间,很可能你设置了2秒,但是他需要2.2秒才触发
// 这时候就会进入这个条件
if (remaining <= 0 || remaining > wait) {
// 如果存在定时器就清理掉否则会调用二次回调
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
}
previous = now;
result = func.apply(context, args);
if (!timeout) {
context = args = null;
} else if (!timeout && options.trainlling !== false){
// 判断是否设置了定时器和 trailling
// 没有的话就开启一个定时器
// 并且不能同时设置 leading 和 trailling
timeout = setTimeout(later, remaining);
}
return result;
}
}
复制代码
7、电商网站A和电影票网站B合作,A的用户,可以通过A网站下单购买电影票,之后跳转到B(不需要登录)去选座位,如果A、B是同域名,比如,a.domain.com, b.domain.com能不能共享cookie?如果不同域如何处理?
这其实是个单点登录的问题,同一级域名下设置cookie设在一级域名下,同级域名下的cookie皆共享。 缺点:
-
同一级域名限制
-
登录的规则一致,如果不同域的话就是跨域问题,跨域问题可以用jsonp解决
-
二者的区别: 1)作用域
相同浏览器的不用页面间可以共享相同的localStorage(页面属于相同域名和端口),但是sessionStorage只能在同源(相同域名相同端口)同窗口访问,但是当sessionStorage在同一窗口下转到同源页面还是可以访问的,因为这时候还是同源同窗口,不要单纯理解为两个不同的页面之间不能访问相同sessionStorage。比如你在A网页设置了一个sessionStorage的值,然后你同时在新的窗口下打开B网页,这时候你尝试在B网页得到A网页设置的sessionStorage是不可以的,但是当你在A网页跳转到B网页的时候,这时候你会发现B网页可以访问A网页中的sessionStorage。所以sessionStorage针对的是同源同窗口,不是同源同页面。 2)生命周期
localStorage生命周期是永久,这意味着除非用户自己清除localStorage信息或者用户清除浏览器缓存,否则这些信息将永久存在。sessionStorage生命周期为当前窗口或标签页,一旦窗口或者标签页被永久关闭了,那么所有通过sessionStorage存储的数据也被清空了
cookie与sessionStorage、localStorage的区别
1、cookie可以在浏览器端与服务器端之间通信,是服务器端获取用户信息、保持一种持久客户端状态的关键。而sessionStorage、localStorage虽然也可以保存会话数据,但是不能与服务器端进行信息交换。
2、cookie的容量比较小,而sessionStorage、localStorage有较大的容量。
3、试用的浏览器范围不同,由于sessionStorage与localStorage是HTML5标准推出的,所以在IE7及以下的版本不适用,替代方案是采用IE的userData。
复制代码
三者的异同
8、说说什么是xss攻击?如何攻击,如何防御?
1、XSS跨网站指令码(Cross-site- scripting,简称:xss)是一种网站应用程式的安全漏洞攻击,是代码注入的一种。它允许恶意使用者将程式码注入到网页上,其他使用者在观看网页时就会收到影响。这类攻击通常包含了HTML以及使用者端脚本语言。
2、XSS分为三中:反射型,存储型和DOM-based
3、如何攻击:
XSS通过修改HTML节点或者执行JS代码来攻击网站;eg:通过URL获取某些参数(反射型攻击)
另一种场景:写了一篇包含攻击代码的 文章,那么可能浏览文章的用户都会被攻击到。这种攻击类型是存储型攻击,也可以说是DOM-based攻击,这种攻击打击面更广。
4、如何防御:最普遍的做法是 转义输入输出的内容,对于引号、尖括号、斜杠进行转义,这里写图片描述、内容安全策略(CSP)是一个额外的安全层,用于检测并削弱某些特定类型的攻击,包括跨站脚本(XSS)和数据注入攻击等。无论是数据盗取、网站内容污染还是散发恶意软件,这些攻击都是主要的手段,我们可以通过CSP来尽量减少XSS攻击。CSP本质上也是建立白名单,规定了浏览器只能够执行特定来源的代码。通过可以通过HTTP Header中的Content-Security-Policy来开启CSP,只允许加载本站资源,只允许加载HTTPS协议图片,允许加载任何来源框架
复制代码
9、CSRF攻击是什么?如何防范?
CSRF概念:CSRF跨站点请求伪造(Cross-Site Request Forgery),跟XSS攻击一样,存在巨大的危害性,参考文章
10、如果发现在某个用户的电脑上,网站的静态资源打不开了,如何确定是CDN的问题还是那个用户机器、浏览器的问题?
1、自己的电脑访问cdn地址,排除是否cdn问题;
2、让用户换浏览器再试,排除用户浏览器的问题(禁用所有插件、清除缓存);
3、已经排除cdn和用户浏览器的问题,那就是用户的机器(或者所在的网络)有问题,有可能是用户所在的公司网络禁止下载某些资源。
4、推荐一个[本地网络诊断的工具](https://cdn.dns-detect.alicdn.com/https/doc.html),可以检查dns和本地ip地址是否正常
复制代码
11、请说说在hybrid端实现类似原生般流畅的体验,要注意哪些事项?
1、资源加载,采用预加载,优先加载到内存中,做到无缝切换,使用原生loading
2、离线加载静态资源,不走网络请求;
3、尽量做到局部更新,动画使用transform,will-change来提高性能;
4、使用webkit over scrolling加速滚动条的滚动,去掉user selection,去掉高亮,手势交互原生实现,复杂交互如日期选择器等调用原生组件
5、遵循APP UI设计规范;
6、考虑文件diff更新,或主动后台请示,在某个时刻更新,两个版本直接的兼容问题;
7、APP方法加上安全性的考虑。
复制代码
12、事件触发的三个阶段
document 往事件触发处传播,遇到注册的捕获事件会触发 传播到事件触发处时触发注册的事件,从事件触发处往document传播,遇到注册的冒泡事件会触发,事件触发一般来说会按照上面的顺序进行,但是也有特例,如果给一个目标节点同时注册冒泡和捕获事件,事件触发会按照注册的顺序执行。
// 以下会先打印冒泡然后是捕获
node.addEventListener('click', (event) => {
console.log('冒泡')
}, false);
node.addEventListener('click', (event) => {
console.log('捕获');
}, true)
复制代码
** 注册事件**
通常我们使用addEventListener注册事件,该函数的第三个参数可以是布尔值,也可以是对象。对于布尔值 useCapture参数来说,该参数默认值是 false,useCapture决定了注册的事件是捕获事件还是冒泡事件。对于对象参数来说,可以使用以下几个属性:
- capture,布尔值,和useCapture作用一样,
- once 布尔值,值为true表示该回调只会调用一次,调用后会移除监听
- passive, 布尔值,表示永远不会调用 preventDefault
一般来说,我们只希望事件只触发在目标上,这时候可以用stopPropagation来阻止事件的进一步传播。通常我们认为stopPropagation是用来阻止事件冒泡的,其实该函数也可以阻止捕获事件。stopImmediatePropagation同样也能实现阻止事件,但是还能阻止该事件目标执行别的注册事件。
node.addEventListener('click', (event) => {
event.stopImmediatePropagation();
console.log('冒泡');
}, false);
// 点击 node 只会执行上面的函数,该函数不会执行
node.addEventListener('click', (event) => {
console.log('捕获');
})
复制代码
事件代理: 如果一个节点中的子节点是动态生成的,那么子节点需要注册事件的话应该注册在父节点上
ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
let ul = document.querySelector('##ul')
ul.addEventListener('click', (event) => {
console.log(event.target);
})
</script>
复制代码
事件代理的方式相对于直接给目标注册事件来说,有以下优点 节省内存 不需要给子节点注销事件 点击查看详解