来自大厂 10+ 前端面试题附答案(整理版)
跨域方案
很多种方法,但万变不离其宗,都是为了搞定同源策略。重用的有
jsonp
、iframe
、cors
、img
、HTML5 postMessage
等等。其中用到html
标签进行跨域的原理就是html
不受同源策略影响。但只是接受Get
的请求方式,这个得清楚。
延伸1:img iframe script 来发送跨域请求有什么优缺点?
1. iframe
- 优点:跨域完毕之后
DOM
操作和互相之间的JavaScript
调用都是没有问题的 - 缺点:1.若结果要以
URL
参数传递,这就意味着在结果数据量很大的时候需要分割传递,巨烦。2.还有一个是iframe
本身带来的,母页面和iframe
本身的交互本身就有安全性限制。
2. script
- 优点:可以直接返回
json
格式的数据,方便处理 - 缺点:只接受
GET
请求方式
3. 图片ping
- 优点:可以访问任何
url
,一般用来进行点击追踪,做页面分析常用的方法 - 缺点:不能访问响应文本,只能监听是否响应
延伸2:配合 webpack 进行反向代理?
webpack
在 devServer
选项里面提供了一个 proxy
的参数供开发人员进行反向代理
'/api': {target: 'http://www.example.com', // your target hostchangeOrigin: true, // needed for virtual hosted sitespathRewrite: {'^/api': '' // rewrite path}
},
然后再配合
http-proxy-middleware
插件对api
请求地址进行代理
const express = require('express');
const proxy = require('http-proxy-middleware');
// proxy api requests
const exampleProxy = proxy(options); // 这里的 options 就是 webpack 里面的 proxy 选项对应的每个选项// mount `exampleProxy` in web server
const app = express();
app.use('/api', exampleProxy);
app.listen(3000);
然后再用
nginx
把允许跨域的源地址添加到报头里面即可
说到
nginx
,可以再谈谈CORS
配置,大致如下
location / {if ($request_method = 'OPTIONS') {add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Allow-Credentials' 'true';add_header 'Access-Control-Allow-Headers' 'DNT, X-Mx-ReqToken, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type'; add_header 'Access-Control-Max-Age' 86400; add_header 'Content-Type' 'text/plain charset=UTF-8'; add_header 'Content-Length' 0; return 200; }
}
什么是物理像素,逻辑像素和像素密度,为什么在移动端开发时需要用到@3x, @2x这种图片?
以 iPhone XS 为例,当写 CSS 代码时,针对于单位 px,其宽度为 414px & 896px,也就是说当赋予一个 DIV元素宽度为 414px,这个 DIV 就会填满手机的宽度;
而如果有一把尺子来实际测量这部手机的物理像素,实际为 1242*2688 物理像素;经过计算可知,1242/414=3,也就是说,在单边上,一个逻辑像素=3个物理像素,就说这个屏幕的像素密度为 3,也就是常说的 3 倍屏。
对于图片来说,为了保证其不失真,1 个图片像素至少要对应一个物理像素,假如原始图片是 500300 像素,那么在 3 倍屏上就要放一个 1500900 像素的图片才能保证 1 个物理像素至少对应一个图片像素,才能不失真。 当然,也可以针对所有屏幕,都只提供最高清图片。虽然低密度屏幕用不到那么多图片像素,而且会因为下载多余的像素造成带宽浪费和下载延迟,但从结果上说能保证图片在所有屏幕上都不会失真。
还可以使用 CSS 媒体查询来判断不同的像素密度,从而选择不同的图片:
my-image { background: (low.png); }
@media only screen and (min-device-pixel-ratio: 1.5) {#my-image { background: (high.png); }
}
使用 clear 属性清除浮动的原理?
使用clear属性清除浮动,其语法如下:
clear:none|left|right|both
如果单看字面意思,clear:left 是“清除左浮动”,clear:right 是“清除右浮动”,实际上,这种解释是有问题的,因为浮动一直还在,并没有清除。
官方对clear属性解释:“元素盒子的边不能和前面的浮动元素相邻”,对元素设置clear属性是为了避免浮动元素对该元素的影响,而不是清除掉浮动。
还需要注意 clear 属性指的是元素盒子的边不能和前面的浮动元素相邻,注意这里“前面的”3个字,也就是clear属性对“后面的”浮动元素是不闻不问的。考虑到float属性要么是left,要么是right,不可能同时存在,同时由于clear属性对“后面的”浮动元素不闻不问,因此,当clear:left有效的时候,clear:right必定无效,也就是此时clear:left等同于设置clear:both;同样地,clear:right如果有效也是等同于设置clear:both。由此可见,clear:left和clear:right这两个声明就没有任何使用的价值,至少在CSS世界中是如此,直接使用clear:both吧。
一般使用伪元素的方式清除浮动:
.clear::after{ content:''; display: block; clear:both;}
clear属性只有块级元素才有效的,而::after等伪元素默认都是内联水平,这就是借助伪元素清除浮动影响时需要设置display属性值的原因。
+
操作符什么时候用于字符串的拼接?
根据 ES5 规范,如果某个操作数是字符串或者能够通过以下步骤转换为字符串的话,+ 将进行拼接操作。如果其中一个操作数是对象(包括数组),则首先对其调用 ToPrimitive 抽象操作,该抽象操作再调用 [[DefaultValue]],以数字作为上下文。如果不能转换为字符串,则会将其转换为数字类型来进行计算。
简单来说就是,如果 + 的其中一个操作数是字符串(或者通过以上步骤最终得到字符串),则执行字符串拼接,否则执行数字加法。
那么对于除了加法的运算符来说,只要其中一方是数字,那么另一方就会被转为数字。
数组有哪些原生方法?
- 数组和字符串的转换方法:toString()、toLocalString()、join() 其中 join() 方法可以指定转换为字符串时的分隔符。
- 数组尾部操作的方法 pop() 和 push(),push 方法可以传入多个参数。
- 数组首部操作的方法 shift() 和 unshift() 重排序的方法 reverse() 和 sort(),sort() 方法可以传入一个函数来进行比较,传入前后两个值,如果返回值为正数,则交换两个参数的位置。
- 数组连接的方法 concat() ,返回的是拼接好的数组,不影响原数组。
- 数组截取办法 slice(),用于截取数组中的一部分返回,不影响原数组。
- 数组插入方法 splice(),影响原数组查找特定项的索引的方法,indexOf() 和 lastIndexOf() 迭代方法 every()、some()、filter()、map() 和 forEach() 方法
- 数组归并方法 reduce() 和 reduceRight() 方法
JavaScript有哪些内置对象
全局的对象( global objects )或称标准内置对象,不要和 “全局对象(global object)” 混淆。这里说的全局的对象是说在
全局作用域里的对象。全局作用域中的其他对象可以由用户的脚本创建或由宿主程序提供。
标准内置对象的分类:
(1)值属性,这些全局属性返回一个简单值,这些值没有自己的属性和方法。例如 Infinity、NaN、undefined、null 字面量
(2)函数属性,全局函数可以直接调用,不需要在调用时指定所属对象,执行结束后会将结果直接返回给调用者。例如 eval()、parseFloat()、parseInt() 等
(3)基本对象,基本对象是定义或使用其他对象的基础。基本对象包括一般对象、函数对象和错误对象。例如 Object、Function、Boolean、Symbol、Error 等
(4)数字和日期对象,用来表示数字、日期和执行数学计算的对象。例如 Number、Math、Date
(5)字符串,用来表示和操作字符串的对象。例如 String、RegExp
(6)可索引的集合对象,这些对象表示按照索引值来排序的数据集合,包括数组和类型数组,以及类数组结构的对象。例如 Array
(7)使用键的集合对象,这些集合对象在存储数据时会使用到键,支持按照插入顺序来迭代元素。
例如 Map、Set、WeakMap、WeakSet
(8)矢量集合,SIMD 矢量集合中的数据会被组织为一个数据序列。
例如 SIMD 等
(9)结构化数据,这些对象用来表示和操作结构化的缓冲区数据,或使用 JSON 编码的数据。例如 JSON 等
(10)控制抽象对象
例如 Promise、Generator 等
(11)反射。例如 Reflect、Proxy
(12)国际化,为了支持多语言处理而加入 ECMAScript 的对象。例如 Intl、Intl.Collator 等
(13)WebAssembly
(14)其他。例如 arguments
总结: js 中的内置对象主要指的是在程序执行前存在全局作用域里的由 js 定义的一些全局值属性、函数和用来实例化其他对象的构造函数对象。一般经常用到的如全局变量值 NaN、undefined,全局函数如 parseInt()、parseFloat() 用来实例化对象的构造函数如 Date、Object 等,还有提供数学计算的单体内置对象如 Math 对象。
ajax、axios、fetch的区别
(1)AJAX Ajax 即“AsynchronousJavascriptAndXML”(异步 JavaScript 和 XML),是指一种创建交互式网页应用的网页开发技术。它是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。传统的网页(不使用 Ajax)如果需要更新内容,必须重载整个网页页面。其缺点如下:
- 本身是针对MVC编程,不符合前端MVVM的浪潮
- 基于原生XHR开发,XHR本身的架构不清晰
- 不符合关注分离(Separation of Concerns)的原则
- 配置和调用方式非常混乱,而且基于事件的异步模型不友好。
(2)Fetch fetch号称是AJAX的替代品,是在ES6出现的,使用了ES6中的promise对象。Fetch是基于promise设计的。Fetch的代码结构比起ajax简单多。fetch不是ajax的进一步封装,而是原生js,没有使用XMLHttpRequest对象。
fetch的优点:
- 语法简洁,更加语义化
- 基于标准 Promise 实现,支持 async/await
- 更加底层,提供的API丰富(request, response)
- 脱离了XHR,是ES规范里新的实现方式
fetch的缺点:
- fetch只对网络请求报错,对400,500都当做成功的请求,服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。
- fetch默认不会带cookie,需要添加配置项: fetch(url, {credentials: ‘include’})
- fetch不支持abort,不支持超时控制,使用setTimeout及Promise.reject的实现的超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费
- fetch没有办法原生监测请求的进度,而XHR可以
(3)Axios Axios 是一种基于Promise封装的HTTP客户端,其特点如下:
- 浏览器端发起XMLHttpRequests请求
- node端发起http请求
- 支持Promise API
- 监听请求和返回
- 对请求和返回进行转化
- 取消请求
- 自动转换json数据
- 客户端支持抵御XSRF攻击
HTTP 1.1 和 HTTP 2.0 的区别
- 二进制协议:HTTP/2 是一个二进制协议。在 HTTP/1.1 版中,报文的头信息必须是文本(ASCII 编码),数据体可以是文本,也可以是二进制。HTTP/2 则是一个彻底的二进制协议,头信息和数据体都是二进制,并且统称为"帧",可以分为头信息帧和数据帧。 帧的概念是它实现多路复用的基础。
- 多路复用: HTTP/2 实现了多路复用,HTTP/2 仍然复用 TCP 连接,但是在一个连接里,客户端和服务器都可以同时发送多个请求或回应,而且不用按照顺序一一发送,这样就避免了"队头堵塞"【1】的问题。
- 数据流: HTTP/2 使用了数据流的概念,因为 HTTP/2 的数据包是不按顺序发送的,同一个连接里面连续的数据包,可能属于不同的请求。因此,必须要对数据包做标记,指出它属于哪个请求。HTTP/2 将每个请求或回应的所有数据包,称为一个数据流。每个数据流都有一个独一无二的编号。数据包发送时,都必须标记数据流 ID ,用来区分它属于哪个数据流。
- 头信息压缩: HTTP/2 实现了头信息压缩,由于 HTTP 1.1 协议不带状态,每次请求都必须附上所有信息。所以,请求的很多字段都是重复的,比如 Cookie 和 User Agent ,一模一样的内容,每次请求都必须附带,这会浪费很多带宽,也影响速度。HTTP/2 对这一点做了优化,引入了头信息压缩机制。一方面,头信息使用 gzip 或 compress 压缩后再发送;另一方面,客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就能提高速度了。
- 服务器推送: HTTP/2 允许服务器未经请求,主动向客户端发送资源,这叫做服务器推送。使用服务器推送提前给客户端推送必要的资源,这样就可以相对减少一些延迟时间。这里需要注意的是 http2 下服务器主动推送的是静态资源,和 WebSocket 以及使用 SSE 等方式向客户端发送即时数据的推送是不同的。
【1】队头堵塞:
队头阻塞是由 HTTP 基本的“请求 - 应答”模型所导致的。HTTP 规定报文必须是“一发一收”,这就形成了一个先进先出的“串行”队列。队列里的请求是没有优先级的,只有入队的先后顺序,排在最前面的请求会被最优先处理。如果队首的请求因为处理的太慢耽误了时间,那么队列里后面的所有请求也不得不跟着一起等待,结果就是其他的请求承担了不应有的时间成本,造成了队头堵塞的现象。
intanceof 操作符的实现原理及实现
instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。
function myInstanceof(left, right) {// 获取对象的原型let proto = Object.getPrototypeOf(left)// 获取构造函数的 prototype 对象let prototype = right.prototype; // 判断构造函数的 prototype 对象是否在对象的原型链上while (true) {if (!proto) return false;if (proto === prototype) return true;// 如果没有找到,就继续从其原型上找,Object.getPrototypeOf方法用来获取指定对象的原型proto = Object.getPrototypeOf(proto);}
}
对AJAX的理解,实现一个AJAX请求
AJAX是 Asynchronous JavaScript and XML 的缩写,指的是通过 JavaScript 的 异步通信,从服务器获取 XML 文档从中提取数据,再更新当前网页的对应部分,而不用刷新整个网页。
创建AJAX请求的步骤:
- 创建一个 XMLHttpRequest 对象。
- 在这个对象上使用 open 方法创建一个 HTTP 请求,open 方法所需要的参数是请求的方法、请求的地址、是否异步和用户的认证信息。
- 在发起请求前,可以为这个对象添加一些信息和监听函数。比如说可以通过 setRequestHeader 方法来为请求添加头信息。还可以为这个对象添加一个状态监听函数。一个 XMLHttpRequest 对象一共有 5 个状态,当它的状态变化时会触发onreadystatechange 事件,可以通过设置监听函数,来处理请求成功后的结果。当对象的 readyState 变为 4 的时候,代表服务器返回的数据接收完成,这个时候可以通过判断请求的状态,如果状态是 2xx 或者 304 的话则代表返回正常。这个时候就可以通过 response 中的数据来对页面进行更新了。
- 当对象的属性和监听函数设置完成后,最后调用 sent 方法来向服务器发起请求,可以传入参数作为发送的数据体。
const SERVER_URL = "/server";
let xhr = new XMLHttpRequest();
// 创建 Http 请求
xhr.open("GET", url, true);
// 设置状态监听函数
xhr.onreadystatechange = function() {if (this.readyState !== 4) return;// 当请求成功时if (this.status === 200) {handle(this.response);} else {console.error(this.statusText);}
};
// 设置请求失败时的监听函数
xhr.onerror = function() {console.error(this.statusText);
};
// 设置请求头信息
xhr.responseType = "json";
xhr.setRequestHeader("Accept", "application/json");
// 发送 Http 请求
xhr.send(null);
使用Promise封装AJAX:
// promise 封装实现:
function getJSON(url) {// 创建一个 promise 对象let promise = new Promise(function(resolve, reject) {let xhr = new XMLHttpRequest();// 新建一个 http 请求xhr.open("GET", url, true);// 设置状态的监听函数xhr.onreadystatechange = function() {if (this.readyState !== 4) return;// 当请求成功或失败时,改变 promise 的状态if (this.status === 200) {resolve(this.response);} else {reject(new Error(this.statusText));}};// 设置错误监听函数xhr.onerror = function() {reject(new Error(this.statusText));};// 设置响应的数据类型xhr.responseType = "json";// 设置请求头信息xhr.setRequestHeader("Accept", "application/json");// 发送 http 请求xhr.send(null);});return promise;
}
use strict是什么意思 ? 使用它区别是什么?
use strict 是一种 ECMAscript5 添加的(严格模式)运行模式,这种模式使得 Javascript 在更严格的条件下运行。设立严格模式的目的如下:
- 消除 Javascript 语法的不合理、不严谨之处,减少怪异行为;
- 消除代码运行的不安全之处,保证代码运行的安全;
- 提高编译器效率,增加运行速度;
- 为未来新版本的 Javascript 做好铺垫。
区别:
- 禁止使用 with 语句。
- 禁止 this 关键字指向全局对象。
- 对象不能有重名的属性。
async/await的优势
单一的 Promise 链并不能发现 async/await 的优势,但是,如果需要处理由多个 Promise 组成的 then 链的时候,优势就能体现出来了(很有意思,Promise 通过 then 链来解决多层回调的问题,现在又用 async/await 来进一步优化它)。
假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。仍然用 setTimeout
来模拟异步操作:
/** * 传入参数 n,表示这个函数执行的时间(毫秒) * 执行的结果是 n + 200,这个值将用于下一步骤 */
function takeLongTime(n) {return new Promise(resolve => {setTimeout(() => resolve(n + 200), n);});
}
function step1(n) {console.log(`step1 with${n}`);return takeLongTime(n);
}
function step2(n) {console.log(`step2 with${n}`);return takeLongTime(n);
}
function step3(n) {console.log(`step3 with${n}`);return takeLongTime(n);
}
现在用 Promise 方式来实现这三个步骤的处理:
function doIt() {console.time("doIt");const time1 = 300;step1(time1).then(time2 => step2(time2)).then(time3 => step3(time3)).then(result => {console.log(`result is${result}`);console.timeEnd("doIt");});
}
doIt();
// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1507.251ms
输出结果 result
是 step3()
的参数 700 + 200
= 900
。doIt()
顺序执行了三个步骤,一共用了 300 + 500 + 700 = 1500
毫秒,和 console.time()/console.timeEnd()
计算的结果一致。
如果用 async/await 来实现呢,会是这样:
async function doIt() {console.time("doIt");const time1 = 300;const time2 = await step1(time1);const time3 = await step2(time2);const result = await step3(time3);console.log(`result is${result}`);console.timeEnd("doIt");
}
doIt();
结果和之前的 Promise 实现是一样的,但是这个代码看起来是不是清晰得多,几乎跟同步代码一样
Promise的基本用法
(1)创建Promise对象
Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve
和reject
。
const promise = new Promise(function(resolve, reject) {// ... some codeif (/* 异步操作成功 */){resolve(value);} else {reject(error);}
});
一般情况下都会使用new Promise()
来创建promise对象,但是也可以使用promise.resolve
和promise.reject
这两个方法:
- Promise.resolve
Promise.resolve(value)
的返回值也是一个promise对象,可以对返回值进行.then调用,代码如下:
Promise.resolve(11).then(function(value){console.log(value); // 打印出11
});
resolve(11)
代码中,会让promise对象进入确定(resolve
状态),并将参数11
传递给后面的then
所指定的onFulfilled
函数;
创建promise对象可以使用new Promise
的形式创建对象,也可以使用Promise.resolve(value)
的形式创建promise对象;
- Promise.reject
Promise.reject
也是new Promise
的快捷形式,也创建一个promise对象。代码如下:
Promise.reject(new Error(“我错了,请原谅俺!!”));
就是下面的代码new Promise的简单形式:
new Promise(function(resolve,reject){reject(new Error("我错了!"));
});
下面是使用resolve方法和reject方法:
function testPromise(ready) {return new Promise(function(resolve,reject){if(ready) {resolve("hello world");}else {reject("No thanks");}});
};
// 方法调用
testPromise(true).then(function(msg){console.log(msg);
},function(error){console.log(error);
});
上面的代码的含义是给testPromise
方法传递一个参数,返回一个promise对象,如果为true
的话,那么调用promise对象中的resolve()
方法,并且把其中的参数传递给后面的then
第一个函数内,因此打印出 “hello world
”, 如果为false
的话,会调用promise对象中的reject()
方法,则会进入then
的第二个函数内,会打印No thanks
;
(2)Promise方法
Promise有五个常用的方法:then()、catch()、all()、race()、finally。下面就来看一下这些方法。
- then()
当Promise执行的内容符合成功条件时,调用resolve
函数,失败就调用reject
函数。Promise创建完了,那该如何调用呢?
promise.then(function(value) {// success
}, function(error) {// failure
});
then
方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved
时调用,第二个回调函数是Promise对象的状态变为rejected
时调用。其中第二个参数可以省略。 then
方法返回的是一个新的Promise实例(不是原来那个Promise实例)。因此可以采用链式写法,即then
方法后面再调用另一个then方法。
当要写有顺序的异步事件时,需要串行时,可以这样写:
let promise = new Promise((resolve,reject)=>{ajax('first').success(function(res){resolve(res);})
})
promise.then(res=>{return new Promise((resovle,reject)=>{ajax('second').success(function(res){resolve(res)})})
}).then(res=>{return new Promise((resovle,reject)=>{ajax('second').success(function(res){resolve(res)})})
}).then(res=>{})
那当要写的事件没有顺序或者关系时,还如何写呢?可以使用all
方法来解决。
2. catch()
Promise对象除了有then方法,还有一个catch方法,该方法相当于then
方法的第二个参数,指向reject
的回调函数。不过catch
方法还有一个作用,就是在执行resolve
回调函数时,如果出现错误,抛出异常,不会停止运行,而是进入catch
方法中。
p.then((data) => {console.log('resolved',data);
},(err) => {console.log('rejected',err);}
);
p.then((data) => {console.log('resolved',data);
}).catch((err) => {console.log('rejected',err);
});
3. all()
all
方法可以完成并行任务, 它接收一个数组,数组的每一项都是一个promise
对象。当数组中所有的promise
的状态都达到resolved
的时候,all
方法的状态就会变成resolved
,如果有一个状态变成了rejected
,那么all
方法的状态就会变成rejected
。
javascript
let promise1 = new Promise((resolve,reject)=>{setTimeout(()=>{resolve(1);},2000)
});
let promise2 = new Promise((resolve,reject)=>{setTimeout(()=>{resolve(2);},1000)
});
let promise3 = new Promise((resolve,reject)=>{setTimeout(()=>{resolve(3);},3000)
});
Promise.all([promise1,promise2,promise3]).then(res=>{console.log(res);//结果为:[1,2,3]
})
调用all
方法时的结果成功的时候是回调函数的参数也是一个数组,这个数组按顺序保存着每一个promise对象resolve
执行时的值。
(4)race()
race
方法和all
一样,接受的参数是一个每项都是promise
的数组,但是与all
不同的是,当最先执行完的事件执行完之后,就直接返回该promise
对象的值。如果第一个promise
对象状态变成resolved
,那自身的状态变成了resolved
;反之第一个promise
变成rejected
,那自身状态就会变成rejected
。
let promise1 = new Promise((resolve,reject)=>{setTimeout(()=>{reject(1);},2000)
});
let promise2 = new Promise((resolve,reject)=>{setTimeout(()=>{resolve(2);},1000)
});
let promise3 = new Promise((resolve,reject)=>{setTimeout(()=>{resolve(3);},3000)
});
Promise.race([promise1,promise2,promise3]).then(res=>{console.log(res);//结果:2
},rej=>{console.log(rej)};
)
那么race
方法有什么实际作用呢?当要做一件事,超过多长时间就不做了,可以用这个方法来解决:
Promise.race([promise1,timeOutPromise(5000)]).then(res=>{})
5. finally()
finally
方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
上面代码中,不管promise
最后的状态,在执行完then
或catch
指定的回调函数以后,都会执行finally
方法指定的回调函数。
下面是一个例子,服务器使用 Promise 处理请求,然后使用finally
方法关掉服务器。
server.listen(port).then(function () {// ...}).finally(server.stop);
finally
方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled
还是rejected
。这表明,finally
方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。finally
本质上是then
方法的特例:
promise
.finally(() => {// 语句
});
// 等同于
promise
.then(result => {// 语句return result;},error => {// 语句throw error;}
);
上面代码中,如果不使用finally
方法,同样的语句需要为成功和失败两种情况各写一次。有了finally
方法,则只需要写一次。
手写题:数组扁平化
function flatten(arr) {let result = [];for (let i = 0; i < arr.length; i++) {if (Array.isArray(arr[i])) {result = result.concat(flatten(arr[i]));} else {result = result.concat(arr[i]);}}return result;
}const a = [1, [2, [3, 4]]];
console.log(flatten(a));
代码输出结果
setTimeout(function () {console.log(1);
}, 100);new Promise(function (resolve) {console.log(2);resolve();console.log(3);
}).then(function () {console.log(4);new Promise((resove, reject) => {console.log(5);setTimeout(() => {console.log(6);}, 10);})
});
console.log(7);
console.log(8);
输出结果为:
2
3
7
8
4
5
6
1
代码执行过程如下:
- 首先遇到定时器,将其加入到宏任务队列;
- 遇到Promise,首先执行里面的同步代码,打印出2,遇到resolve,将其加入到微任务队列,执行后面同步代码,打印出3;
- 继续执行script中的代码,打印出7和8,至此第一轮代码执行完成;
- 执行微任务队列中的代码,首先打印出4,如遇到Promise,执行其中的同步代码,打印出5,遇到定时器,将其加入到宏任务队列中,此时宏任务队列中有两个定时器;
- 执行宏任务队列中的代码,这里我们需要注意是的第一个定时器的时间为100ms,第二个定时器的时间为10ms,所以先执行第二个定时器,打印出6;
- 此时微任务队列为空,继续执行宏任务队列,打印出1。
做完这道题目,我们就需要格外注意,每个定时器的时间,并不是所有定时器的时间都为0哦。
Number() 的存储空间是多大?如果后台发送了一个超过最大自己的数字怎么办
Math.pow(2, 53) ,53 为有效数字,会发生截断,等于 JS 能支持的最大数字。
display的block、inline和inline-block的区别
(1)block: 会独占一行,多个元素会另起一行,可以设置width、height、margin和padding属性;
(2)inline: 元素不会独占一行,设置width、height属性无效。但可以设置水平方向的margin和padding属性,不能设置垂直方向的padding和margin;
(3)inline-block: 将对象设置为inline对象,但对象的内容作为block对象呈现,之后的内联对象会被排列在同一行内。
对于行内元素和块级元素,其特点如下:
(1)行内元素
- 设置宽高无效;
- 可以设置水平方向的margin和padding属性,不能设置垂直方向的padding和margin;
- 不会自动换行;
(2)块级元素
- 可以设置宽高;
- 设置margin和padding都有效;
- 可以自动换行;
- 多个块状,默认排列从上到下。
浏览器的垃圾回收机制
(1)垃圾回收的概念
垃圾回收:JavaScript代码运行时,需要分配内存空间来储存变量和值。当变量不在参与运行时,就需要系统收回被占用的内存空间,这就是垃圾回收。
回收机制:
- Javascript 具有自动垃圾回收机制,会定期对那些不再使用的变量、对象所占用的内存进行释放,原理就是找到不再使用的变量,然后释放掉其占用的内存。
- JavaScript中存在两种变量:局部变量和全局变量。全局变量的生命周期会持续要页面卸载;而局部变量声明在函数中,它的生命周期从函数执行开始,直到函数执行结束,在这个过程中,局部变量会在堆或栈中存储它们的值,当函数执行结束后,这些局部变量不再被使用,它们所占有的空间就会被释放。
- 不过,当局部变量被外部函数使用时,其中一种情况就是闭包,在函数执行结束后,函数外部的变量依然指向函数内部的局部变量,此时局部变量依然在被使用,所以不会回收。
(2)垃圾回收的方式
浏览器通常使用的垃圾回收方法有两种:标记清除,引用计数。 1)标记清除
- 标记清除是浏览器常见的垃圾回收方式,当变量进入执行环境时,就标记这个变量“进入环境”,被标记为“进入环境”的变量是不能被回收的,因为他们正在被使用。当变量离开环境时,就会被标记为“离开环境”,被标记为“离开环境”的变量会被内存释放。
- 垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。然后,它会去掉环境中的变量以及被环境中的变量引用的标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后。垃圾收集器完成内存清除工作,销毁那些带标记的值,并回收他们所占用的内存空间。
2)引用计数
- 另外一种垃圾回收机制就是引用计数,这个用的相对较少。引用计数就是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数就减1。当这个引用次数变为0时,说明这个变量已经没有价值,因此,在在机回收期下次再运行时,这个变量所占有的内存空间就会被释放出来。
- 这种方法会引起循环引用的问题:例如:
obj1
和obj2
通过属性进行相互引用,两个对象的引用次数都是2。当使用循环计数时,由于函数执行完后,两个对象都离开作用域,函数执行结束,obj1
和obj2
还将会继续存在,因此它们的引用次数永远不会是0,就会引起循环引用。
function fun() {let obj1 = {};let obj2 = {};obj1.a = obj2; // obj1 引用 obj2obj2.a = obj1; // obj2 引用 obj1
}
这种情况下,就要手动释放变量占用的内存:
obj1.a = nullobj2.a = null
(3)减少垃圾回收
虽然浏览器可以进行垃圾自动回收,但是当代码比较复杂时,垃圾回收所带来的代价比较大,所以应该尽量减少垃圾回收。
- 对数组进行优化: 在清空一个数组时,最简单的方法就是给其赋值为[ ],但是与此同时会创建一个新的空对象,可以将数组的长度设置为0,以此来达到清空数组的目的。
- 对
object
进行优化: 对象尽量复用,对于不再使用的对象,就将其设置为null,尽快被回收。 - 对函数进行优化: 在循环中的函数表达式,如果可以复用,尽量放在函数的外面。
说一说你用过的css布局
gird布局,layout布局,flex布局,双飞翼,圣杯布局等
动态规划求解硬币找零问题
题目描述:给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1
示例1:
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1示例2:
输入: coins = [2], amount = 3
输出: -1
实现代码如下:
const coinChange = function (coins, amount) {// 用于保存每个目标总额对应的最小硬币个数const f = [];// 提前定义已知情况f[0] = 0;// 遍历 [1, amount] 这个区间的硬币总额for (let i = 1; i <= amount; i++) {// 求的是最小值,因此我们预设为无穷大,确保它一定会被更小的数更新f[i] = Infinity;// 循环遍历每个可用硬币的面额for (let j = 0; j < coins.length; j++) {// 若硬币面额小于目标总额,则问题成立if (i - coins[j] >= 0) {// 状态转移方程f[i] = Math.min(f[i], f[i - coins[j]] + 1);}}}// 若目标总额对应的解为无穷大,则意味着没有一个符合条件的硬币总数来更新它,本题无解,返回-1if (f[amount] === Infinity) {return -1;}// 若有解,直接返回解的内容return f[amount];
};
来自大厂 10+ 前端面试题附答案(整理版)相关推荐
- 前端面试题及答案整理(2022最新版)
收集整理2022年最新前端面试题及答案,方便平时翻看记忆,欢迎各位大佬们补充. 一般来说,把下面基础中的高频题写熟练就差不多了.当然去面大厂这些远远不够,还要再刷一些算法题. 基础 高频 1.手写 i ...
- web前端面试题(附答案)
web前端面试题 1.src和href的区别 ? href: Hypertext Reference的缩写,超文本引用,多数用于a link 标签 看下图 加载他们的时候 不会停止对当前页面的加载 浏 ...
- web前端面试题附答案044 - vue获取param参数,有什么缺点吗?
看这个vue的图标像不像小时候看的<魔神坛斗士>身上的某个元素.真希望成年人的世界就像小时候一样简单快乐. 今天这道面试题主要围绕vue中获取param参数展开,看完本文你可以吸取到以下知 ...
- 2022前端面试题附答案.
1.HTML5的新特性? 1.语义化标签,比如header.footer.nav.aside.article.section 2.音视频,比如audio.video 3.画布canvas.矢量图svg ...
- web前端面试题附答案035-关于number考点大全,走过路过不会错过
1.如何创建一个number数值型的变量 <script>// 创建一个10进制number数值var num = 5;var num2 = 5.55;// 创建一个16进制的数值var ...
- 前端面试题及答案整理(一)
各公司秋招很快就开始了,最近在准备面试的东西,干脆将发现的各类面试题整理一下共享出来,大部分面试题是没有标准答案的,我给出的答案也是仅供参考,如果有更好的解答欢迎在评论区留言. Part1 手写代码 ...
- 2023前端面试题及答案整理(JavaScript)
JS类型 string,number,boolean,undefined,null,symbol(es6),BigInt(es10),object 值类型和引用类型的区别 两种类型的区别是:存储位置不 ...
- 2023前端面试题及答案整理(JS面试题)
ES6 let / const 全局作用域中,用 const 和 let 声明的变量不在 window 上,那到底在哪里?如何去获取? ES6规定,var 命令和 function 命令声明的全局变量 ...
- 2023前端面试题及答案整理(Vue)
watch 和 computed 区别 watch 是监听动作,computed 是计算属性 watch 没缓存,只要数据变化就执行.computed 有缓存,只在属性变化的时候才去计算. watch ...
最新文章
- 路遥知马力——Momentum动量梯度
- cookie JS验证码
- [译]你真的理解grok吗
- 英语写作中常见语法总结(二)
- Java Web乱码分析及解决方案
- java实现时钟方法汇总
- python爬虫的技能_关于 Python 爬虫可能涉及到的技能点
- gitlab+jenkins+sonar做代码质量分析
- mysql大数据量使用limit分页,随着页码的增大,查询效率越低下。
- 【白皮书分享】2021年智慧城市白皮书:依托智慧服务,共创新型智慧城市.pdf(附下载链接)
- 一般来讲,大公司都有自己的决策团队
- Django 现可在 Windows Azure 上使用
- kafka中处理超大消息的一些考虑
- Leetcode 817.链表组件
- vue + elementUI 框架下给el-table 增加一条数据,默认滚动条定位到表格的最底部
- Django中Python3安装Crypto使用RSA
- php js attr,php如何像js一样解析DOM?
- SLAM十四讲中的第五讲点云拼接
- 收藏!这10部关于数学的顶级纪录片,告诉孩子数学跟枯燥不沾边!
- DMPQ2Iracac红光铱(Ir)配合物|Ir(dfbpy)2(bpy)+PF6-|Ir(dfbpy)2(pyq)+PF6-|Ir(dfbpy)2(quqo)+PF6-科研试剂