前端面试题-JavaScript

137 阅读38分钟

Javascript ****面试问题整理

 

1、原型/原型链/构造函数/实例/继承

构造函数:用  new  操作后面的函数,即便是空函数,结果生成一个实例

原型:声明一个函数时,就自动给该函数增加一个  propotype  属性,指向原型

对象(初始化一个空对象),为了实现函数复用

原型链:实例上的  _proto 属性指向构造函数的原型对象,最顶端是

Object.prototype,然后再往上就是  null,Object.prototype.proto = null

函数也有  proto  属性,指向  Function.prototype,函数是构造函数

Function  的实例

 

构造器:原型对象的  constructor  属性,默认指向声明的函数

 

2、如何实现 ****new ****运算符

 

let new2 = function(func) {

 

//创建一个空对象  o,并且继承构造函数的原型对象

let o =Object.create(func.prototype);

 

//执行构造函数,并且上下文  this  指向  o  对象

let k = func.call(o);

//如果构造函数返回的是对象就返回该对象,否则返回  o  对象

if(typeof k=== 'object') {

 

return k

 

}else {

 

return o

 

}}

 

3、有几种方式可以实现继承

 

//借助构造函数实现继承:缺点是父构造函数的原型链继承不了,若要全部继承

除非将所有属性和方法定义在构造函数中 function Parent1 () {

 

this.name = 'parent1';}function Child1 () {

 

//这么做的好处是定义在父构造函数中的引用类型的属性,对于子构造函数的

每个实例来说是独立的 //并且在子构造函数实例化时,可以给父构造函数传参

Parent.call(this);

 

this.type = 'child1';}

 

//借助原型链实现继承:缺点是继承的引用类型属性是共享的,子构造函数的实

例更改会影响其他实例上的这个属性,比如  play  属性 function Parent2 () {

 

this.name = 'parent2';

 

this.play = [1, 2, 3];}function Child2 () {

 

this.type = 'Child2';}Child2.prototype = new Parent2();

 

//组合方式:缺点是会执行两次父构造函数 function Child3 () {

 

//执行第一次 Parent2.call(this);

 

this.type = 'Child3';}Child3.prototype = new Parent2(); //执行第二次//组

合优化 1,不需要再将定义在父构造函数中的属性和方法再继承一次,只需要继

承原型链上的 Child3.prototype = Parent2.prototype;//缺点是无法区分一个

实例是子函构造函数实例化的还是父构造函数实例化的 let s1 = new

Child3();//s1.constructor  指向了  Parent2,而不是  Child3,因为  Child3  原

型对象的属性  constructor  继承了  Parent2  原型对象上的//如果你强行执行

Child3.prototype.constructor = Child3  的话,也会将

Parent2.prototype.constructor  改成  Child3//组合优化 2,通过

Object.create()  创建一个中间对象,将两个原型对象区别开来,并且继承父构

造函数的原型链 Child3.prototype =

Object.create(Parent2.prototype);Child3.prototype.constructor =

Child3;//即  Child3.prototype.proto === Parent2.prototype  为  true//

如此  Child3.prototype  和  Parent2.prototype  被隔离开,是两个对象,不会

相互影响//这种方式为理想继承方式

 

4、arguments

 

可变参/不定参,为一个类数组,有  length  属性,可以通过循环找到每个参数,

可以用  arguments[index]  来找到具体的参数,但是不是真正的数组,如果要

转换成真正的数组(意味着可以使用数组方法),可以这么做:

 

//以  forEach  为例:

 

Array.prototype.forEach.call(arguments, (item) => {console.log(item);});

 

[].forEach.call(arguments, () => {});

 

Array.from(arguments).forEach(() => {}); //直接将  arguments  转换成了数

 

当我们给参数命名,是为了增加可读性。

 

5、数据类型判断

 

判断基本类型(除  null),使用  typeof  运算符:

 

let num = 1;

 

 

 

 

 

typeof num ==='number'; //为  true

 

//还可以判断是否为  function

 

typeof func === 'function';

 

//还可以判断是否为  object

 

typeof obj === 'object';

 

判断  null  或者  undefined,直接跟  null  或者  undefined  作比较,num ===

null

 

判断数组:

 

1)arr instanceof Array //返回  true  表示  arr  是数组类型,前提是必须保证

由同一个  Array  构造函数生成的实例,如果是另一个  iframe  的话,则不为

true,则即便是数组类型,也得不到正确的结果

2)arr.constructor === Array //问题跟  instanceof  相同,且这个属性是可以

被改写的

3)Array.isArray(arr) //一般使用这个方式,不存在上面的问题

 

4)Object.prototype.toString.call(arr) //必须使用  Object  原型对象上的

toString  方法,而不能使用  Array  重写的  toString  方法,这个方式可以区分

函数/数组/对象/null,也没有上面的问题,且兼容性比第三种更好(第三就是这

种方式的语法糖,只不过只能判断是否是数组类型,直接使用这种方式可以判断

更多类型),这么使用:

 

Object.prototype.toString.call(arr) === '[object Function]' || '[object

Array]' || '[object null]' || '[object Object]'

 

PS:instranceof  的作用是判断一个对象是否是另一个对象的实例,其问题除了

上面的以外,还有就是只要是原型链上的对象,返回都为  true,因此我们使用

constructor  来判断更精准,但是  constructor  属性是可以被改写的,因此使

用第二道面试题中理想继承方式,就可以使用  constructor  来精准判断一个对

象是否是另一个对象的实例。

 

 

 

 

 

6、作用域链、闭包、作用域

 

作用域即执行环境,每个执行环境都有一个与之关联的变量对象,环境中定义的

所有变量和函数都保存在这个对象中。在  web  浏览器中,全局执行环境被认为

是  window  对象。且每个函数都有自己的执行环境,在进入函数时入栈,在函

数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。

 

当代码在一个环境中执行时,会创建变量对象的一个作用域链,是保证对执行环

境有权访问的所有变量和函数的有序访问。作用域链的前端,始终是当前执行的

代码所在环境的变量对象,最后端,始终是全局执行环境的变量对象。

 

闭包指的是有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,

就是在一个函数内部创建另一个函数。也就是一个闭包的作用域链上至少包含三

个作用域,并且只有闭包才有自由变量(即不是本地定义的,也不是全局变量)

 

//使用闭包要作内存管理 let func = (function() { return function closure()

{} })();//会将  closure  函数和它的作用域链赋值给  func  全局变量,外部函数的

作用域会一直保存在内存中,直到  closure  函数不存在//为了优化性能,在调

用完  func  后应该给  func  变量作解除引用的操作,即  func = null; //让值脱

离环境,让垃圾收集器下次运行时将值回收。

 

7、Ajax ****的原生写法

 

function ajax1() {

 

//创建一个  XHR  对象 let oAjax =

window.XMLHttpRequest ? (new XMLHttpRequest()) : (new

window.ActiveXobject('Microsoft.XMLHTTP'));

 

//返回一个函数,这是函数柯里化操作,不用每次调用  ajax  都判断浏

览器环境 //但是会占用更多的内存,因为总是会保存外部函数的作用域

return function(url, fnSucc, fnFaild) {

 

//只要  XHR  对象的  readyState  属性的值发生改变,就触发这个事

件 oAjax.onreadystatechange = function() {

 

 

 

 

 

// readyState  属性是  0-4  的值,当为  4  时,表示已经接收到全

部响应数据,并可以在客户端使用 if(oAjax.readyState === 4) {

 

//响应的  HTTP  状态 let s = oAjax.status;

 

if(s === 200 || s === 206 || s === 304) {

 

//将响应主体被返回的文本作为参数传给这个函数,并执行这

个函数 if(fnSucc) fnSucc(oAjax.responseText);

 

}else {

 

if(fnFaild) fnFaild(oAjax.status);

 

}

 

}

 

};

 

//启动一个请求,准备发送 oAjax.open('GET', url, true);

 

//发送请求 oAjax.send(null);

 

}

 

}

 

8、对象深拷贝、浅拷贝

 

对象浅拷贝是共用一个引用,因此更改新拷贝的对象时,也会更改原来的对象

 

对象深拷贝是两个引用,有以下几种方式实现深拷贝:

 

//使用  Object.assign,只能实现第一层属性的深拷贝

 

let clone = Object.assign({},obj)

 

//使用  slice,如果数组中有引用类型的元素的话,只能实现第一层的深拷贝

 

 

 

 

 

let clone = arr.slice(0);

 

//使用  concat,同  slice

 

let clone = [].concat(arr);

 

//使用  JSON  对象,无法实现属性值为  function  和  undefined  的拷贝,并

且拷贝从原型链继承的值也会有问题,比如  constructor  的值变成了  Object

 

function deepClone(obj) {

 

let _obj = JSON.stringify(obj);

 

let clone = JSON.parse(_obj);

 

return clone;

 

}

 

//使用递归,在不使用库的情况下,这种方式可以实现真正的深层度的拷贝

 

function deepClone(obj) {

 

let clone = Array.isArray(obj) ? [] : {};

 

if(obj && typeof obj === 'object') {

 

for(let key in obj) {

 

if(obj.hasOwnProperty(key) {

 

if(obj[key] && typeof obj[key] === 'object') {

 

clone[key] = deepClone(obj[key]);

 

}else {

 

clone[key] = obj[key];

 

 

 

 

 

}

 

}

 

}

 

}

 

return clone;

 

}

 

//通过  JQuery  的  extend  方法

 

//使用  lodash  函数库

 

for-in  和  for-of  的区别:

 

 

 

 

 

 

 

 

在使用  console.log  这类方法输出对象时会显示内存的最新状态,在同一个

tick  中,即便更改对象是在  console.log  之后,那么输出的对象也是被更改过

的对象,因此,如果想输出某个时刻的对象值时,应该进行深拷贝进行输出。

 

9、图片懒加载、预加载

 

图片懒加载是为了降低一次性的  HTTP  请求数量,当图片很多时,或者同时在

线人数较多时,图片懒加载可以起到很好的性能优化的作用。

 

实现步骤:

 

1)设置自定义属性  data-src  来存储图片资源;

 

2)页面初始化或者在滚动时判断图片是否出现在可视区域;

 

3)在可视区域的话,将自定义属性  data-src  的值赋值给  src  属性。

 

 

 

 

 

class LazyLoad {

 

constructor(tag) {

 

this.boundTop = 0;

 

this.clientHeight = 0;

 

this.timer = null;

 

this.aImg = Array.from(document.querySelectorAll(tag));

 

this.init();

 

}

 

isShow(el) {

 

this.boundTop = el.getBoundingClientRect().top;

 

this.clientHeight = document.documentElement.clientHeight;

 

return this.boundTop <= this.clientHeight - 100;

 

}

 

canLoad() {

 

for(let i=0;i<this.aImg.length;i++) {

 

let img = this.aImg[i]

 

if(this.isShow(img)) {

 

img.src = img.dataset.src

 

//使用  for  循环是为了在改动数组后来操作  i,使得正确遍历每个

元素

 

 

 

 

 

this.aImg.splice(i, 1)

 

i--

 

}

 

}

 

}

 

addEvent() {

 

window.addEventListener('scroll', () => {

 

if(!this.aImg.length && this.timer) return;

 

this.timer = setTimeout(() => {

 

this.canLoad();

 

this.timer = null;

 

}, 200);

 

});

 

}

 

init() {

 

this.canLoad();

 

this.addEvent();

 

}

 

}

 

let lazy = new LazyLoad('img');

 

 

 

 

 

图片预加载:在需要显示图片之前,就加载完毕,当需要显示图片时,就从缓存

中取图片,在图片不是特别多的时候,可以使用预加载。

 

有多种实现方式,以下为较喜欢的一种:

 

<script type="text/javascript">

 

//使用  Image  对象来实现

 

let imgs = [];

 

function preload() {

 

for(let i=0; i<arguments.length;i++) {

 

//因为有这个对象,所以不用像生成其他对象那样来创建新对象

 

imgs[i] = new Image();

 

//这时就会下载远程图片到本地

 

imgs[i].src = arguments[i];

 

}

 

}

 

preload(

 

 

 

"img4.imgtn.bdimg.com/it/u=951914…

=0.jpg",

 

"domain.tld/gallery/ima…",

 

"domain.tld/gallery/ima…"

 

);

 

 

 

 

 

//将图片都缓存到了  imgs  数组中,当需要显示它时,就从里面取

 

 

10、实现页面加载进度条

 

 

style="position:fixed;left:0;top:0;z-index:99;width:0%;height:3px;backgro

und:#24e1b6;" id="bar">

 

<img src="./img/1.png" />

 

<img src="./img/2.png" />

 

<img src="./img/3.png" />

 

<img src="./img/4.png" />

 

<img src="./img/5.png" />

 

 

 

 

 

 

 

如果是  Ajax  请求,那么就使用  XHR  的  progress  事件,事件处理程序接受

一个  event  参数,这个参数有三个属性:

lengthComputabel:表示进度信息是否可用,为一个布尔值;

potion:表示已经接收的字节数;

 

totalSize:便是根据  Content-Length  响应头部确定的预期字节数,即需要加

载的总字节数

 

11、this ****关键字

 

在全局环境中,this  指向  window  对象,ES5  函数中,this  对象是在运行时

基于函数的执行环境(变量对象,如全局是  window),匿名函数的执行环境

具有全局性,因此其  this  对象通常指向  window(在非严格模式下),在严格

模式下  this  指向的是  undefined,为了在严格模式下,this  重新指向

window,可以使用非直接调用  eval  的方式,如  (0, eval)('this'),使用了逗号

运算符,括号前面会返回  eval,但是和直接调用的区别就是在严格模式下  this

指向不一样。ES6  的箭头函数中,this  对象是在函数定义的执行环境。

 

12、函数式编程

 

函数式编程中的函数指的数学概念中的函数,即自变量的映射,得到的结果由输

入的值决定,不依赖于其他状态,是声明式(依赖于表达式),而非命令式,组

合纯函数来构建软件的编程方式。

 

13、手动实现 ****parseInt

 

几乎与原生  parseInt  的结果一样,如果有不同的结果,请一定留言告诉我

 

function compare(str, radix) {

 

let code = str.toUpperCase().charCodeAt(0),

 

num;

 

if(radix >= 11 && radix <= 36) {

 

if(code >= 65 && code <= 90) {

 

 

 

 

 

num = code - 55;

 

}else {

 

num = code - 48;

 

}

 

}else {

 

num = code - 48;

 

}

 

return num;

 

}

 

function isHex(first, str) {

 

return first === '0' && str[1].toUpperCase() === 'X'

 

}

 

function _parseInt(str, radix) {

 

str = String(str);

 

if(typeof str !== 'string') return NaN;

 

str = str.trim();

 

let first = str[0],

 

sign;

 

//处理第一个字符为  '-' || '+'  的情况

 

if(first === '-' || first === '+') {

 

 

 

 

 

sign = str[0];

 

str = str.slice(1);

 

first = str[0];

 

}

 

//当  radix  不存在或者小于  11  时,第一个字符只能为数字

 

if(radix === undefined || radix < 11) {

 

if(isNaN(first)) return NaN;

 

}

 

 

 

 

let reg = /^(0+)/;

 

//截取  str  前面符合要求的一段,直到遇到非数字和非字母的字符

 

let reg2 = /^[0-9a-z]+/i;

 

str = str.match(reg2)[0];

 

let len = str.length;

 

//在没有第二个参数时或者不是数字时,给第二个参数赋值

 

//isNaN('0x12')  会执行  Number('0x12')  可以转换成十进制

 

if(radix === undefined || isNaN(radix)) {

 

if(len === 1) return str;

 

//如果  str  是十六进制形式,就转换成十进制

 

if(isHex(first, str)) {

 

 

 

 

 

return Number(str);

 

}else {

 

radix = 10;

 

}

 

}else {

 

//如果有第二个参数,并且是数字,要处理第二个参数

 

radix = String(radix);

 

//如果有小数点,取小数点前面一段,处理不为整数的情况

 

radix = radix.split('.')[0];

 

//如果  radix  等于零的话,就按照  str  来判断  radix  的基数

 

if(radix === '0') {

 

if(isHex(first, str)) {

 

return Number(str);

 

}else {

 

radix = 10;

 

}

 

}

 

//如果  radix  前面有零将零去除,十六进制除外

 

if(radix.length > 1) {

 

let twoR = radix[1].toUpperCase();

 

 

 

 

 

if(radix[0] === '0' && twoR !== 'X') radix = radix.replace(reg,

'');

 

}

 

//如果  radix  是十六进制的字符串类型,也会转变成十进制的数字类

 

radix = Number(radix);

 

//radix  是否在正确的区间

 

if(radix >= 2 && radix <= 36) {

 

//如果  radix  为  16,且  str  是十六进制形式的话,直接将十六进制

转换成十进制

 

if(radix === 16 && isHex(first, str)) return Number(str);

 

}else {

 

//只要  radix  是一个有效的数字,但不在正确的区间里,就返回

NaN

 

return NaN;

 

}

 

}

 

//去除  str  前面的零

 

str = str.replace(reg, '');

 

if(str.length === 0) return 0;

 

let strArr = str.split(''),

 

 

 

 

 

numArr = [],

 

result = 0,

 

num;

 

for(let i=0; i<strArr.length; i++) {

 

num = compare(strArr[i], radix);

 

if(num < radix) {

 

numArr.push(num);

 

}else {

 

break;

 

}

 

}

 

let lenN = numArr.length;

 

if(lenN > 0) {

 

numArr.forEach(function(item, index) {

 

result += item * Math.pow(radix, lenN - index -1);

 

});

 

}else {

 

//str  开头有零的话要返回零

 

return first === '0' ? 0 : NaN;

 

}

 

 

 

 

 

if(sign === '-') result = -result;

 

return result;

 

}

 

14、为什么会有同源策略

 

同源策略限制从一个源加载的文档或脚本如何与另一个源的资源进行交互。这是

用于隔离潜在恶意文件的关键安全机制。

 

同源策略:协议相同、域名相同、端口相同,三者都必须相同

 

什么叫限制:不同源的文档不能操作另一个源的文档,在以下几个方面操作不了:

 

1)Cookie、localStorage、indexDB  无法读取

2)DOM  无法获得

 

3)AJAX  请求无法发送

 

15、怎么判断两个对象是否相等

 

1)判断引用是否为同一个引用;

 

2)如果是不同引用,判断长度是否相同;

 

3)通过  Object.getOwnpropertyNames(a)  拿到所有属性,判断是否有相同

的属性  key,如果相同,再判断值是否相同。

 

参考资料

 

16、事件模型

 

一个完整的事件流分为三个阶段:第一阶段是捕获,第二阶段是目标阶段,第三

阶段是目标元素通过冒泡上传到  window  对象。这个过程就是用户和浏览器交

互的过程,浏览器通过事件捕获知道了用户的操作,再通过事件冒泡将操作信息

传递给浏览器。

 

 

 

 

 

//事件捕获的具体流程:

 

window -> document -> html -> body -> … ->  目标元素

 

//事件冒泡的流程是反过来的

 

1)事件委托、代理

 

事件委托是将事件监听器绑定到父元素上,点击任意一个子元素,通过事件冒泡

将事件传给父元素

 

event.currentTarget //传回事件绑定的这个元素

 

event.target //传回被点击的这个元素对象

 

2)如何让事件先冒泡后捕获

 

window.addEventListener('click', func, false);

 

//false  为冒泡时触发事件,默认

 

//true  为捕获时触发事件

 

//还可以这样写:

 

el.addEventListener(type, listener, {

 

capture: false, // useCapture

 

once: false, //  是否设置单次监听

 

passive: false //  是否让阻止默认行为 preventDefault()失效

 

})

 

17、window **** ****onload ****事件和 ****DOMContentLoaded ****事件

第一个是当页面所有的资源(图片、音频、视频等)全部加载完毕才会触发

第二个是当  DOM  结构加载完毕就会触发

 

 

 

 

 

简单来说,页面的  load  事件会在  DOMContentLoaded  被触发之后才触发。

 

18、for...in ****迭代和 ****for...of ****有什么区别

 

在循环对象属性时使用  for...in,因为会将所有可枚举属性都遍历出来,但是有

以下几个问题:

 

1)将自定义属性也会遍历出来;

 

2)遍历出来的属性顺序是随机的

 

在循环数组时使用  for...of,我们可以配合迭代器,可以对复杂的、自定义的数

据结构输出我们想要的结果,且不会输出自定义的属性:

 

//以  Symbol.iterator  为  key,以函数为值,为对象构建一个迭代器

 

Array.prototype[Symbol.iterator] = function () {

 

let arr = [].concat(this);

 

let getFirst = function(array) {

 

return array.shift();

 

};

 

return { //规定返回一个对象

 

next () { //规定返回的对象中必须有  next  方法

 

let item = getFirst(arr);

 

if(item) {

 

return { //规定  next  方法必须返回一个对象,有以下两个

属性

 

value: item, //这里做了隐式类型转换,进行了  toString()

 

 

 

 

 

done: false

 

};

 

}else {

 

return {

 

done: true

 

};

 

}

 

}

 

}

 

};

 

let arr = ['a', ['b', 'c'], 2, ['d', 'e', 'f'], 'g', 3, 4];

 

function flat() {

 

let r = [];

 

for(let i of arr) { //调用  for...of  就是内部不断调用了  next()

 

r.push(i);

 

}

 

return r.join(',');

 

}

 

console.log(flat(arr));

 

//结果为:a,b,c,2,d,e,f,g,3,4

 

 

 

 

 

//当然还可以使用递归和类型转换来达到目的

 

如果想输出带有顺序的对象属性值的话,要配合  Object.keys()  来使用:

 

let obj = {};

 

for(let key of Object.keys(obj)) { //这里  for...of  迭代的是数组

 

console.log(obj[key]);

 

}

 

在使用它们时,建议加上  let  来使用,且在使用  for...of  时,要注意迭代对象

是否是可迭代的对象,即是否有迭代器,对象默认是没有迭代器的,只有数组有

迭代器,无法直接使用  for...of。

 

//比如无法直接迭代对象

 

let obj = {};

 

for(let key of obj) {} //会报错

 

19、函数柯里化

 

函数柯里化是将多参数或者差异化参数转变成单参数或者无差异化参数的一种

函数式编程的运算方式。这么做的好处是可以将核心逻辑跟其他逻辑(包括环境

判断等只需要一次性操作的逻辑)分离开来,做法是使用一个函数返回一个函数,

在第一个函数中执行其他逻辑,并且对差异化参数进行处理,然后返回一个无差

异参数且只有核心逻辑代码的函数,以后每次执行都只需要执行这个函数。在

vue.js  源码中大量使用了函数柯里化方式。举一个  ajax  的例子:

 

let ajax = (function() {

 

let oAjax = window.XMLHttpRequest ? (new XMLHttpRequest()) :

(new window.ActiveXobject('Microsoft.XMLHTTP'));

 

return function(url, fnSucc, fnFaild) {

 

 

 

 

 

oAjax.onreadystatechange = function() {

 

if(oAjax.readyState === 4) {

 

let s = oAjax.status;

 

if(s === 200 || s === 206 || s === 304) {

 

if(fnSucc) fnSucc(oAjax.responseText);

 

}else {

 

if(fnFaild) fnFaild(oAjax.status);

 

}

 

}

 

};

 

oAjax.open('GET', url, true);

 

oAjax.send(null);

 

}

 

})();

 

 

 

 

//如此环境判断只需要执行一次,当然这样又因为使用了闭包,要保存外部函数

的作用域,占用更多的内存,所以也这都是有一定的取舍的

 

20、call,apply,bind ****三者用法和区别,原生实现 ****bind

 

function fn(num1, num2) {

 

console.log(num1 + num2);

 

 

 

 

 

console.log(this);

 

}

 

fn.call(obj , 100 , 200); //call  是一个一个传入参数

 

fn.apply(obj , [100, 200]); //apply  是传入一个数组

 

//它们都是将第一个参数替换  fn  函数中的  this  关键字,然后执行  fn  函数

 

//只是替换  this  关键字,不执行  tempFn  函数

 

var tempFn = fn.bind(obj, 1, 2);

 

//在需要执行时,执行这个函数

 

tempFn();

 

//原生实现  bind

 

Function.prototype.bind2 = function(newThis) {

 

var aArgs = Array.prototype.slice.call(arguments, 1) //拿到除了 newThis

之外的预置参数序列

 

var that = this

 

return function() {

 

return that.apply(newThis,

aArgs.concat(Array.prototype.slice.call(arguments)))

 

//绑定 this 同时将调用时传递的序列和预置序列进行合并,比如  tempFn(3)

其中  3  就是调用是传递的序列

 

}

 

}

 

 

 

 

 

//原生实现  call

 

Function.prototype.call2 = function(context, ...arg) {

 

context = context || window;

 

context.fn = this; //调用  call2  的函数就是  this,比如这里的

show  函数

 

let result = context.fn(...arg); //调用  show  函数,这个函数里面的

this  是执行环境中的对象

 

delete context.fn; //不要污染  context  对象

 

return result;

 

}

 

function show (){

 

console.log(this);

 

}

 

let obj = {haha: '123'}

 

show.call2(obj); //跟原生的  call  还是有差异的,会将对象的

proto  属性也显示出来

 

show.call(obj);

 

21、立即执行函数和使用场景

 

通过定一个匿名函数,创建了一个新的函数作用域,该命名空间的变量和方法,

不会污染全局的命名空间(反过来也一样)。如果在这个函数作用域中要访问全

局对象,将全局对象以参数形式传入进去,虽然函数体内可以直接访问全局对象,

但为了不污染全局的命名空间,所以以参数形式传入,那么对这个参数的修改不

会污染全局变量。

 

 

 

 

 

有多种方式使用立即执行函数:

 

//一般使用第一种

 

(function(num) {

 

console.log(num);

 

})(123);

 

(function(num) {

 

console.log(num);

 

}(123));

 

!function(num) {

 

console.log(num);

 

}(123);

 

+function(num) {

 

console.log(num);

 

}(123);

 

-function(num) {

 

console.log(num);

 

}(123);

 

//使用运算符  =

 

let fn = function(num) {

 

console.log(num);

 

 

 

 

 

}(123);

 

22、iframe ****的缺点有哪些

 

1)页面太多不好管理;

2)框架个数多的话,会出现上下左右滚动条,会分散访问者的注意力,用户体

验度差;

3)代码复杂,不利于搜索引擎;

4)设备兼容性差,一些移动设备无法完全显示框架;

 

 

5)增加服务器  HTTP  请求。

 

 

23、数组问题

 

 

1)数组去重

 

 

2)数组常用方法

 

 

3)扁平化数组

 

 

4)按数组中各项和特定值差值排序

 

24、BOM ****属性对象方法

 

 

BOM  提供了很多对象,用于访问浏览器的功能

 

 

window  对象扮演全局对象的角色,所有在全局作用域中声明的变量和函数都

会变成  window  对象的属性和方法,以下列出操作浏览器的方法:

 

 

窗口关系即框架:

 

 

window.frames[0]  每个框架都有自己的全局对象,并且保存在  frames  集合

中;

 

 

 

 

 

window.parent  指向当前框架的上层框架,如果是当前框架是上层框架,则

parent  就等于自身的  window  对象;

 

 

窗口位置:用来确定和修改  window  对象位置的属性和方法

 

 

window.screenLeft/screenTop  分别用于表示窗口相对于屏幕(电脑显示屏)

左边和上边的位置,在火狐中是  window.screenX/screenY

 

 

确定窗口大小:

 

 

四个属性:innerWidth innerHeight outerWidth outerHeight,不同浏览器

返回的具体值不同,由于有浏览器兼容性问题,选择用:

 

 

document.documentElement.clientWidth/clientHeight  用来取得页面视口

的大小

 

 

导航和打开窗口:

 

 

使用  window.open(url, frameName)  方法可以导航到一个特定的  url,也可

以打开一个新的浏览器窗口,此方法返回指向新窗口的引用,第二个参数还可以

是一个特殊的窗口名称:_self _parent _top _blank

 

 

let frame = window.open(url, frameName);

 

 

frame.close();

 

 

alert(frame.closed); //true

 

 

系统对话框:

 

 

alert()  只显示  ok  按钮

confirm()  显示  ok  按钮和  cancel  按钮

 

 

prompt(文本提示,输入域默认值)  这是一个提示框,显示文本输入域,供用户

 

 

 

 

 

在其中输入内容

 

 

location  对象,提供了与当前窗口中加载的文档有关的信息,还提供了一些导

航功能。其用处不只表现在它保存着当前文档的信息,还表现在它将  url  解析

为独立的片段,使开发人员可以通过不同的属性访问这些片段。

 

 

hash:url  中包含  '#'  即后面的内容,如果没有则返回空字符串

host:返回服务器名称和端口号(如果有)

hostname:返回不带端口号的服务器名称

href:返回当前加载页面的完整  url

pathname:返回  url  中的目录或文件名

port:返回端口号

protocol:返回页面使用的协议

 

 

search:返回  url  中包含  '?'  即后面的内容,即查询字符串

 

 

每次修改  location  的属性(hash  除外),页面都会以新  url  重新加载

 

 

位置操作:

 

 

location.assign(url)  立即打开新  url  并在浏览器的历史记录中生成一条记录。

以下方式都是内部调用了这个方法:

 

 

window.location = url;

 

 

location.href = url;

 

 

location.replace(url)  方法导致页面改变,但不会在历史记录中生成新记录,调

用这个方法后,用户不能回到前一个页面。

 

 

location.reload()  方法没有参数时,会使页面重新加载,如果有缓存就从缓存

中获取,如果要强制从服务器重新加载,传入参数  location.reload(true)

 

 

navigator  对象,通常用于检测显示网页的浏览器类型,根据浏览器的不同有不

 

 

 

 

 

同的属性可以用,主要讲下三个通用的又实用的属性:

 

 

1)appCodeName  浏览器的名称,除了  IE  浏览器之外,基本都返回:

"Netscape"

2)online  指明系统是否处于脱机状态,返回布尔值

 

 

3)userAgent  返回客户端的完整信息

 

 

screen  对象,在编程中用处不大,基本上只用来表明客户端的能力,其中包括

浏览器窗口外部的显示器的信息,如像素宽度和高度等。这些信息经常集中出现

在测定客户端能力的站点跟踪工具中。列举一个属性(只有  IE  浏览器支持):

 

 

deviceXDPI:返回显示屏幕的每英寸水平点(像素)数。返回的其实就是分辨

率,而那个水平点数中的一点就是一像素,DPI  是显示分辨率的一个比例值,

DPI  越高说明分辨率越高,如果安卓  DPI  比率分辨值为  1  的话,苹果必然是

2,苹果的像素高

 

 

history  对象保存着用户上网的历史记录,其有一个  length  属性,保存这历史

记录的数量

 

 

history.go()  方法可以在用户的历史记录中任意跳转,可以向后也可以向前,接

收一个整数值作为参数;

还可以传递一个字符串参数,此时浏览器会跳转到历史记录中包含该字符串的第

一个位置,可能前进可能后退,具体要看哪个位置更近,如果不包含该字符串,

那么这个方法什么都不做。

 

 

可以用  back()  来后退,forward()  来前进

 

 

history.pushState(state/null, title/null, url)  和  history.replaceState()  ,第

二个  API  不会加入到历史记录中,用法和第一个相同,第一个  API  的第三个

参数必须是与当前页面处于同一个域,它们的作用是使  url  跳转而无需重新加

载页面,不会触发  popstate  事件,只有在执行了  history.back

history.forward  以及  history.go,或者点击了浏览器的前进后退或者跳转按钮

(双击前进或者后退按钮),就会触发  popstate  事件。

 

 

 

 

 

vue  的编程式导航利用了  location.hash  属性以及  window.history  上面两

个  API  实现页面跳转但不重新加载页面的效果。

 

 

25、服务端渲染

 

服务端渲染指的是后端(比如  JAVA PHP)生成  html  文件,客户端发送  http

请求直接获得  html  文件然后直接显示出来,优点是首屏渲染快,有利于  SEO

搜索引擎,缺点是页面切换慢,因为都要发起  http  请求;

客户端渲染指的是客户端通过  ajax  请求数据,首次渲染通过  http  请求  JS  和

CSS  文件,然后在客户端拼接成  html  文件,再显示出来,优点是页面切换快

(通过  JS  渲染,动态更新页面),但首屏渲染慢,且不利于  SEO  搜索引擎,

React  和  Vue  等  MVVM  框架都是客户端渲染。

如果对于百度的  SEO  有需求(因为谷歌已经可以支持  JS  动态网页),那么就

考虑使用前端同构,指的是原本在客户端生成  html  文件,在中间层  node  环

境来生成  html  文件,首屏渲染是在  node  环境生成,之后页面切换还是在客

户端,中间层和客户端使用一套  JS  代码,最终都是将虚拟  DOM  转换成  html

文件,因为虚拟  DOM  本质上是一个  JS  对象,所以可以跑在  node  环境。

 

 

React  的前端同构的支持框架是  next.js,Vue  的前端同构的支持框架是

nuxt.js,在使用这些框架时,要特别注意  node  环境和客户端环境是不同的,

比如说没有  window  对象的,因此使用前端框架会报错,当使用这些框架时,

要声明只在客户端渲染,再如引入  npm  包,带有  DOM  操作的,不能用

import  来引入,要使用  require()  方式,总之一套代码在两个环境下运行,会

遇到很多坑。因此学习成本也是较高的。

 

 

26、垃圾回收机制

 

JS  具有自动垃圾收集机制,执行环境会负责管理代码执行过程中使用的内存,

所需内存的分配以及无用内存的回收完全实现了自动管理。垃圾收集机制的原

理:找到那些不再继续使用的变量,然后释放其占用的内存。垃圾收集机制会按

照固定的时间间隔周期性地执行这一操作。

垃圾收集机制必须跟踪哪个变量有用哪个变量无用,对于不再有用的变量打上标

记,以备将来收回其占用的内存。

JS  中最常用的跟踪方法是标记清除,当函数执行完后,就会给局部变量打上“离

开环境”的标记(除了闭包),在下一次垃圾回收时间到来时就会清除这一块内

 

 

 

 

 

存,手动将一个有值的变量赋值为  null,也是让这个值离开环境,也可以释放

内存。

 

 

还有一种跟踪方法是引用计数,这会引起循环引用的问题,但现在所有的浏览器

都使用了标记清除式的垃圾回收机制,所以不用考虑这个问题了。

 

 

27、eventloop

 

 

1)进程和线程

 

 

进程是一个程序完成的过程  = CPU  加载上下文  + CPU  执行  + CPU  保存上

下文

 

 

线程是构成一个程序的任务组合,在  CPU  执行阶段就是执行这一个一个任务

 

 

不同进程间数据很难共享,不同线程间数据容易共享

进程可以拓展到多机(多进程),线程最多适合多核(多线程),多核中可以开

启多进程,比如浏览器打开一个新的页面(除了多个空白页面外),就会创建一

个进程

某个线程使用某些共享内存时,可以上一把“互斥锁”,其他线程只能排队等待,

防止多线程同时读取某一块内存区域

 

 

某些内存区域只能供给固定的数目线程使用,其他线程排队等待,这叫“信号量

“,保证多线程不会互相冲突

 

 

2)任务队列

 

 

JS  是单线程的,即同一时间只能完成一个任务

 

 

任务队列也可以叫异步队列,当一个事件被触发时,就会将回调函数体扔进这个

任务队列中,当运行栈的同步任务执行完毕时(一个  tick  完成),就会去任务

队列中取异步任务,然后执行(此时为下一个  tick)。所以  JS  遇到一个异步

任务时会挂起,而不是立即执行。

 

 

 

 

 

28、如何快速让字符串变成以千为精度的数字

 

 

我也不知道理解题目是否到位,应该是为了考  Number()  的精准度的问题,代

码如下:

 

 

(Number('12.69598')*1000).toFixed(0)

 

 

//Number('12.69598')*1000  得到的结果为  12695.970000000001

 

 

//通过  toFixed()  完全去除小数点

 

 

29、什么是内存泄漏

 

 

我也不知道理解题目是否到位,应该是为了考  Number()  的精准度的问题,代

码如下:

 

 

(Number('12.69598')*1000).toFixed(0)

 

 

//Number('12.69598')*1000  得到的结果为  12695.970000000001

 

 

//通过  toFixed()  完全去除小数点

 

 

30、js ****怎么判断一个对象中是否包含某个属性?

 

1.  用  in  关键字 会返回  true/false,例如:

 

if(属性名称  in  目标对象){  执行内容; }

 

2.  使用 hasOwnProperty  方法,该方法会返回一个布尔值,指示对象自身属性

 

中是否具有指定的属性(也就是,是否有指定的键),例如:

 

var obj = {name:'jack'};

 

obj.hasOwnProperty('name'); // --> true

 

 

 

 

 

obj.hasOwnProperty('toString'); // --> false

 

3.  使用 undefined 判断,例如:

 

var o={x:1};

 

o.x!==undefined; //true

 

o.y!==undefined; //false

 

o.toString!==undefined //true

 

31、数组遍历的方法有哪些?

 

ES5 新增:

 

1. forEach()参数为一个函数,函数的参数有当前元素的值(必填),当前元素

 

下标,数组对象本身。例如:

 

let a=[1,2,3]

 

a.forEach((item,index,arr)=>console.log(item,index,arr))

 

//1 0 [ 1, 2, 3 ]

 

//2 1 [ 1, 2, 3 ]

 

//3 2 [ 1, 2, 3 ]

 

2. some()用来判断数组中是否有至少一个元素符合函数条件,返回布尔值。例如:

 

let a=[1,2,3]

 

console.log(a.some(item=>item%2==0)) //true

 

console.log(a.some(item=>item>5)) //false

 

3. every()判断数组中的每一个元素是否全部符合函数条件,返回布尔值。例如:

 

let a=[5,6,7]

 

console.log(a.every(item=>item>1)) //true

 

 

 

 

 

console.log(a.every(item=>item>5)) //false

 

4. filter()过滤元素,返回符合条件的元素组成的新数组。例如:

 

let a=[1,2,3,4,5,6]

 

console.log(a.filter(item=>item%2==0)) //[2,4,6]

 

5. Map()对数组中的每一个元素进行处理,返回新的数组。例如:

 

let a=[1,2,3]

 

console.log(a.map(item=>item*3)) //[3,6,9]

 

6. reduce()累加器,把数组中的元素累加到一起。参数为一个函数,函数的参数

 

第一个第二个必填,分别代表上次计算的总数和将要被计算的元素,第三个第四

 

个参数可选,分别为当前元素的下标和数组本身。例如:

 

let a=[1,2,3,4,5]

 

console.log(a.reduce((a,b)=>a+b,0)) //15

 

ES6 新增:

 

1. find() 找 出 数 组 中 第 一 个 符 合 回 调 函 数 条 件 的 元 素 , 如 果 没 找 到 则 返 回

 

undefined。例如:

 

let a=[1,2,3,4]

 

console.log(a.find(item=>item>5))   //undefined

 

console.log(a.find(item=>item>2))   //3

 

2. findIndex()找出数组中第一个符合函数条件的元素的下标,如果没找到返回

 

-1。例如:

 

let a = [1, 3, 3, 4, 5, 6];

 

console.log(

 

 

 

 

 

a.findIndex(item => {

 

item % 2 == 0;

 

})

 

); //3

 

console.log(

 

a.findIndex(item => {

 

item > 10;

 

})

 

); //-1

 

32、如何准确判断数据类型?

 

一、  typeof 关键字

 

typeof 是一个关键字,可以判断表达式或者变量的类型,但是有个 bug,

 

typeof null  会返回  ‘object’。

 

 

 

二、  Object.prototype.toString.call(value)

 

使用 Object.prototype 上的原生 toString()方法判断数据类型。

 

举个例子:

 

Function fn(){console.log(“test”);}

 

Object.prototype.toString.call(fn); //  得到'[object Function]'字符串

 

 

 

三、  instanceof 关键字

 

Object.prototype.toString.call(value)  这种方法不能准确判断 person 是

 

 

 

 

 

Person 类的实例,如果要判断某个对象是否为某个类的实例,可以用 instanceof

 

操作符来进行判断,这个运算符只能用于对象,不适用原始类型的值。

 

33、await **** ****async ****是做什么用的?

 

async  和  await  是  ES2016  新增两个关键字,目的是简化  Promise api

 

的使用,并非是替代  Promise。

 

 

 

mdn 上解释:async function  声明用于定义一个返回  AsyncFunction  对

 

象的异步函数。异步函数是指通过事件循环异步执行的函数,它会通过一个

 

Promise  对象返回其结果。

 

 

 

简单来说,如果你在一个函数前面使用了 async 关键字,那么这个函数就会

 

返回一个 promise。如果你返回的不是一个 promise,JavaScript 也会自动把

 

这个值"包装"成 Promise 的 resolve 值。

 

 

 

而 await 关键字必须出现在 async 函数中,它会暂停当前  async function

 

的执行,等待  Promise  处理完成。如果 await 后面的表达式是一个 Promise,

 

则会分为以下两种情况:

 

 

 

若  Promise  正常处理(fulfilled),其回调的 resolve 函数参数作为  await  表

 

达式的值,继续执行  async function。

 

若  Promise  处理异常(rejected),await  表达式会把  Promise  的异常原因

 

抛出。

 

 

 

 

 

 

 

 

如果 await 的表达式不是 Promise,则会将其使用 Promise.resolve 包装后

 

按照规则运行。

 

34、JS ****中代码提升怎么理解?

 

1.函数声明会置顶

 

2.变量声明也会置顶

 

3.函数声明比变量声明更置顶

 

4.变量和赋值语句一起书写,在 js 引擎解析时,会将其拆成声明和赋值 2 部分,

 

声明置顶,赋值保留在原来位置

 

5.声明过的变量不会重复声明,只有最后一次声明生效。例如函数和变量声明重

 

名时

 

35、this ****指向怎么理解?

 

 

1.  在简单的调用的时候,函数运行环境是 window,所以 this 的值就等于全局

对象,  在严格模式中,则为 undefined。

 

 

2.  在方法调用是,this 的值就是含有该方法的对象。

 

 

3.  在使用  myFunc.call(context, arg1, ..., argN)或 myFunc.apply(context,

[arg1, ..., argN])的间接调用中,this 的值等于 call 或者 apply 的第一个参数

 

 

4.  使用 new 关键字时,this 等于创建的实例

 

 

36、js ****中有哪些内置对象?

 

基本数据:Boolean、null、undefined、String、Number、symbol(ES6 中新

 

增)

 

 

 

 

 

内置对象:Array 对象、Date 对象、string 对象、正则表达式对象、Global 对

 

 

ES6 ****面试问题整理

 

1.ES6 ****新增的一些特性:

 

1.let 声明变量和 const 声明常量,两个都有块级作用域

ES5 中是没有块级作用域的,并且 var 有变量提升,在 let 中,使用的变量

一定要进行声明

2.箭头函数

ES6 中的函数定义不再使用关键字 function(),而是利用了()=>来进行定义

3.模板字符串

模板字符串是增强版的字符串,用反引号(`)标识,可以当作普通字符串

使用,也可以用来定义多行字符串

4.解构赋值

ES6  允许按照一定模式,从数组和对象中提取值,对变量进行赋值

5.for of 循环

for...of 循环可以遍历数组、Set 和 Map 结构、某些类似数组的对象、对象,

以及字符串

6.import、export 导入导出

ES6 标准中,Js 原生支持模块(module)。将 JS 代码分割成不同功能的小块

进行模块化,将不同功能的代码分别写在不同文件中,各模块只需导出公共接口

部分,然后通过模块的导入的方式可以在其他地方使用

7.set 数据结构

Set 数据结构,类似数组。所有的数据都是唯一的,没有重复的值。它本身

是一个构造函数

8....展开运算符

可以将数组或对象里面的值展开;还可以将多个值收集为一个变量

9.修饰器  @

decorator 是一个函数,用来修改类甚至于是方法的行为。修饰器本质就是

编译时执行的函数

10.class  类的继承

 

 

 

 

 

ES6 中不再像 ES5 一样使用原型链实现继承,而是引入 Class 这个概念

11.async、await

使用  async/await,  搭配 promise,可以通过编写形似同步的代码来处理异

步流程,  提高代码的简洁性和可读性

async  用于申明一个  function  是异步的,而  await  用于等待一个异步方

法执行完成

12.promise

Promise 是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)

更合理、强大

13.Symbol

Symbol 是一种基本类型。Symbol  通过调用 symbol 函数产生,它接收一

个可选的名字参数,该函数返回的 symbol 是唯一的

14.Proxy 代理

 

 

使用代理(Proxy)监听对象的操作,然后可以做一些相应事情

 

 

2、var、let **** ****const ****区别是什么?

 

首先,我们都知道,var 是 es5 中声明变量或常量的关键字,而 const 和 let 则

 

是 es6 中的概念。

 

 

 

const 和 let 在原先的基础上主要做了以下三点改进:

 

 

 

1.失去变量提升

 

只有 var 声明的变量存在变量提升,es6 新增的 const 和 let 声明的变量不

 

存在变量提升的概念。ES6  规定  let  和  const  命令不发生变量提升,使用  let

 

和  const  命令声明变量之前,该变量是不可用的。主要是为了减少运行时错误,

 

防止变量声明前就使用这个变量,从而导致意料之外的行为。

 

 

 

 

 

2.新增暂时性死区的概念

 

如果代码块中的变量是 let 或者 const 声明的,那么就会形成一个块级作用

 

域(前花括号和后花括号包裹的区域),其实就是一个封闭作用域,内部变量与

 

外界无关,使内部的变量只能在当前块级作用域中声明。

 

 

 

3.不再允许重复声明

 

var 可以重复声明,let  和  const  命令声明的变量不允许重复声明。

 

 

 

最后,let 和 const 之间的区别是 let 声明的是一个变量,而 const 声明一个只

 

读的常量,一旦声明变量,就必须立即初始化,不能留到以后赋值。

 

3、使用箭头函数应注意什么?

 

(1)用了箭头函数,this 就不是指向 window,而是父级(指向是可变的)

(2)不能够使用 arguments 对象

(3)不能用作构造函数,这就是说不能够使用 new 命令,否则会抛出一个

错误

 

 

(4)不可以使用 yield 命令,因此箭头函数不能用作  Generator  函数

 

 

4、ES6 ****的模板字符串有哪些新特性?并实现一个类模板字符串的功能

 

基本的字符串格式化。将表达式嵌入字符串中进行拼接。用${}来界定

在 ES5 时我们通过反斜杠()来做多行字符串或者字符串一行行拼接。ES6 反

引号(``)就能解决

 

 

类模板字符串的功能

 

 

let name = 'web';

let age = 10;

 

 

 

 

 

let str = '你好,name  已经  {name}  已经  {age}岁了'

str = str.replace(/${([^}]*)}/g,function(){ return eval(arguments[1]);

})

console.log(str);//你好,web  已经  10 岁了

 

5、介绍下 ****Set、Map ****的区别?

 

应用场景 Set 用于数据重组,Map 用于数据储存

Set:

(1)成员不能重复

(2)只有键值没有键名,类似数组

(3)可以遍历,方法有 add, delete,has

Map:

(1)本质上是健值对的集合,类似集合

 

 

(2)可以遍历,可以跟各种数据格式转换

 

 

6、ECMAScript ****6 ****怎么写 ****class ****,为何会出现 ****class?

 

 

ES6 的 class 可以看作是一个语法糖,它的绝大部分功能 ES5 都可以做到,

新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法

 

 

//定义类 class Point {

constructor(x,y){ //构造方法

this.x = x; //this 关键字代表实例对象

this.y = y;

}toString(){

return '(' + this.x + ',' + this.y + ')';

}}

 

7、Promise ****构造函数是同步执行还是异步执行,那么 ****then ****方法呢?

 

 

promise 构造函数是同步执行的,then 方法是异步执行的

 

 

 

 

 

8、setTimeout、Promise、Async/Await ****的区别

 

事件循环中分为宏任务队列和微任务队列

其中 setTimeout 的回调函数放到宏任务队列里,等到执行栈清空以后执行

promise.then 里的回调函数会放到相应宏任务的微任务队列里,等宏任务

里面的同步代码执行完再执行

async 函数表示函数里面可能会有异步方法,await 后面跟一个表达式

 

 

async 方法执行时,遇到 await 会立即执行表达式,然后把表达式后面的代

码放到微任务队列里,让出执行栈让同步代码先执行

 

 

9、promise ****有几种状态,什么时候会进入 ****catch?

 

三个状态:pending、fulfilled、reject

两个过程:padding -> fulfilled、padding -> rejected

 

 

当 pending 为 rejectd 时,会进入 catch

 

 

10、下面的输出结果是多少

 

const promise = new Promise((resolve, reject) => {

console.log(1);

resolve();

console.log(2);})

promise.then(() => {

console.log(3);})

console.log(4);//1 2 4 3

 

 

Promise  新建后立即执行,所以会先输出  1,2,而  Promise.then()  内部的代

码在  当次  事件循环的  结尾  立刻执行  ,所以会继续输出 4,最后输出 3

 

 

11、使用结构赋值,实现两个变量的值的交换

 

let a = 1;let b = 2;[a,b] = [b,a];

 

 

 

 

 

12、设计一个对象,键名的类型至少包含一个 ****symbol ****类型,并且实现遍历所

 

****key

 

let name = Symbol('name');

let product = {

Reflect.ownKeys(product);

 

13、下面 ****Set ****结构,打印出的 ****size ****值是多少

 

let s = new Set();

s.add([1]);

s.add([1]);console.log(s.size);//答案:2

两个数组[1]并不是同一个值,它们分别定义的数组,在内存中分别对应着不同

的存储地址,因此并不是相同的值

 

 

都能存储到 Set 结构中,所以 size 为 2

 

 

14、Promise **** ****reject **** ****catch ****处理上有什么区别

 

reject  是用来抛出异常,catch  是用来处理异常

reject  是  Promise  的方法,而  catch  是  Promise  实例的方法

reject 后的东西,一定会进入 then 中的第二个回调,如果 then 中没有写

第二个回调,则进入 catch

 

 

网络异常(比如断网),会直接进入 catch 而不会进入 then 的第二个回调

 

 

15、使用 ****class ****手写一个 ****promise

 

//创建一个 Promise 的类

class Promise{

constructor(executer){//构造函数 constructor 里面是个执行器

this.status = 'pending';//默认的状态  pending

this.value = undefined//成功的值默认 undefined

this.reason = undefined//失败的值默认 undefined

//状态只有在 pending 时候才能改变

 

 

 

 

 

let resolveFn = value =>{ //判断只有等待时才能 resolve 成功

if(this.status == pending){ this.status = 'resolve'; this.value = value;

}

} //判断只有等待时才能 reject 失败

let rejectFn = reason =>{ if(this.status == pending){ this.status = 'reject';

this.reason = reason;

}

} try{ //把 resolve 和 reject 两个函数传给执行器 executer

executer(resolve,reject);

}catch(e){

reject(e);//失败的话进 catch

}

}

then(onFufilled,onReject){ //如果状态成功调用 onFufilled

if(this.status = 'resolve'){

onFufilled(this.value);

} //如果状态失败调用 onReject

if(this.status = 'reject'){

onReject(this.reason);

}

}

}

 

16、如何使用 ****Set ****去重

 

let arr = [12,43,23,43,68,12];let item = [...new Set(arr)];

console.log(item);//[12, 43, 23, 68]

 

17、将下面 ****for ****循环改成 ****for ****of ****形式

 

let arr = [11,22,33,44,55];let sum = 0; for(let i=0;i<arr.length;i++){

sum += arr[i];}

//答案:let arr = [11,22,33,44,55];let sum = 0; for(value of arr){

sum += value;}

 

 

 

 

 

18、理解 ****async/await ****以及对 ****Generator ****的优势

 

async await  是用来解决异步的,async 函数是 Generator 函数的语法糖

使用关键字 async 来表示,在函数内部使用  await  来表示异步

async 函数返回一个  Promise  对象,可以使用 then 方法添加回调函数

当函数执行的时候,一旦遇到 await 就会先返回,等到异步操作完成,再接

着执行函数体内后面的语句

async 较 Generator 的优势:

1.内置执行器。Generator  函数的执行必须依靠执行器,而  Aysnc  函数

自带执行器,调用方式跟普通函数的调用一样

2.更好的语义。async  和  await  相较于  *  和  yield  更加语义化

3.更广的适用性。yield 命令后面只能是  Thunk  函数或  Promise 对象,

async 函数的 await 后面可以是 Promise 也可以是原始类型的值

 

 

4.返回值是  Promise。async  函数返回的是  Promise  对象,比

Generator 函数返回的 Iterator 对象方便,可以直接使用  then()  方法进行调用

 

 

19、forEach、for ****in、for ****of ****三者区别

 

forEach 更多的用来遍历数组

for in  一般常用来遍历对象或 json

for of 数组对象都可以遍历,遍历对象需要通过和 Object.keys()

 

 

for in 循环出的是 key,for of 循环出的是 value

 

 

20、说一下 ****es6 ****的导入导出模块

 

 

导入通过 import 关键字

 

 

//  只导入一个 import {sum} from "./example.js"

//  导入多个 import {sum,multiply,time} from "./exportExample.js"

//  导入一整个模块 import * as example from "./exportExample.js"

 

 

导出通过 export 关键字

 

 

 

 

 

//可以将 export 放在任何变量,函数或类声明的前面 export var firstName =

'Michael';export var lastName = 'Jackson';export var year = 1958; //也可

以使用大括号指定所要输出的一组变量 var firstName = 'Michael'; var

lastName = 'Jackson'; var year = 1958;export {firstName, lastName, year};

//使用 export default 时,对应的 import 语句不需要使用大括号 let bosh =

function crs(){}export default bosh;import crc from 'crc'; //不使用 export

default 时,对应的 import 语句需要使用大括号 let bosh = function

crs(){}export bosh;import {crc} from 'crc';