0
点赞
收藏
分享

微信扫一扫

2021年这些js相关的前端面试题真的值得收藏


1. 实用js写红绿灯的效果?

<ul id="traffic" class="">
<li id="green"></li>
<li id="yellow"></li>
<li id="red"></li>
</ul>
ul {
position: absolute;
width: 200px;
height: 200px;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
}
/*画3个圆代表红绿灯*/
ul >li {
width: 40px;
height: 40px;
border-radius:50%;
opacity: 0.2;
display: inline-block;
}
/*执行时改变透明度*/
ul.red >#red,
ul.green >#green,
ul.yellow >#yellow{
opacity: 1.0;
}
/*红绿灯的三个颜色*/
#red {background: red;}
#yellow {background: yellow;}
#green {background: green;}
function timeout(timer){
return function(){
return new Promise(function(resolve,reject){
setTimeout(resolve,timer)
})
}
}
var green = timeout(3000);
var yellow = timeout(4000);
var red = timeout(5000);
var traffic = document.getElementById("traffic");
(function restart(){
'use strict' //严格模式
console.log("绿灯"+new Date().getSeconds()) //绿灯执行三秒
traffic.className = 'green';
green()
.then(function(){
console.log("黄灯"+new Date().getSeconds()) //黄灯执行四秒
traffic.className = 'yellow';
return yellow();
})
.then(function(){
console.log("红灯"+new Date().getSeconds()) //红灯执行五秒
traffic.className = 'red';
return red();
}).then(function(){
restart()
})
})();

2. axios是否需要promise封装?

需要

import axios from 'axios'
const http = ({
url,method,params,headers
}) => {
return new Promise ( (resolve,reject) => {
axios({
url,
method,
params,
headers
})
.then( res => {
resolve(res)
})
.catch( error => {
throw error
})
})
}
export default http

3. Promise内部发生错误,如果同时.then方法有第二个参数,也有.catch会调用哪个

.catch

4. 宏任务 微任务

setTimeout(function(){
console.log('1')
});

new Promise(function(resolve){
console.log('2');
resolve();
}).then(function(){
console.log('3')
});

console.log('4')

settimeout肯定是异步的。 我也知道有一个event队列,你settimeout没设置时间应该直接就进入这个队列了吧,然后就是Promise的回掉函数进入event队列。 当时我二话不说给了个答案 2,4,1,3.并且很自信。然后面试官就问你不想想了?我说不想了。然后后半段他全程开始皱眉头了。我也凉凉。最后他让我回去看一下宏任务和微任务。

首先说一下普通的异步函数的执行过程吧

同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。当指定的事情完成时,Event Table会将这个函数移入Event Queue。主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。上述过程会不断重复,也就是常说的Event Loop(事件循环)。

那么如此看来我给的答案还是对的。但是js异步有一个机制,就是遇到宏任务,先执行宏任务,将宏任务放入eventqueue,然后在执行微任务,将微任务放入eventqueue最骚的是,这两个queue不是一个queue。当你往外拿的时候先从微任务里拿这个回掉函数,然后再从宏任务的queue上拿宏任务的回掉函数。 我当时看到这我就服了还有这种骚操作。

而宏任务一般是:包括整体代码script,setTimeout,setInterval。

微任务:Promise,process.nextTick。

记住就行了。

然后回到开头的代码。因为settimeout是宏任务,虽然先执行的他,但是他被放到了宏任务的eventqueue里面,然后代码继续往下检查看有没有微任务,检测到Promise的then函数把他放入了微任务序列。等到主线进程的所有代码执行结束后。先从微任务queue里拿回掉函数,然后微任务queue空了后再从宏任务的queue拿函数。

所以正确的执行结果当然是:2,4,3,1。

5. Js 原型和原型链

原型链的设计是js的精髓所在,比较抽象。需要从内部设计原理去理解这种设计思想,在纸上画画其中的关系会帮助理解。

prototype对象

prototype对象的引入:所有实例对象需要共享的属性和方法,都放在这个对象中,那些不需要共享的属性和方法,就放在构造函数中。以此来模拟类。

function Animal(name) {
this.name = name
}

Animal.prototype.getName = function() {
console.log(this.name)
}

var animal1 = new Animal('Kate')
var animal2 = new Animal('Lucy')

//对象animal1 和 animal2共享方法getName
animal1.getName()
animal2.getName()

原型链

在javascript中,每个对象都有一个指向它的原型(prototype)对象的内部链接。每个原型对象又有自己的原型,直到某个对象的原型为null为止,组成这条链的最后一环。

*proto写入es6标准

当一个对象被创建时,它的​​__protp__​​属性和内部属性​​[[prototype]]​​指向相同的对象(也就是它的构造函数的​​prototype​​属性)。改变​​__proto__​​属性的值同时也会改变内部属性​​[[prototype]]​​的值,除非该对象是不可扩展的。

在ES5中,所有构造函数的__proto__都指向Function.prototype

**在ES6中,构造函数的__proto__指向它的父类构造函数

obj.__proto__ === obj.[[prototype]]
// ES5
Cat.__proto__ === Function.prototype
// ES6
Cat.__proto__ === Animal

构造函数继承

有四种方式可以实现构造函数的继承

1.调用apply方法

function Animal() {
this.species = '动物'
}
Animal.prototype.getName = function() {
console.log('我是动物')
}

function Cat() {
Animal.apply(this, arguments)
}
var cat = new Cat()
cat.species // 动物
cat.getName() // undefined

这种方法可以继承父类构造函数的属性,但是无法继承​​prototype​​属性,即父类中共享的方法和属性

2.改写​​prototype​​对象

Cat.prototype = new Animal()
Cat.prototype.constructor = Cat

这是最常用的方法来模拟单继承,缺点是始终要保留Animal的对象,如果Animal对象比较大时,会消耗部分内存(其实很少),并且没有实现多继承

3.直接继承​​prototype​

Cat.prototype = Animal.prototype
Cat.prototype.constructor = Cat

缺点是当修改了Cat.prototype上的方法时会影响Animal.prototype

4.利用空对象作中介

var F = function(){}
F.prototype = Animal.prototype
Cat.prototype = new F()
Cat.prototype.constructor = Cat

6. js的事件循环机制是什么

进程、线程

  • 进程是系统分配的独立资源,是 CPU 资源分配的基本单位,进程是由一个或者多个线程组成的。
  • 线程是进程的执行流,是CPU调度和分派的基本单位,同个进程之中的多个线程之间是共享该进程的资源的。

浏览器内核

  • 浏览器是多进程的,浏览器每一个 tab 标签都代表一个独立的进程(也不一定,因为多个空白 tab 标签会合并成一个进程),浏览器内核(浏览器渲染进程)属于浏览器多进程中的一种。
  • 浏览器内核有多种线程在工作。
  • GUI 渲染线程:
  • 负责渲染页面,解析 HTML,CSS 构成 DOM 树等,当页面重绘或者由于某种操作引起回流都会调起该线程。
  • 和 JS 引擎线程是互斥的,当 JS 引擎线程在工作的时候,GUI 渲染线程会被挂起,GUI 更新被放入在 JS 任务队列中,等待 JS 引擎线程空闲的时候继续执行。
  • JS 引擎线程:
  • 单线程工作,负责解析运行 JavaScript 脚本。
  • 和 GUI 渲染线程互斥,JS 运行耗时过长就会导致页面阻塞。
  • 事件触发线程:
  • 当事件符合触发条件被触发时,该线程会把对应的事件回调函数添加到任务队列的队尾,等待 JS 引擎处理。
  • 定时器触发线程:
  • 浏览器定时计数器并不是由 JS 引擎计数的,阻塞会导致计时不准确。
  • 开启定时器触发线程来计时并触发计时,计时完成后会被添加到任务队列中,等待 JS 引擎处理。
  • http 请求线程:
  • http 请求的时候会开启一条请求线程。
  • 请求完成有结果了之后,将请求的回调函数添加到任务队列中,等待 JS 引擎处理。

JavaScript 引擎是单线程

JavaScript 引擎是单线程,也就是说每次只能执行一项任务,其他任务都得按照顺序排队等待被执行,只有当前的任务执行完成之后才会往下执行下一个任务。

HTML5 中提出了 Web-Worker API,主要是为了解决页面阻塞问题,但是并没有改变 JavaScript 是单线程的本质。了解 ​​Web-Worker​​。

JavaScript 事件循环机制

JavaScript 事件循环机制分为浏览器和 Node 事件循环机制,两者的实现技术不一样,浏览器 Event Loop 是 HTML 中定义的规范,Node Event Loop 是由 libuv 库实现。这里主要讲的是浏览器部分。

Javascript 有一个 main thread 主线程和 call-stack 调用栈(执行栈),所有的任务都会被放到调用栈等待主线程执行。

  • JS 调用栈
    JS 调用栈是一种后进先出的数据结构。当函数被调用时,会被添加到栈中的顶部,执行完成之后就从栈顶部移出该函数,直到栈内被清空。
  • 同步任务、异步任务
    JavaScript 单线程中的任务分为同步任务和异步任务。同步任务会在调用栈中按照顺序排队等待主线程执行,异步任务则会在异步有了结果后将注册的回调函数添加到任务队列(消息队列)中等待主线程空闲的时候,也就是栈内被清空的时候,被读取到栈中等待主线程执行。任务队列是先进先出的数据结构。
  • Event Loop
    调用栈中的同步任务都执行完毕,栈内被清空了,就代表主线程空闲了,这个时候就会去任务队列中按照顺序读取一个任务放入到栈中执行。每次栈内被清空,都会去读取任务队列有没有任务,有就读取执行,一直循环读取-执行的操作,就形成了事件循环。

7.前端跨域的方式

前端跨域的方案:

1、通过jsonp跨域
2、postMessage跨域
3、跨域资源共享(CORS)
4、nginx代理跨域
5、nodejs中间件代理跨域
6、WebSocket协议跨域
7.反向代理

<u></u>

8.Promise的理解,和promise都有哪些方法

Promise,就是一个对象,用来传递异步操作的消息,避免了层层嵌套的回调函数。它代表了某个未来才会知道结果的事件(通常是一个异步操作),并且这个事件提供统一的API,可供进一步处理。 
(1)对象的状态不受外界影响。有三种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled)和Rejected(已失败)。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果

9.原型和原型链的理解

我们创造的每一个函数都有一个prototype(原型)属性。这个属性是一个指针,指向原型对 象。在默认情况下,所有的原型对象都会有一个constructor(构造函数)属性,这个属性包含一个指向prototype属相所在的指针。当调用构造函数创建一个新实例之后,该实例内部将包含一个指针(内部属性),指向构造函数的原型对象。

<u></u>

10:异步方式

1. Promise



2. Generator



3. Async-await

4. Node.js 中的nextTick()和setimmediate()



5. async库

11:修复bug或添加新功能的常见工作流(git命令)是什么?空和未定义的javascript有什么区别?

git flow feature start f1 添加新特性,这个操作创建了一个基于develop的特性分支,并切换到这个分支之下。

git flow feature finish f1 完成新特性,这个操作会合并f1分支到develop分支,并删除特性分支,切换回develop分支。

git flow feature publish f1 发布新分支,发布新特性分支到远程服务器,其它用户也可以使用这分支。

修复bug:

git flow hotfix start VERSION [BASENAME] 创建hotfix分支,VERSION 参数标记着修正版本,[BASENAME]为finish release时填写的版本号。

12:您使用什么框架来编写单元测试,写下一个案例来验证调用的函数

一、问题描述: 
在一个升序数组中,使用折半查找得到要查询的值的索引位置。如:
var a=[1,2,3,4,5,6,7,8,9];
search(a,3);//返回2
search(a,1);//左边界,返回0
search(a,9);//右边界,返回8
search(a,0);//比最小的值还小,返回"您查找的数值不存在"
search(a,10);//比最大的值还大,返回"您查找的数值不存在"

注:折半查找必须在有序数组中才有效,无序的数组不能实现查找功能。比如:在[10,5,6,7,8,9,20]中查找10,中间索引位置的值为7,比较得出7比10小,因而应该在右子数组中查找,实际上不可能找到10;

二、我的实现

function search(arr,num) {
var l=arr.length;
var left=0;
var right=l-1;
var center=Math.floor((left+right)/2);
while(left<=l-1&&right>=0){
if (arr[center]==num) return center;
if (left==right) return "您查找的数不存在";
if (arr[center]>num) {
right=center-1;
center=Math.floor((left+right)/2);
}else if (arr[center]<num) {
left=center+1;
center=Math.floor((left+right)/2);
}
}
}
var a=[1,2,3,4,5,6,7,8,9];
console.log(search(a,-2));
说明: 
1、基本思路:
每次比较,如果数组中间索引位置的值比要查找的值大,就转而在数组中间位置之前的子数组中查找;相反,如果数组中间索引位置的值比要查找的值大,就转而在数组中间位置之后的子数组中查找;如果数组中间索引位置的值恰好等于要查找的值,就返回该索引位置。

2、left定义查找范围的起始位置,right定义查找范围的结束位置,center定义查找范围的中间位置。

3、while中的逻辑说明:
(1)由于不知道具体查找查找多少次,while是比较好的选择;
(2)循环结束条件:
a、一旦当right小于0时,就不再查找,再纠缠也不会有结果。例如:在a=[1,2,3,4,5,6,7,8,9]中查找0,当查找范围变为left=0,right=0,center=0时,进入while语句,由于arr[center]>0,故执行
right=center-1;center=Math.floor((left+right)/2);
得到right=-1此时应不再进入循环;
b、一旦当left>l-1时,就不再查找,同样再纠缠也不会有结果。例如:在a=[1,2,3,4,5,6,7,8,9]中查找10,当查找范围变为left=8,right=8,center=8时,进入while语句,由于arr[center]<10,故执行
left=center;center=Math.floor((left+right)/2);
得到left=9,此时应不再进入循环;

4、始终是通过center匹配到要查找的值;

5、Math.floor处理了查找范围长度为偶数的情况;

6、当left==right了,而arr[center]==num却没执行,可以得出结论查找不到的;

7、当arr[center]==num时,整个函数都结束了,后面语句是不会执行的。

13.编写一个regex表达式以查找内容,内容以2个数字开头,以结尾

var reg = /​​1​​[0-9]$/

14.push 添加数组后, 是怎么响应的

push() 方法可向数组的末尾添加一个或多个元素,并返回新的长度

注释:该方法会改变数组的长度。

语法:

arrayObject.push(newelement1,newelement2,…,newelementX)

参数描述

newelement1 必需。要添加到数组的第一个元素。

newelement2 可选。要添加到数组的第二个元素。

newelementX 可选。可添加多个元素。

push() 方法可把它的参数顺序添加到 arrayObject 的尾部。它直接修改 arrayObject,而不是创建一个新的数组。push() 方法和 pop() 方法使用数组提供的先进后出栈的功能。

15.js基本数据类型

Undefined、Null、Bollean、Number、String

16.js中=的区别是什么

前者会自动转换类型

后者不会

17.for和for in 区别

语法结构上不同,

for 一般用来遍历数组的,是比较简单的操作

for in 一般用来遍历对象,虽然for in 也能遍历数组,但是会存在

以下几个问题:

1、index索引为字符串型数字,不能直接进行几何运算

2、遍历顺序有可能不是按照实际数组的内部顺序

3、使用for in会遍历数组所有的可枚举属性,包括原型。例如上栗

的原型方法method和name属性

这也是为什么用for不用for in的区别,如果是遍历普通数组的话,

用for是最好的选择,但是如果是对象,用for in就好了。

18.js中=的区别是什么

操作数1 == 操作数2,  操作数1 === 操作数2


双等号==:   
(1)如果两个值类型相同,再进行三个等号(===)的比较  
(2)如果两个值类型不同,也有可能相等,需根据以下规则进行类型转换在比较:   
1)如果一个是null,一个是undefined,那么相等    
2)如果一个是字符串,一个是数值,把字符串转换成数值之后再进行比较


三等号===:  
(1)如果类型不同,就一定不相等  
(2)如果两个都是数值,并且是同一个值,那么相等;如果其中至少一个是NaN,那么不相 等。(判断一个值是否是NaN,只能使用isNaN( ) 来判断)  
(3)如果两个都是字符串,每个位置的字符都一样,那么相等,否则不相等。  
(4)如果两个值都是true,或是false,那么相等  
(5)如果两个值都引用同一个对象或是函数,那么相等,否则不相等  
(6)如果两个值都是null,或是undefined,那么相等

19:for和for in 区别

for in:

1.for...in 语句用于对数组或者对象的属性进行循环操作。

2.for ... in 循环中的代码每执行一次,就会对数组的元素或者对象的属性进行一次操作。

3.for...in语句以任意顺序遍历一个对象的可枚举属性。对于每个不同的属性,语句都会被执行。

for :
1.for循环是对数组的元素进行循环,而不能引用于非数组对象。

20:数组去重的方法

第一种:

function uniq(array){
var temp = []; //一个新的临时数组
for(var i = 0; i < array.length; i++){
if(temp.indexOf(array[i]) == -1){
temp.push(array[i]);
}
}
return temp;
}

var aa = [1,2,2,4,9,6,7,5,2,3,5,6,5];
console.log(uniq(aa));
第二种:对象键值法去重

function uniq(array){
var temp = {}, r = [], len = array.length, val, type;
for (var i = 0; i < len; i++) {
val = array[i];
type = typeof val;
if (!temp[val]) {
temp[val] = [type];
r.push(val);
} else if (temp[val].indexOf(type) < 0) {
temp[val].push(type);
r.push(val);
}
}
return r;
}

var aa = [1,2,"2",4,9,"a","a",2,3,5,6,5];
console.log(uniq(aa));
第三种:排序后相邻去除法

function uniq(array){
array.sort();
var temp=[array[0]];
for(var i = 1; i < array.length; i++){
if( array[i] !== temp[temp.length-1]){
temp.push(array[i]);
}
}
return temp;
}

var aa = [1,2,"2",4,9,"a","a",2,3,5,6,5];
console.log(uniq(aa));
第四种:数组下标法

function uniq(array){
var temp = [];
for(var i = 0; i < array.length; i++) {
//如果当前数组的第i项在当前数组中第一次出现的位置是i,才存入数组;否则代表是重复的
if(array.indexOf(array[i]) == i){
temp.push(array[i])
}
}
return temp;
}

var aa = [1,2,"2",4,9,"a","a",2,3,5,6,5];
console.log(uniq(aa));
第五种:优化遍历数组法

function uniq(array){
var temp = [];
var index = [];
var l = array.length;
for(var i = 0; i < l; i++) {
for(var j = i + 1; j < l; j++){
if (array[i] === array[j]){
i++;
j = i;
}
}
temp.push(array[i]);
index.push(i);
}
console.log(index);
return temp;
}

var aa = [1,2,2,3,5,3,6,5];
console.log(uniq(aa));

21:排序的方法

第一种:冒泡排序:

var arr = [1,4,-8,-3,6,12,9,8];
function bubbleSort(arr){
for (var i = 0; i < arr.length; i++) {
for (var j = 0; j < arr.length-i-1; j++) {
if(arr[j] > arr[j+1]){
var c = arr[j];
arr[j] = arr[j+1];
arr[j+1] = c;
}
}
}
return arr;
}
console.log(bubbleSort(arr));
快速排序:

var arr = [1,4,-8,-3,6,12,9,8];
function quicksort(arr){
if(arr.length <= 1){
return arr;
}
var middleIndex = Math.floor(arr.length/2);
var middleNum = arr.splice(middleIndex,1);
var left = [], right = [];
for (var i = 0; i < arr.length; i++) {
if(arr[i] < middleNum){
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return quicksort(left).concat(middleNum, quicksort(right));
}
console.log(quicksort(arr));
选择排序:

var arr = [1,4,-8,-3,6,12,9,8];
function selectSort(arr){
for(var i=0;i<arr.length;i++){
//设置当前范围最小值和索引
var min = arr[i];
var minIndex = i;
//在该范围选出最小值
for(var j=i+1;j<arr.length;j++){
if(min>arr[j]){
min = arr[j];
minIndex = j;
}
}
//将最小值插入,并将原来位置的最小值删除
arr.splice(i,0,min);
arr.splice(minIndex+1,1);
}
return arr;
}
console.log(selectSort(arr));
插入排序:

var array = [1,4,-8,-3,6,12,9,8];
function selectSort(arr){
for(var i=0;i<arr.length;i++){
//设置当前范围最小值和索引
var min = arr[i];
var minIndex = i;
//在该范围选出最小值
for(var j=i+1;j<arr.length;j++){
if(min>arr[j]){
min = arr[j];
minIndex = j;
}
}
//将最小值插入,并将原来位置的最小值删除
arr.splice(i,0,min);
arr.splice(minIndex+1,1);
}
}
selectSort(array);
document.write(array);

22:冒泡排序

var arr = [1,4,-8,-3,6,12,9,8];
function bubbleSort(arr){
for (var i = 0; i < arr.length; i++) {
for (var j = 0; j < arr.length-i-1; j++) {
if(arr[j] > arr[j+1]){
var c = arr[j];
arr[j] = arr[j+1];
arr[j+1] = c;
}
}
}
return arr;
}
console.log(bubbleSort(arr));

23:原型链的理解:

在谈原型链之前,我们首先要了解自定义函数与 Function 之间是什么关系,而构造函数、原型和实例之间又存在什么千丝万缕的关系呢?其实,所有的函数都是 Function 的实例。在构造函数上都有一个原型属性 prototype,该属性也是一个对象;那么在原型对象上有一个 constructor 属性,该属性指向的就是构造函数;而实例对象上有一个 _proto_  属性,该属性也指向原型对象,并且该属性不是标准属性,不可以用在编程中,该属性用于浏览器内部使用。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QqhVpWo7-1630416218424)(en-resource://database/450:1)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8tp8hnPS-1630416218430)(en-resource://database/452:1)]

1、原型链

1)构造函数、原型和实例的关系
①构造函数都有一个属性prototype,这个属性是一个对象(Object的实例)
②原型对象prototype里面有一个constructor属性,该属性指向原型对象所属的构造函数
③实例对象都有一个_proto_属性,该属性也指向构造函数的原型对象,它是一个非标准属性,
不可以用于编程,它是用于浏览器自己使用的

2)prototype与_proto_的关系
①prototype是构造函数的属性
②_proto_是实例对象的属性 ——这两者都指向同一个对象
【总结】
i)函数也是对象,对象不一定是函数;
ii)对象的本质:无序的键值对集合;键值对当中的值可以是任意数据类型的值
iii)对象就是一个容器,这个容器当中放的是(属性和方法)

3)属性搜索   
①在访问对象的某个成员的时候会先在对象中找是否存在   
②如果当前对象中没有就在构造函数的原型对象中找   
③如果原型对象中没有找到就到原型对象的原型上找   
④知道Object的原型对象的原型是null为止


2、Function——
所有函数都是Function的实例
`①本地对象:独立于宿主环境(浏览器)的对象——包括Object、Array、Date、RegExp、 Function、Error、Number、String、Boolean
②内置对象——包括Math、Global(window,在js中就是全局变量),使用的时候不需要 new
③宿主对象——包括自定义对象、DOM、BOM

24:改变this指向的方法

第一种: new关键字改变this指向

function Fn(){
this.user = "追梦子";
}
var a = new Fn();
console.log(a.user); //追梦子


用变量a创建了一个Fn的实例(相当于复制了一份Fn到对象a里面),此时仅仅只是创建,并没有执行,而调用这个函数Fn的是对象a,那么this指向的自然是对象a,那么为什么对象a中会有user,因为你已经复制了一份Fn函数到对象a中,用了new关键字就等同于复制了一份第二种: call()
第二种: call()

var a = {
user:"追梦子",
fn:function(){
console.log(this.user); //追梦子
}
}
var b = a.fn;
b.call(a); //若不用call,则b()执行后this指的是Window对象


把b添加到第一个参数的环境中,简单来说,this就会指向那个对象。
第三种:apply()

var a = {
user:"追梦子",
fn:function(){
console.log(this.user); //追梦子
}
}
var b = a.fn;
b.apply(a);
第四种:bind()

var a = {
user:"追梦子",
fn:function(){
console.log(this.user);
}
}
var b = a.fn;
b.bind(a); //代码没有被打印


我们发现代码没有被打印,对,这就是bind和call、apply方法的不同,实际上bind方法返回的是一个修改过后的函数。

25:es6新特性

1. 变量声明
let const:
可以把let看成var,只是它定义的变量被限定在了特定范围内才能使用,而离开这个范围则无效。const则很直观,用来定义常量,即无法被更改值的变量。

for (let i=0;i<2;i++)console.log(i);//输出: 0,1
console.log(i);//输出:undefined,严格模式下会报错
2.类的支持

ES6中添加了对类的支持,引入了class关键字(其实class在JavaScript中一直是保留字,目的就是考虑到可能在以后的新版本中会用到,现在终于派上用场了)。JS本身就是面向对象的,ES6中提供的类实际上只是JS原型模式的包装。现在提供原生的class支持后,对象的创建,继承更加直观了,并且父类方法的调用,实例化,静态方法和构造函数等概念都更加形象化。


//类的定义
class Animal {
//ES6中新型构造器
constructor(name) {
this.name = name;
}
//实例方法
sayName() {
console.log('My name is '+this.name);
}
}
//类的继承
class Programmer extends Animal {
constructor(name) {
//直接调用父类构造器进行初始化
super(name);
}
program() {
console.log("I'm coding...");
}
}
//测试我们的类
var animal=new Animal('dummy'),
wayou=new Programmer('wayou');
animal.sayName();//输出 ‘My name is dummy’
wayou.sayName();//输出 ‘My name is wayou’
wayou.program();//输出 ‘I'm coding...’
3.字符串模板
字符串模板相对简单易懂些。ES6中允许使用反引号 ` 来创建字符串,此种方法创建的字符串里面可以包含由美元符号加花括号包裹的变量${vraible}。如果你使用过像C#等后端强类型语言的话,对此功能应该不会陌生。
//产生一个随机数
var num=Math.random();
//将这个数字输出到console
console.log(`your num is ${num}`);
4.解构:
自动解析数组或对象中的值。比如若一个函数要返回多个值,常规的做法是返回一个对象,将每个值做为这个对象的属性返回。但在ES6中,利用解构这一特性,可以直接返回一个数组,然后数组中的值会自动被解析到对应接收该值的变量中。

var [x,y]=getVal(),//函数返回值的解构
[name,,age]=['wayou','male','secrect'];//数组解构

function getVal() {
return [ 1, 2 ];
}

console.log('x:'+x+', y:'+y);//输出:x:1, y:2
console.log('name:'+name+', age:'+age);//输出: name:wayou, age:secrect
5.Promise:

Promises是处理异步操作的一种模式,之前在很多三方库中有实现,比如jQuery的deferred 对象。当你发起一个异步请求,并绑定了.when(), .done()等事件处理程序时,其实就是在应用promise模式。
//创建promise
var promise = new Promise(function(resolve, reject) {
// 进行一些异步或耗时操作
if ( /*如果成功 */ ) {
resolve("Stuff worked!");
} else {
reject(Error("It broke"));
}
});
//绑定处理程序
promise.then(function(result) {
//promise成功的话会执行这里
console.log(result); // "Stuff worked!"
}, function(err) {
//promise失败会执行这里
console.log(err); // Error: "It broke"
});

26.promise的理解

ES6提供的解决异步处理方法

有两个优点

1.promise对象的状态不受外界影响

-pending 初始状态

-fulfilled 成功状态

-rejected 失败状态

2.promise的状态一旦改变,就不会再变,状态不可逆,只能由pending变成pending变成fulfilled或者由pending变成rejected

三个缺点

1.无法取消promise,一旦新建它就会立即执行,无法中途取消

2.如果不设置回调函数,promise内部抛出的错误,不会反映到外部

3.当处于pending状态时,无法得知目前进展到哪一个阶段

用法

const promise = new Promise(function(resolve, reject) {
// ... some code

if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});

27.同源策略

同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。同源指的是协议,域名和端口号均相同则属于同源

28.前端跨域的方式

使用jsonp跨域,因为script标签引入的js是不受同源策略的限制,通过script标签引入一个js或者是一个其他后缀形式(如php,jsp等)的文件,此时文件返回一个JS函数的调用

通过cors跨域,实现cors通信的关键是服务器,只要服务器实现cors接口,就可以跨域

<script type="text/javascript">
var xhr = new XMLHttpRequest();
xhr.open("GET", "",true);
xhr.send();
</script>

反向代理跨域,反向代理指的是在前端的服务器环境中, 短暂的开启一个后端服务器, 由后端服务器进行数据请求, 然后在将结果返回给前端

29.AMD,CMD模块化

模块化的开发方式可以提高代码复用率,方便进行代码的管理。通常一个文件就是一个模块,有自己的作用域,只向外暴露特定的变量和函数。目前流行的js模块化规范有CommonJS、AMD、CMD以及ES6的模块系统

1、AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块

2、CMD推崇就近依赖,只有在用到某个模块的时候再去require

这种区别各有优劣,只是语法上的差距,而且requireJS和SeaJS都支持对方的写法

AMD和CMD最大的区别是对依赖模块的执行时机处理不同,注意不是加载的时机或者方式不同

30.报表绘图类的框架

highcharts ​​ http://www.highcharts.com/​​

jscharts ​​http://www.jscharts.com/​​

AdminLTE http://adminlte.la998.com/

31.库和插件的源代码

库和框架都是一种有别于软件、面向程序开发者的产品形式。

库是将代码集合成的一个产品,供程序员调用。面向对象的代码组织形式而成的库也叫
类库。
框架则是为解决一个(一类)问题而开发的产品,框架用户一般只需要使用框架提供的类
或函数,即可实现全部功能。

32.函数柯里化是什么 ?

柯里化(英语:Currying),又称为部分求值,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回一个新的函数的技术,新函数接受余下参数并返回运算结果。
具体内容见

33.哪些情况下会造成内存泄漏 ?

1)意外的全局变量引起的内存泄露
```javascript
function leak () {
leak="xxx"; //leak成为一个全局变量,不会被回收 相当于 window.leak = 'XXX'
}
```
2. 闭包可以维持函数内局部变量,使其得不到释放。
3. 没有清理的DOM元素引用
4. 被遗忘的定时器或者回调

34.性能优化 ?

1. 定义局部变量.查找局部变量比全局变量要快。
2. 不滥用闭包。
3. 合并js文件,减少http请求
4. 避免使用for-in循环
5. 尽量不用with,eval语句,try-catch的catch子句要谨慎使用

35.工作中闭包的使用案例?使用过什么闭包工具库嘛

1. 闭包经典使用场景一:通过循环给页面上多个dom节点绑定事件
```javascript
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<button>Button0</button>
<button>Button1</button>
<button>Button2</button>
<button>Button3</button>
<button>Button4</button>
</body>
</html>

for(var i = 0, len = btns.length; i < len; i++) {
(function(i) {
btns[i].onclick = function() {
alert(i);
}
}(i))
}
```

2. 封装变量 闭包可以将一些不希望暴露在全局的变量封装成“私有变量”。
3. 闭包使用场景三:延续局部变量的寿命

闭包工具库:???

36.301 302如何重定向 ?

301 redirect: 301 代表永久性转移(Permanently Moved)
302 redirect: 302 代表暂时性转移(Temporarily Moved )
详细来说,301和302状态码都表示重定向,就是说浏览器在拿到服务器返回的这个状态码后会自动跳转到一个新的URL地址,这个地址可以从响应的Location首部中获取(用户看到的效果就是他输入的地址A瞬间变成了另一个地址B)——这是它们的共同点。他们的不同在于。301表示旧地址A的资源已经被永久地移除(这个资源不可访问了),搜索引擎在抓取新内容的同时也将旧的网址交换为重定向之后的网址;302表示旧地址A的资源还在(仍然可以访问),这个重定向只是临时地从旧地址A跳转到地址B,搜索引擎会抓取新的内容而保存旧的网址。

37.暂时性死区 ?

举例:
```javascript
console.log (a) //由于变量提升,输出undefined
var a
```

```javascript
console.log(a) //报错 ReferenceError: a is not defined
let a
```
ES6规定,let/const 命令会使区块形成封闭的作用域。若在声明之前使用变量,就会报错。
总之,在代码块内,使用 let 命令声明变量之前,该变量都是不可用的。
这在语法上,称为 “暂时性死区”( temporal dead zone,简称 TDZ)。

38.堆和栈

  • 栈(stack):为自动分配的内存空间,他由系统自动释放。存放在栈内存中的简单数据段,数据大小确定,内存空间大小可以分配,是直接按值存放的,所以可以直接访问。基本的数据类型放在栈中
  • 堆(heap):则是动态分配的内存,大小不定也不会自动释放。引用类型(object)是存放在堆内存中的,变量实际上是一个存放在栈内存的指针,这个指针指向堆内存中的地址。每个空间大小不一样,要根据情况进行特定的分配。

39.箭头函数的this指向

  • 箭头函数默认不会使用自己的this,而是会和外层的this保持一致,最外层的this就是window对象。在多层对像嵌套里箭头函数里this是和最最外层保持一致的

40.深拷贝和浅拷贝的区别

  • 浅拷贝:只复制指向某个对象的指针,而不复制对象本身,新旧对象共享一块内存
  • 深拷贝:复制并创建一个一模一样的对象,不共享内存,修改新对象,旧对象保持不变。

41.手写一个promise,采用es5的方面。promise的源码理解

var fn=function(resolve, reject){
console.log('begin to execute!');
var number=Math.random();
if(number<=0.5){
resolve('less than 0.5');
}else{
reject('greater than 0.5');
}
}

var p=new Promise(fn);
p.then(function(data){
console.log('resolve: ', data);
}, function(data){
console.log('reject: ', data);
})

对promise源码的理解:
当我们运行 var p=new Promise(fn) 这条语句的时候,fn函数就已经执行了,然而,p.then这个方法是在后面才定义了resolve和reject,那么为何fn函数能够知道resolve和reject函数是什么呢?换句话说,resolve和reject函数是如何回到过去,出现在先执行的fn函数当中的呢?要解决这个问题,主要运用的就是setTimeout这个方法,来延迟fn当中resolve和reject的执行。我们知道js是单线程+消息队列,必须等主线程代码执行完毕才能开始执行消息队列当中的代码。因此,会首先执行then这个方法,给里面两个参数赋值。
加入状态:pending, resolved, rejected
在Promise规范当中,规定Promise只能从初始pending状态变到resolved或者rejected状态,是单向变化的,也就是说执行了resolve就不会再执行reject,反之亦然。并在必要的地方进行判断,防止重复执行。
function MyPromise(fn) {
this.value;
this.status = 'pending';
this.resolveFunc = function() {};
this.rejectFunc = function() {};
fn(this.resolve.bind(this), this.reject.bind(this));
}

MyPromise.prototype.resolve = function(val) {
var self = this;
if (this.status == 'pending') { //判断状态
this.status = 'resolved';
this.value=val;
setTimeout(function() {
self.resolveFunc(self.value);
}, 0);
}
}

MyPromise.prototype.reject = function(val) { //判断状态
var self = this;
if (this.status == 'pending') {
this.status = 'rejected';
this.value=val;
setTimeout(function() {
self.rejectFunc(self.value);
}, 0);
}
}

MyPromise.prototype.then = function(resolveFunc, rejectFunc) {
this.resolveFunc = resolveFunc;
this.rejectFunc = rejectFunc;
}

链式调用:
要实现链式调用,then方法的返回值也必须是一个Promise对象,这样才能再次在后面调用then。

42.async与promise的区别:

  • 在函数前有一个关键字​​async​​​,​​await​​​关键字只能在使用​​async​​​定义的函数中使用。任何一个​​async​​​函数都会隐式返回一个​​promise​​,并且promise resolve 的值就是 return 返回的值
  • 不能在函数开头使用​​await​
  • Async 函数的实现最简洁,最符合语义,几乎没有语义不相关的代码。
  • Promise 的写法比回调函数的写法大大改进,但是一眼看上去,代码完全都是 Promise 的 API(​​then​​​、​​catch​​等等),操作本身的语义反而不容易看出来。

43.async是promise暴露出来的语法糖?

  • async函数就是generator函数的语法糖

44.箭头函数与function的区别?

(1) 箭头函数与function定义函数的写法:

//function
function fn(a, b){
return a + b;
}
//arrow function
var foo = (a, b)=>{ return a + b };

(2) this的指向:

使用function定义的函数,this的指向随着调用环境的变化而变化的,而箭头函数中的this指向是固定不变的,一直指向的是定义函数的环境。

(3) 构造函数

function是可以定义构造函数的,而箭头函数是不行的。

(4) 变量提升

由于js的内存机制,function的级别最高,而用箭头函数定义函数的时候,需要var(let const定义的时候更不必说)关键词,而var所定义的变量不能得到变量提升,故箭头函数一定要定义于调用之前!

45.箭头函数中没有this对象,this是最近的this

(1) 由于箭头函数不绑定this, 它会捕获其所在(即定义的位置)上下文的this值, 作为自己的this值

(2)方法的箭头函数this指向全局window对象,而普通函数则指向调用它的对象,箭头函数没有this

46.箭头函数可以作为构造函数吗?

因为箭头函数没有自己的this,它的this其实是继承了外层执行环境中的this,且this指向永远不会随在哪里调用、被谁调用而改变,所以箭头函数不能作为构造函数使用,或者说构造函数不能定义成箭头函数,否则用new调用时会报错!

47.箭头函数替代arguments的方法?

箭头函数没有自己的arguments对象。在箭头函数中访问arguments实际上获得的是外层局部(函数)执行环境中的值。可以在箭头函数中使用rest参数代替arguments对象,来访问箭头函数的参数列表

48.解构和负载的场景?

  1. 解构赋值,即对某种结构进行解析,然后将解析出来的值赋值给相关的变量,常见的有数组、对象、字符串的解构赋值等

数组解构

只要等号两边的模式相同,左边的变量就会被赋予对应的值。

let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3
  • 对象结构
    对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"
  • 字符串结构
    字符串被转换成了一个类似数组的对象
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
  1. 负载

49.扩展运算符了解?

  • 概念
    扩展运算符(spread)是三个点(…)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。扩展运算符与正常的函数参数可以结合使用,后面也可以放置表达式,但如果后面是一个空数组,则不产生任何效果。
let arr = [];
arr.push(...[1,2,3,4,5]);
console.log(arr); //[1,2,3,4,5]
console.log(1, ...[2, 3, 4], 5) //1 2 3 4 5
console.log(...(1 > 0 ? ['a'] : [])); //a
console.log([...[], 1]); //[1]
  • 应用

1 替代函数的apply方法

由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了。

// ES5 的写法 
Math.max.apply(null, [14, 3, 77])
// ES6 的写法
Math.max(...[14, 3, 77])

2 复制数组

// ES5 的写法 
const a1 = [1, 2];
const a2 = a1.concat();
// ES6 的写法
const a1 = [1, 2];
onst a2 = [...a1]; //或 const [...a2] = a1;

3 合并数组

// ES5 的写法 
[1, 2].concat(more);
arr1.concat(arr2, arr3);
// ES6 的写法
[1, 2, ...more];
[...arr1, ...arr2, ...arr3]

4 与结构赋值结合

// ES5 的写法 
a = list[0], rest = list.slice(1)
// ES6 的写法
[a, ...rest] = list

50.深拷贝的方法?

答:对数组进行深拷贝:

  • for循环
  • slice方法
  • concat方法
  • ES6扩展运算符
    对对象进行深拷贝:
  • for循环
  • 先转换成json在转换成对象
  • ES6扩展运算符

51.扩展运算符的方法进行拷贝,是深拷贝还是浅拷贝?

答:深拷贝

52.set的用法:数组去重。

答:有两种方法:

方法一: Set + Array.from()

var set1 = Array.from(new Set([1,1,2,2,33,'33',44,'44'
]))

方法二: …[拓展运算符] + Set

var tt  = [...new Set([5,5,6,6,8,])] // 5,6,8

53.es6判断是否是数组:isArray,以及其他判断数组的方法?typeof 检测数组返回值

答:

判断数组方法:

  • 1.用instanceof判断
    使用instanceof运算符可以分辨数组和对象,可以判断数组是数组。
  • 2.用constructor判断
    实例化的数组拥有一个constructor属性,这个属性指向生成这个数组的方法。
    当constructor属性被修改之后,就无法用这个方法判断数组是数组了
  • 3.用Object的toString方法判断
    toString方法将会返回"[object type]",其中的type代表的是对象的类型,根据type的值,我们就可以判断这个疑似数组的对象到底是不是数组了
  • 4.用Array对象的isArray方法判断
    isArray方法返回true,当参数不为数组的时候,isArray方法返回false
typeof 检测数组返回值

typeof是javascript原生提供的判断数据类型的运算符,它会返回一个表示参数的数据类型的字符串

54.数组去重的方法?

答:

  • 利用ES6中的Set
  • 利用for嵌套for,然后splice去重(ES5中最常用)
  • 利用indexOf去重
  • 利用sort()
  • 利用includes
  • 利用hasOwnProperty
  • 利用filter
  • 利用递归去重
  • 利用Map数据结构去重
  • 利用reduce+includes

55.数组中出现重复数字的重复统计?

思路:

//统计一个数组中有多少个不重复的单词:

// 不用reduce时:
var arr = ["apple","orange","apple","orange","pear","orange"];
function getWordCnt(){
var obj = {};
for(var i= 0, l = arr.length; i< l; i++){
var item = arr[i];
obj[item] = (obj[item] +1 ) || 1;
}
return obj;
}
console.log(getWordCnt());//{apple: 2, orange: 3, pear: 1}

// 用reduce时:
var arr = ["apple","orange","apple","orange","pear","orange"];
function getWordCnt(){
return arr.reduce(function(prev,next){
prev[next] = (prev[next] + 1) || 1;
return prev;
},{});
}
console.log(getWordCnt());//{apple: 2, orange: 3, pear: 1}

56.数组常见的api

  • 数组api又叫应用程序接口,函数方法
  • 常见api
  1. arr.push():数据添加 - 在数组尾部添加元素
    push方法一次可添加单个或多个元素到数组末端,也可以添加数组
    ​​​数组名.push("元素1","元素2",...)​
  2. arr.pop():数据更新 - 删除数组的最后一个元素
    pop方法的作用是移除数组末尾的一个元素。把数组长度减1,并且返回它的删除值,如果数组为空,则pop()不改变数组,返回undefind
    ​​​数组名.pop()​
  3. arr.concat():连接两个或更多的数组,并返回结果。
  4. arr.join():字符连接
    若不想要任何连接符,则括号中用空字符即可。
    ​​​数组名.join("连接符")​
  5. arr.reverse():颠倒数组中元素的顺序。
  6. arr.shift():删除数据 - 移除数组顶端的元素
    shift方法与pop相反,移除数组的第一个元素并将其返回。该方法执行后,数组剩下的元素向前移动,下标索引号重新调整从0开始。
    ​​​数组名.shift()​
  7. arr.unshift():添加数据 - 在数组头部添加元素
    nshift方法与push方法正好相反,是将元素插入数组的首部。一次可以插入单个或多个元素,所有元素按顺序插入,操作完成后返回新数组的引用
    ​​​数组名.unshift("元素1","元素2",...)​
  8. arr.slice():生成特定数据 - 获取数组中的一部分元素
    slice方法的作用是抽取数组的一段元素,抽取指定下标索引区间中的元素作为新数组返回
    ​​​数组名.slice(start,end)​​注:splice是直接修改原数组,而slice不会修改原数组。
  9. arr.sort():对数组的元素进行排序
  10. arr.splice():更新移动数据 - 删除、替换或插入数组元素
    splice方法的作用是,从一个数组中移除一个或多个元素。剩下的元素组成一个数组,移除的元素组成另一个数组并返回它的引用。同时,原数组可以在移除的开始位置处顺带插入一个或多个新元素,达到修改替换数组元素的目的。这个操作效果通常称为接合
    ​​​数组名.splice(start,deleteCount,item1,item2,...)​​​参数说明:
    start:必选项,表示从数组中剪切的起始位置下标索引号。
    deteleCount:必选项,表示从数组中切取的元素个数。
    item:可选项,表示切取时插入原数组切入点开始出的一个或多个元素。
  11. arr.indexof():查找元素对应下标
  12. arr.forEach():数组遍历
  13. arr.filter():数组遍历,将返回的值新建成一个新数组
  14. arr.map():遍历数组,直接操作数组,返回一个新数组
  15. arr.reduce():数组归并

57.数组中sort的用法。sort的返回值?没有传比较函数的比较?

用法:对数组的元素进行排序

arr.sort()直接操作原有数组,返回原有数组,当没有传比较函数的话不会按大小排序,而是按顺序排序

var arr = [22,12,3,43,56,47,4];
arr.sort();
console.log(arr); // [12, 22, 3, 4, 43, 47, 56]
arr.sort(function (m, n) {
if (m < n) return -1
else if (m > n) return 1
else return 0
});
console.log(arr); // [3, 4, 12, 22, 43, 47, 56]

58.canvas与webGL的区别

- 概要
Canvas 位图,是需要自己画点的白板;
WebGL 3D位图,是基于 Canvas 的 3D 框架。
- 用途
Canvas 适用于位图,高数据量高绘制频率(帧率)的场景,如动画、游戏;
WebGL 主要用来做 3D 展示、动画、游戏。
- canvas缺点:

- 只能绘制2D图像,暂时不支持3D图像。
-canvas绘制图形出并非可以直接操作的dom对象。如果要对其进行类似dom的操作,例如添加属性等等,比较麻烦(这就是为什么必须使用类库)。
- canvas优点:

- 由于canvas绘图不会给每个点生成对象,所以绘制速度快,消耗内存少。(这点主要是相对于SVG,VML技术而言)
- 兼容性较好。除了IE6,其他浏览器都可以支持。(IE7,8需要载入扩展JS,终究还是能用的)
- webGL
是一项使用JavaScript实现3D绘图的技术,浏览器无需插件支持,Web开发者直接使用js调用相关API就能借助系统显卡(GPU)进行编写代码从而呈现3D场景和对象。

59.BOM对象与DOM对象的区别?实现由BOM对象还是先有DOM对象

BOM:浏览器对象模型(Brower Object Model),是用于操作浏览器而出现的API,BOM对象则是Javascript对BOM接口的实现。
BOM提供了独立于内容的、可以与浏览器窗口进行交互的对象结构。通过BOM对象可以访问浏览器功能部件和属性。
BOM中代表浏览器窗口的window对象是Javascript顶层对象,其他BOM对象均为window对象的子对象。被作为window对象的属性来引用。
其他BOM对象都是在window对象中进行操作。

DOM 是文档对象模型,比如 html 是树结构的,操作 dom 就是操作这颗树:
DOM:文档对象模型(Document Object Model),是W3C定义的一套用于处理HTML和XML文档内容的标准编程接口API。javascript实现DOM接口的对象对应的是document对象,JS通过该对象来对HTML/XML文档进行增删改查。DOM定义了HTML和XML的逻辑结构,将整个页面划分成由层次节点构成的文档,以树的形式来展现,如上面那张图所示。

在BOM和DOM结构层次图中,document对象属于window对象,所以DOM也可以看作是BOM的一部分
  1. 0-9​​↩︎​​


举报

相关推荐

0 条评论