节流 (需要背代码)
一段时间内只能触发一次,如果这段时间内触发多次事件,只有第一次生效会触发回调函数,一段时间过后才能再次触发(一定时间内只执行第一次)
应用场景
① 鼠标连续不断地触发某事件(如点击),只在单位时间内只触发一次;
② 懒加载时要监听计算滚动条的位置,但不必每次滑动都触发,可以降低计算的频率,而不必去浪费 CPU 资源;
function throttle(fn,delay){
let flag = true;
return function(){
if(flag){
flag = false;
setTimeout(() =>{
flag = true
},delay)
return fn()
}
}
}
防抖 (需要背代码)
在事件被触发时,延迟n秒后再触发回调函数,如果n秒内又触发了事件,则会重新开始计算时间(一定时间内最后一次生效)
应用场景
用户在输入框中连续输入一串字符时,可以通过防抖策略,只在输入完后,才执行查询的请求,这样可以有效减
少请求次数,节约请求资源
function debounce(fn,delay){
let timer = null;
return function(){
if(timer){
clearTimeout(timer)
}
timer = setTimeout(fn,delay)
}
}
什么是重排?什么事重绘
重排
当DOM的变化影响了元素的几何信息(元素的的位置和尺寸大小),浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排。
重排也叫回流,简单的说就是重新生成布局,重新排列元素。
下面情况会发生重排(挑几个就好):
- 页面初始渲染,这是开销最大的一次重排
- 添加/删除可见的DOM元素
- 改变元素位置
- 改变元素尺寸,比如边距、填充、边框、宽度和高度等
- 改变元素内容,比如文字数量,图片大小等
- 改变元素字体大小
- 改变浏览器窗口尺寸,比如resize事件发生时
- 激活CSS伪类(例如::hover)
- 设置 style 属性的值,因为通过设置style属性改变结点样式的话,每一次设置都会触发一次reflow
- 查询某些属性或调用某些计算方法:offsetWidth、offsetHeight等,除此之外,当我们调用 getComputedStyle方法,或者IE里的 currentStyle 时,也会触发重排,原理是一样的,都为求一个“即时性”和“准确性”。
重绘
一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,叫做重绘。
关于重排的优化
浏览器渲染内容流程
用户输入 URL,浏览器进程进行什么操作??
最简回答:
● 在解析前,会执行预解析操作,会预先加载 CSS、JS 等文件
● HTML 解析器解析 HTML,生成 DOM 树
● CSS 解析器解析 CSS,产生 CSS 规则树
● 解析 JS 脚本,该过程中需要等待 JS 执行完成才继续解析 HTM
更长一点的回答:
网络篇:
构建请求
查找强缓存
DNS解析
建立TCP连接(三次握手)
发送HTTP请求(网络请求后网络响应)
浏览器解析篇:
解析html构建DOM树
解析css构建CSS树、
样式计算
生成布局树(LayoutTree)
浏览器 渲染篇:
建立图层树(LayerTree) 生成绘制列表
生成图块并栅格化
显示器显示内容
最后断开连接:TCP 四次挥手 (浏览器会将各层的信息发送给GPU,GPU会将各层合成,显示在屏幕上)
Http和Https区别(高频)
1.`HTTP` 的URL 以http:// 开头,而HTTPS 的URL 以https:// 开头
2.`HTTP` 是不安全的,而 HTTPS 是安全的
3.`HTTP` 标准端口是80 ,而 HTTPS 的标准端口是443
4.`在OSI` 网络模型中,HTTP工作于应用层,而HTTPS 的安全传输机制工作在传输层
5.`HTTP` 无法加密,而HTTPS 对传输的数据进行加密
6.`HTTP`无需证书,而HTTPS 需要CA机构wosign的颁发的SSL证书
http特性以及状态码
200响应成功
301永久重定向
302临时重定向
304资源缓存
403服务器禁止访问
404服务器资源未找到
500 502服务器内部错误
504 服务器繁忙
1xx Informational(信息状态码) 接受请求正在处理
2xx Success(成功状态码) 请求正常处理完毕
3xx Redirection(重定向状态码) 需要附加操作已完成请求
4xx Client Error(客户端错误状态码) 服务器无法处理请求
5xx Server Error(服务器错误状态码) 服务器处理请求出错
http三次握手
- 第一步:客户端发送SYN报文到服务端发起握手,发送完之后客户端处于SYN_Send状态
- 第二步:服务端收到SYN报文之后回复SYN和ACK报文给客户端
- 第三步:客户端收到SYN和ACK,向服务端发送一个ACK报文,客户端转为established状态,此时服务端收到ACK报文后也处于established状态,此时双方已建立了连接
http四次挥手
刚开始双方都处于 establised 状态,假如是客户端先发起关闭请求,则:
- 第一次挥手:客户端发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于FIN_WAIT1状态。
- 第二次挥手:服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 + 1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT状态。
- 第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态。
- 第四次挥手:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 + 1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态
- 服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。
Promise
Promise是异步编程的一种解决方案,它是一个对象,可以获取异步操作的消息,他的出现大大改善了异步编程的困境,避免了地狱回调,它比传统的解决方案回调函数和事件更合理和更强大。
Promise 状态
- pending: 初始状态,不是成功或失败状态。
- fulfilled: 意味着操作成功完成。
- rejected: 意味着操作失败。
Promise 优缺点
⚠️ promise的then如果传入的两个函数那么第一个是 成功回调 ,第二个是失败回调
有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。
Promise 也有一些缺点。首先,无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。第三,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
谈谈Promise.all方法,Promise.race方法以及使用
⚠️无论是all 还是 rece 当中有一个promise失败那么全部都会失败
- Promise.all 可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。
- 顾名思义,Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
浏览器缓存机制
将一些资源(如HTML文件、CSS文件、JavaScript文件、图像等)缓存在本地存储介质中,以便下
DNS缓存、CDN缓存。浏览器缓存、页面本地缓存等等,有一个良好的缓存策略可以减低重复资源的请求,降低服务器的开销,提高用户页面的加载速度。
回答的时候优先回答 浏览器缓存
http缓存
在浏览器加载资源的时候,首先会根据请求头的expires和cache-control判断是否命中强缓存策略,判断是否向远程服务器请求资源还是去本地获取缓存资源。
这当中又分为
强缓存
Expires
在浏览器第一个请求资源时,服务器端的响应头会附上Expires这个响应字段,当浏览器在下一次请求这个资源时会根据上次的expires字段是否使用缓存资源(当请求时间小于服务端返回的到期时间,直接使用缓存数据)
Cache-control
cache-control字段优先级高于上面提到的Expires,值是相对时间。
在cache-control中有常见的几个响应属性值,它们分别是(下面的属性背 max-age 和 no-cache就可以了)
协商缓存
上面说的强缓存简单来说就是给缓存资源设置一个过期时间,浏览器每次想要请求资源时都会判断一下这个资源是否过期,只在过期的时候才去向服务器请求资源。
如果浏览器在某次请求资源的时候发现本地缓存的资源过期了,这时候就会向服务器发送请求,并设置协商缓存
因此,协商缓存是需要浏览器向服务器发送请求的,服务器会根据 request header 里面的一些参数来判断是否命中协商缓存:如果命中,则返回 304 状态码,并且在 response header中携带新的参数,通知浏览器从缓存中读取资源;如果没有命中,则返回 200 状态码,并且服务器会返回新的缓存资源给浏览器
对async/await 的理解
所以,async 函数执行会返回一个 Promise 对象,如果在函数中 return 一个直接量,async 会把这个直接量通过 Promise.resolve() 封装成 Promise 对象。
那如果 async 函数没有返回值,又该如何?很容易想到,它会返回 Promise.resolve(undefined)。
这和普通返回 Promise 对象的函数并无二致。
await 到底在等啥?
await 表达式的运算结果取决于它等的是什么。
- 如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。
- 如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。
function getSomething() {
return "something";
}
async function testAsync() {
return Promise.resolve("hello async");
}
async function test() {
const v1 = await getSomething();
const v2 = await testAsync();
console.log(v1, v2);
}
test();
async/await 如何捕获异常
使用 try catch的方式
async function fn(){
try{
let a = await Promise.reject('error')
}catch(error){
console.log(error)
}
}
哪些情况会导致内存泄漏
- 意外的全局变量: 由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。
- 被遗忘的计时器或回调函数: 设置了 setInterval 定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收。
- 脱离 DOM 的引用: 获取一个 DOM 元素的引用,而后面这个元素被删除,由于一直保留了对这个元素的引用,所以它也无法被回收。
- 闭包: 不合理的使用闭包,从而导致某些变量一直被留在内存当中。
深拷贝浅拷贝的区别?
浅拷贝如果是基本类型,拷贝的就是基本类型的值 ,如果拷贝的是引用数据类型那么拷贝的结果将是对原始对象的引用。换句话说,新创建的引用数据类型变量将与原始对象共享同一个内存地址,修改其中任何一个变量的属性或元素都会影响另外一个变量。
深拷贝如果是引用类型,拷贝的就是内存地址。
如何实现一个深拷贝?
常见的深拷贝方式有:
- 我的项目中使用的是 lodash 这个库里面的一个 _.cloneDeep()
- jQuery.extend() 通过jquery 这个可以实现
- JSON.stringify() --这种方式存在弊端,会忽略undefined、symbol和函数
- 拓展运算符就是浅拷贝,浅拷贝的第一层就是互不影响,第二层引用类型的才会受到影响。深拷贝是不管第一层都不受到影响,创建了一个新的内存。(很容易误认为 扩展运算也是深拷贝 分情况回答)
- 手写循环递归(不勉强 但是尽量, 实在背不了 就回答个大概原理: 通过递归的形式逐个遍历每个值然后根据每个值的类型来决定是否需要再试递归去处理)
function deepClone(obj, hash = new WeakMap()) {
if (obj === undefined) return undefined // undefined 的情况
if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
if (obj instanceof Date) return new Date(obj);//日期对象的情况
if (obj instanceof RegExp) return new RegExp(obj);//正则表达式的情况
// 可能是对象或者普通的值 如果是函数的话是不需要深拷贝
if (typeof obj !== "object") return obj;
// 是对象的话就要进行深拷贝
if (hash.get(obj)) return hash.get(obj);
let cloneObj = new obj.constructor();
// 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 实现一个递归拷贝
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
对原型和原型链的理解
原型链
实例对象在查找属性时,如果查找不到,就会沿着__proto__去与对象关联的原型上查找,如果还查找不到,就去找原型的原型,直至查到最顶层,这也就是原型链的概念。
原型
在js中,每个构造函数内部都有一个prototype属性,该属性的值是个对象,该对象包含了该构造函数所有实例共享的属性和方法。当我们通过构造函数创建对象的时候,在这个对象中有一个指针,这个指针指向构造函数的prototype的值,我们将这个指向prototype的指针称为原型
apply call bind 区别
apply、call、bind都可以为函数指定this
apply 和 call 就是传参方式不一样,apply 参数以一个数组的形式传入。但是两个都是会在调用的时候同时执行调用的函数。bind则会返回一个绑定了this的函数。
下面的有空再背
实现一个 bind
const myBind = function(context,...args){
const self = this;
args = args?args:[];
return function(...newArgs) {
return self.apply(context,[...args,...newArgs])
}
}
实现一个 apply
const myApply = function(context,args) {
// 不传this默认指向window
context = context || window;
args = args ? args : [];
// 给context新增一个独一无二的属性以免覆盖原有属性
const key = Symbol();
// this 指向调用它的对象
context[key]=this;
// 通过隐式绑定的方式调用函数
const result = context[key](...args);
// 删除添加的属性
delete context[key];
return result
}
实现一个call
const myCall = function(context,...args) {
context = context || window;
args = args ? args : [];
const key = Symbol();
context[key] = this;
const result = context[key](...args);
delete context[key];
return result;
}
说说你对HTML5语义化的理解
让页面能够结构化的展示,比如段落用p标签,头部用header标签,主要内容用main标签,侧边栏用aside标签等等 比较容易阅读,方便开发人员快速了解页面的结构,更具有可读性,利与开发和维护
说一下你对事件循环(eventloop的理解)
- 执行栈按照顺序执行同步任务,直到执行栈为空。
- 当出现异步任务时,将其放入事件队列中,继续执行栈中的其他任务。
- 当执行栈为空时,事件循环开始轮询事件队列。
- 如果事件队列中有任务,将其中的第一个任务推出并从事件队列中移除,然后执行该任务的回调函数。
- 重复步骤 1 至 4,直到事件队列为空。
事件循环机制使得 JavaScript 可以处理异步任务,避免阻塞主线程的情况发生,从而提高了程序的性能和响应能力。
跨域问题解决
浏览器有一个重要的安全策略,称之为「同源策略」其中,源=协议+主机+端口源=协议+主机+端口,两个源相同,称之为同源,两个源不同,称之为跨源或跨域
可能会被追问什么是同源策略
同源策略是指,若页面的源和页面运行过程中加载的源不一致时,出于安全考虑,浏览器会对跨域的资源访问进行一些限制
跨域解决方法1-代理
开发环境使用代理解决跨域即可主要的方式是
1.通过nginx服务器来配置代理转发。 目标域名然后设置一个 pass到真实服务器地址就好
2 vue和react当中都有对应的proxy的配置,根据需求去配置就好
跨域解决方法2-CORS
如果浏览器要跨域访问服务器的资源,需要获得服务器的允许。就是说需要服务器开启配置允许我们访问。
跨域解决方法3 - jsonp 这个办法你就别回答了!!! 太古老了 jquery中常用的方案
如果你不小心回答了 还有一个JSONP那么你就有大概率会被追问 说一下 JSONP的实现原理是什么??
当需要跨域请求时,不使用AJAX,转而生成一个script元素去请求服务器,由于浏览器并不阻止script元素的请求,这样请求可以到达服务器。服务器拿到请求后,响应一段JS代码,这段代码实际上是一个函数调用,调用的是客户端预先生成好的函数,并把浏览器需要的数据作为参数传递到函数中,从而间接的把数据传递给客户端。
下下策,实在太长记不住 你就回答说 : 这个JSONP我只在以前的老的项目中用过,当时用的是jquery去对接 里面有个$.jsonp方法,太久了不太记得了。现在项目中都是用代理
讲讲CSRF XSS攻击?如何防范?二者的区别是什么?
XSS攻击:(Cross Site Scripting)跨域脚本攻击。
原理:跨域问题解决
不需要你做任何的登录认证,它会通过合法的操作(比如在 url 中输入、在评论框中输入),
向你的页面注入脚本(可能是 js、hmtl 代码块等)。
防范:
1. 编码;对于用户输入进行编码
2. 过滤;移除用户输入和事件相关的属性。(过滤 script、style、iframe等节点)
3. 校正;使用 DOM Parse 转换,校正不配对的 DOM 标签
CSRF攻击:SRF(Cross-site request forgery)跨站请求伪造。
原理:
1. 登录受信任网站 A,并在本地生成 Cookie
(如果用户没有登录网站 A,那么网站 B 在诱导的时候,请求网站 A 的 api 接口时,会提示你登录)
2. 在不登出 A 的情况下,访问危险网站 B(其实是利用了网站 A 的漏洞)
防范:
1. token 验证;
2. 隐藏令牌;把 token 隐藏在 http 请求的 head 中。
3. referer 验证;验证页面来源。
二者的区别:
1. CSRF:需要用户先登录网站 A,获取 cookie。
XSS:不需要登录。
2. CSRF:是利用网站 A 本身的漏洞,去请求网站 A 的 api。
XSS:是向网站 A 注入 JS 代码,然后执行 JS 里的代码,篡改网站 A 的内容。
setInterval 时间为什么不准确?怎么解决
setInterval 是一个宏任务。其实是setInterval将会在 N 秒之后把一个函数推入任务队列,而不是让函数立即执行。假如定时器里面的代码需要进行大量的计算(耗费时间较长),或者是 DOM 操作。这样一来,花的时间就比较长,有可能前一次代码还没有执行完,后一次代码就被添加到队列了。也会到时定时器变得不准确,甚至出现同一时间执行两次的情况。
所以 因而我们一般用 setTimeout 模拟 setInterval 会更准备。
细节看这里:https://juejin.cn/post/6989055903651594248
说一下你了解的js设计模式 (优先背模式概念)
观察者模式
定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使它们能够自动更新自己,当一个对象的改变需要同时改变其它对象,并且它不知道具体有多少对象需要改变的时候,就应该考虑使用观察者模式
- 发布 & 订阅
- 一对多
优点
- 支持简单的广播通信,自动通知所有已经订阅过的对象
- 目标对象与观察者之间的抽象耦合关系能单独扩展以及重用
- 增加了灵活性
- 观察者模式所做的工作就是在解耦,让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响到另一边的变化。
缺点
过度使用会导致对象与对象之间的联系弱化,会导致程序难以跟踪维护和理解
单例模式
一个类只有一个实例,并提供一个访问它的全局访问点。
优点
- 划分命名空间,减少全局变量
- 增强模块性,把自己的代码组织在一个全局变量名下,放在单一位置,便于维护
- 且只会实例化一次。简化了代码的调试和维护
缺点
- 由于单例模式提供的是一种单点访问,所以它有可能导致模块间的强耦合 从而不利于单元测试。无法单独测试一个调用了来自单例的方法的类,而只能把它与那个单例作为一个单元一起测试。
场景例子
- 定义命名空间和实现分支型方法
- 登录框
- vuex 和 redux中的store
代理模式
- HTML元 素事件代理
优点
- 代理模式能将代理对象与被调用对象分离,降低了系统的耦合度。代理模式在客户端和目标对象之间起到一个中介作用,这样可以起到保护目标对象的作用
- 代理对象可以扩展目标对象的功能;通过修改代理对象就可以了,符合开闭原则;
缺点
处理请求速度可能有差别,非直接访问存在开销
实现 (5).add(3).minus(2) 功能。
Number.prototype.add = function(n) {
return this.valueOf() + n;
};
Number.prototype.minus = function(n) {
return this.valueOf() - n;
};
8种方法实现数组去重(能力有限就背后面3个)
双循环去重
双重for(或while)循环是比较笨拙的方法,它实现的原理很简单:先定义一个包含原始数组第一个元素的数组,然后遍历原始数组,将原始数组中的每个元素与新数组中的每个元素进行比对,如果不重复则添加到新数组中,最后返回新数组;因为它的时间复杂度是O(n^2),如果数组长度很大,那么将会非常耗费内存
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
let res = [arr[0]]
for (let i = 1; i < arr.length; i++) {
let flag = true
for (let j = 0; j < res.length; j++) {
if (arr[i] === res[j]) {
flag = false;
break
}
}
if (flag) {
res.push(arr[i])
}
}
return res
}
indexOf方法去重1
数组的indexOf()方法可返回某个指定的元素在数组中首次出现的位置。该方法首先定义一个空数组res,然后调用indexOf方法对原来的数组进行遍历判断,如果元素不在res中,则将其push进res中,最后将res返回即可获得去重的数组
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
let res = []
for (let i = 0; i < arr.length; i++) {
if (res.indexOf(arr[i]) === -1) {
res.push(arr[i])
}
}
return res
}
indexOf方法去重2
利用indexOf检测元素在数组中第一次出现的位置是否和元素现在的位置相等,如果不等则说明该元素是重复元素
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
return Array.prototype.filter.call(arr, function(item, index){
return arr.indexOf(item) === index;
});
}
相邻元素去重
这种方法首先调用了数组的排序方法sort(),然后根据排序后的结果进行遍历及相邻元素比对,如果相等则跳过改元素,直到遍历结束
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
arr = arr.sort()
let res = []
for (let i = 0; i < arr.length; i++) {
if (arr[i] !== arr[i-1]) {
res.push(arr[i])
}
}
return res
}
利用对象属性去重
创建空对象,遍历数组,将数组中的值设为对象的属性,并给该属性赋初始值1,每出现一次,对应的属性值增加1,这样,属性值对应的就是该元素出现的次数了
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
let res = [],
obj = {}
for (let i = 0; i < arr.length; i++) {
if (!obj[arr[i]]) {
res.push(arr[i])
obj[arr[i]] = 1
} else {
obj[arr[i]]++
}
}
return res
}
set与解构赋值去重
ES6中新增了数据类型set,set的一个最大的特点就是数据不重复。Set函数可以接受一个数组(或类数组对象)作为参数来初始化,利用该特性也能做到给数组去重
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
return [...new Set(arr)]
}
Array.from与set去重
Array.from方法可以将Set结构转换为数组结果,而我们知道set结果是不重复的数据集,因此能够达到去重的目的
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
return Array.from(new Set(arr))
}
使用 lodash 的 uniq()
_.uniq([2, 1, 2]); // => [2, 1]
如何实现断点续传/大文件上传
核心是利用 Blob.prototype.slice 方法,和数组的 slice 方法相似,文件的 slice 方法可以返回原文件的某个切片
预先定义好单个切片大小,将文件切分为一个个切片,然后借助 http 的可并发性,同时上传多个切片。这样从原本传一个大文件,变成了并发传多个小的文件切片,可以大大减少上传时间
另外由于是并发,传输到服务端的顺序可能会发生变化,因此我们还需要给每个切片记录顺序
如果追问 具体代码是如何实现的 怎么回答???
上面是我了解的一个大概的原理,我们的项目中使用的是oss,通过引入ossclient就可以对文件进行切片上传,需要配置切片的大小就可以了。
函数柯里化
柯里化(Currying) 是把接收多个参数的原函数变换成接受一个单一参数(原来函数的第一个参数的函数)并返回一个新的函数,新的函数能够接受余下的参数,并返回和原函数相同的结果。
- 参数对复用
- 提高实用性
- 延迟执行 只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。柯里化的函数可以延迟接收参数,就是比如一个函数需要接收的参数是两个,执行的时候必须接收两个参数,否则没法执行。但是柯里化后的函数,可以先接收一个参数
// 普通的add函数
function add(x, y) {
return x + y
}
// Currying后
function curryingAdd(x) {
return function (y) {
return x + y
}
}
add(1, 2) // 3
curryingAdd(1)(2) // 3
link与@import的区别
link与import , 本质使用上,我们都是用他来引入css。
区别:
- link是一种引入资源的标签,import是引入css的方式。所以,import引入的只能是css,而link可以引入所有的资源,包括图片,RSS等。
- 加载顺序上也有一些差异。link引用的CSS会同时被加载。import引用的CSS会等到页面全部被下载完再加载。
- 兼容性的差别。link无任何兼容问题,import兼容IE5以上。(当然,IE5估计也找不到了)
- 动态引入样式 link可以后期引入样式,而import是不可以后期引入的,只能初始化页面之前引入。
- 复用率的问题 import可以复用之前的css文件,而link只能一次引用一个文件。当然,import复用文件时,在浏览器实际上是加载了多个文件,会有多个请求。而每一个link只是一个http请求。
判断数组的方法以及优缺点
- Array.isArray(arr) :兼容性不好
- arr instanceof Array
- arr.proto === Array.prototype
- arr.constructor === Array
- Object.prototype.toString.call(arr) === '[object Array]'
Javascript垃圾回收
当对象不再被 引用 时被垃圾回收机制回收(“对象有没有其他对象引用到它”)。
内存泄露就是不再被需要的内存, 由于某种原因, 无法被释放
- 引用计数法:对象是否不再需要(限制:循环引入不能被回收)
- 标记清除法:从根开始,找所有从根开始引用的对象标记,然后找这些对象引用的对象标记,把不能达到的回收(这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”。)
- 通过构造函数中设置(构造函数this指向 new 的对象)
避免内存泄漏:
- 尽少创建全局变量
- 手动清除计时器
- 少用闭包
- 使用弱引用WeakMap和WeakSet
0.1 + 0.2 === 0.3 嘛?为什么?
计算机存储以二进制的方式,而0.1 在二进制中是无限循环的一些数字,所以会出现裁剪,精度丢失会出现,0.100000000000000002 === 0.1,0.200000000000000002 === 0.2 // true 这两加起来肯定不等于0.3
解决:parseFloat((0.1 + 0.2).toFixed(10)) === 0.3 // true