JS 中级前端面试题

1.闭包:内部函数引用外部函数的变量(高频)

  • 作用:延长它的生命周期
  • 缺点:
    • 容易导致内存泄漏
    • 更多的内存消耗
    • 会造成内存泄露,因为闭包中引用到的函数中定义的变量都永远不会被释放  解决:闭包在不使用的时候,及时释放。将引用内层函数对象的变量赋值为null。

优点:局部变量:仅函数内可用不会被污染 

function a(){
    var n = 0;
    function add(){
       n++;
       console.log(n);
    }
    return add;
}
var a1 = a(); //注意,函数名只是一个标识(指向函数的指针),而()才是执行函数;
a1();    //1
a1();    //2  第二次调用n变量还在内存中
var f=(function fn(){
    var name=1;
    return function(){
        name++;
        console.log(name);
    }
})();

function init(){
   var name="zhangsan";    // 创建局部变量name和局部函数alertName
   function alertName(){   //alertName()是函数内部方法,是一个闭包
        console.log(name)  //使用了外部函数声明的变量,内部函数可以访问外部函数的变量
    }
   alertName()
}
init()

函数内部嵌套了一个新的函数,嵌套的函数对外部的函数造成了引用就形成了闭包???

2.JavaScript this、闭包、作用域(高频)
  • this:this是js执行时生成的一个对象, 可能会有不同的值(this的值是不确定的),this的值是取决于谁调用他 
  • 作用域有全局作用域,局部作用域 ,eval作用域,其实你写代码能体会到这种,双层循环时,最里面往外访问,但是外面不能访问里面,里面找变量一层一层往外找。内部函数可以访问外部函数就形成了作用域链,但外部没办法访问函数内部
  • 闭包是能访问里面作用域的函数。

3.eval是做什么的?

eval 的功能是把对应的字符串解析成 JS 代码并运行

  • 应该避免使用 eval,不安全,非常耗性能(先解析成 js 语句,再执行)
  • 由 JSON 字符串转换为 JSON 对象的时候可以用 eval('('+ str +')');

示例:

语法:eval(string)


输出
200
4
27

4.谈谈This对象的理解。 

 this的五种情况:

1.直接调用函数指向window
function MyObject(){
    console.log(this);
}
MyObject();//window

2.对象函数调用,用对象点的情况,指向这个对象
 var test = {
        a:0,
        b: {
            c:7,
            fn:function(){
                alert(this.c);//7
            }
        }
    };
    window.test.b.fn();

3.new对象时候this指向这个对象
function f2(){
    this.x = 1;
    return this;
}
var a = new f2();
console.log(a.x);//1

4.箭头函数指向上下文
全局范围:指向全局对象(浏览器下指window)
全局函数:指向全局对象。
对象函数:调用某个对象的函数,指向当前对象。
new实例化对象时:指向新创建的对象。

JavaScript 秘密花园

 https://www.zhihu.com/question/19636194/answer/25081397

5.JS的作用域链是什么及其作用

一般情况下,变量取值到创建这个变量的函数的作用域中取值。但是如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链

JS中的作用域链主要用于解析变量的值。 如果没有这个,在不同的作用域内定义了许多变量,JS很难为变量选择某个值。

6.函数(function)和方法(method)(高频)

区别:

本质上是一样的,方法是函数的特例,将函数赋值给了对象
函数:可以实现一定功能的一段代码的封装

  • 注意:函数创建后,在内存堆中以一段字符串文本存储,不执行时不会调用,就单单是存了一段字符串。
function f() {
 console.log('我是函数');   //调用函数 f();
} 

方法:把函数定义到对象里面就称为方法

var obj = {
 function f(){ 
console.log("我是方法"); 
} 
}; 
//对象调用方法 (对象调用通过点对象的属性名) obj.f();

7.简述创建函数的几种方式(高频)

// 第一种字面量(函数声明):
    语法:关键字  标识符   小括号  {函数体}
function sum1(num1,num2){
   return num1+num2;
}

// 第二种 表达式(匿名函数表达式):
    语法: 关键字  标识符 = function() 函数体
var sum2 = function(num1,num2){
   return num1+num2;
}

const square = function(number) { return number * number; };
var x = square(4); // x gets the value 16
// 注意:匿名函数:没有函数名称,无法通过函数名称调用。function(){}:只能自己执行自己


// 第三种(Function构造函数方式):
语法:   关键字   标识符  = new Function()
var sum3 = new Function("num1","num2","return num1+num2");

注:内存栈:sum3:函数的引用

          内存堆:"num1","num2","return num1+num2",就是一个字符串

注意:函数提升仅适用于函数声明,而不适用于函数表达式。

函数的三要素 :

  • 函数名,参数,返回值(return)
  • 返回值:每个函数都必须有return,如果没有return语句,计算机会自动在函数的最后一行补一个return,返回值为undefined。
// 把函数当作参数传递给另一个函数
function map(f,a){
    let result = [];
    for(let i = 0; i !== a.length; i++){
        result[i] = f(a[i]);
    }
    return result;
}

const f = funtion(x){
    return x*x*x;
}

let numbers = [0,1,2,5,10];
let cube = map(f,numbers);
console.log(cube);   //  [0, 1, 8, 125, 1000]

Function 构造函数

Function 构造函数与函数声明之间的不同

// 由 Function 构造函数创建的函数不会创建当前环境的闭包,它们总是被创建于全局环境,因此在运行时它们只能访问全局变量和自己的局部变量,不能访问它们被 Function 构造函数创建时所在的作用域的变量。这一点与使用 eval() 执行创建函数的代码不同。
var x = 10;

function createFunction1() {
    var x = 20;
    return new Function('return x;'); // 这里的 x 指向最上面全局作用域内的 x
}

function createFunction2() {
    var x = 20;
    function f() {
        return x; // 这里的 x 指向上方本地作用域内的 x
    }
    return f;
}

var f1 = createFunction1();
console.log(f1());          // 10
var f2 = createFunction2();
console.log(f2());          // 20

8.什么是函数的封装?

  • 把实现相同功能的代码放到一个函数体中,当想实现这个功能时,直接执行这个函数即可;减少了的冗余;
  • 高内聚,低耦合.

9.函数的执行过程(重要)

在函数执行时,函数体中可以找到函数外面的变量;但是函数外面不能访问里面的变量;

  1. 方法在对象里,方法也是函数
  2. 函数可以随意调用
  3. 首先会形成一个私有的作用域
  4. 形参赋值
  5. 变量提升
  6. 代码从上到下运行;
  7. 作用域的销毁; 

10.构造函数与普通函数的区别

  1. 构造函数的的方法名首字母要大写,普通函数不需要。
  2. 构造函数的调用方法为: new  函数名();普通函数:函数名()。例子:new Show();show()
  3. 构造函数的类名和方法名一样;
  4. 构造函数要用this构造属性和方法;
// 定义构造函数
function Show(){
this.name = "张三"
this.age = "12"
}
var show = new Show()  // 调用构造函数
alert(show.name)    // 张三

// 普通函数
function show (name,age){
        this.name = 'zhangsan';
        this.age = 12;
}
alert(show(this.name))   //zhangsanm

12.介绍箭头函数和普通函数的区别   

箭头函数与普通函数的区别详解_小弦的博客的博客-CSDN博客_箭头函数与普通函数有哪些区别

相同:箭头函数和普通函数,都是封装了多行代码实现了一定功能的代码块,都可以提高代码的复用性

不同点:

  • 箭头函数内部的 this 指向 window 或指向外部函数的 this
  • 箭头函数不能和普通函数一样作为构造函数使用
  • 箭头函数比普通函数更加简洁 如果没有参数,就直接写一个空括号即可 如果只有一个参数,可以省去参数括号 如果有多个参数,用逗号分割 如果函数体的返回值只有一句,可以省略大括号 如果函数体不需要返回值,且只有一句话,可以给这个语句前面加一个void关键字。最常用的就是调用一个函数: let fn = () => void doesNotReturn()

  • 箭头函数没有自己的this 箭头函数不会创建自己的this,所以它没有自己的this,它只会在自己作用域的上一层继承this。所以箭头函数中的this的指向在它在定义时一家确定了,之后不会改变。

  • 箭头函数继承来的this指向永远不会改变

  • call()、apply()、bind()等方法不能改变箭头函数中的this指向

  • 箭头函数不能作为构造函数使用

  • 箭头函数没有自己的arguments

  • 箭头函数没有prototype

  • 箭头函数不能用作Generator函数,不能使用yeild关键字


 
注意:关于箭头函数内的 this ,某些资料会提到箭头函数没有 this 指
向(如果真的没有 this 指向,内部无法访问 this 引用);但是如果箭头函
数包含在其他有 this 指向的函数内部,箭头函数内的 this 就是外部函数的
this

练习题


    

13.new 对象时发生了什么?

  • 初始化一个空对象
  • 将新对象内部的 __proto__ 赋值为构造函数的 prototype 属性。
  • 将构造函数内部的 this 被赋值为新对象(即 this 指向新对象)。
  • 执行构造函数内部的代码(给新对象添加属性)。
  • 如果构造函数返回非空对象,则返回该对象。否则返回 this。(不需要return来返回)
function fakeNew() {
  // 创建新对象
  var obj = Object.create(null);
  var Constructor = [].shift.call(arguments);
  // 将对象的 __proto__ 赋值为构造函数的 prototype 属性
  obj.__proto__ = Constructor.prototype;
  // 将构造函数内部的 this 赋值为新对象
  var ret = Constructor.apply(obj, arguments);
  // 返回新对象
  return typeof ret === "object" && ret !== null ? ret : obj;
}

function Group(name, member) {
  this.name = name;
  this.member = member;
}

var group = fakeNew(Group, "hzfe", 17);

完全搞懂js 中的new()到底做了什么?_张木期的博客-CSDN博客_js new

JS 中级前端面试题_第1张图片

1、创建一个空对象、并且this变量引用该函数、同时还继承了该函数的原型
2、属性和方法被添加到this引入的对象中
3、新创建的对象由this说引用、并且最后隐式的返回this
var obj = {};
obj.__proto__ = Base.prototype;
Base.call(obj);

14.创建对象的方式有几种?(高频)

// 1.对象字面量(最常用的方式)
// 对象字面量创建了一个对象o1
var o1 = {
    p:"hello world",
    alertP:function(){
        alert(this.p);
    }
}
缺点 :每创建一个新的对象都需要写出完整的定义语句,不便于创建大量相同类型的对象,不利于使用继承等高级特性。

// 2.用new构造对象
function CO(){
    this.p = “I’m in constructed object”;
    this.alertP = function(){
        alert(this.p);
    }
}
var o2 = new CO();

深入理解js构造函数 - 奔跑的蜗牛~ - 博客园

15.call,apply,bind有什么作用?区别是什么?(高频)

this指向有哪些?  call,apply,bind

call和apply

  • 相同点:改变函数中this的指向
  • 不同点: call和apply一样 只是传参数不一样 apply参数是以数组形式传的  bind和call传参方式一样 但是bind会立即执行

建议使用es6的箭头函数:这样就解决了this的指向问题

// 无参数调用:
function fn(){
    alert(this.name);
}
var p1={name:1};
fn.call(p1);
fn.apply(p1);


// 有参数调用:
function fn2(name,age){
    this.name=name;
    this.age=age;
}
var p1={};
fn2.call(p1,"张三",20);
fn2.apply(p1,["张三",20]);

16.形参(parameter)和实参(argument)的区别(高频)

//形参就是函数声明时的变量
//实参就是函数随时参入的具体参数
// a,b是形参,1,3是实参
function add(a,b){
return a+ b
}
add(1,3)

17.事件绑定和普通事件有什么区别

 普通事件(onclick):直接触发事件,同一时间只能指向唯一对象,所以会被覆盖掉

var btn = document.getElementById("btn")
btn.onclick = function(){
    alter("你好111")
}
btn.onclick = function(){
     alter("你好222")
}
// 输出的结果只会有<你好222>,一个click处理器在同一时间
只能指向唯一的对象。所以就算一个对象绑定了多次,其结果只
会出现最后的一次绑定的对象。

事件绑定(addEventListener):事件绑定就是对于一个可以绑定的事件对象,进行多次绑定事件都能运行

var btn = document.getElementById("btn");
btn.addEventListener("click",function(){
	alert("你好111");
},false);
btn.addEventListener("click",function(){
	alert("你好222");
},false);
//运行结果会依次弹出你好111,你好222的弹出框。

区别:

  • addEventListener对任何DOM都是有效的,而onclick仅限于HTML.
  • addEventListener可以控制listener的触发阶段(捕获/冒泡)。对于多个相同的事件处理器,不会重复触发,不需要手动使用removeEventListener清除。

18.对内存泄漏的了解

定义:程序中已在堆中分配的内存,因为某种原因未释放或者无法释放的问题

简单理解: 无用的内存还在占用,得不到释放和归还,比较严重的时候,无用的内存还会增加,从而导致整个系统卡顿,甚至崩溃

19.哪些操作会造成内存泄漏?

  1. 闭包(不合理的使用闭包,从而导致某些变量一直被留在内存中)
  2. setTimeout 的第一个参数使用字符串而非函数的话,会引发内存泄漏(被遗忘的计时器或回调函数)--(设置了setInterval定时器,忘记取消它,如果循环函数有对外部变量的引用的话,这个变量会被一直留在内存中,而无法被回收)

20.设置定时器: setTimeout(cb,ms) ; setInterval(cb,ms)

相同点:全局函数在指定的毫秒(ms)内执行指定函数(cb) 

区别:

setTimeout(cb,ms) :只执行一次指定函数

function printHello(){
    console.log( "Hello, World!");
}
// 两秒后执行以上函数
var timer = setTimeout(printHello, 2000);

//清除定时器
clearTimeout(timer)

 setInterval(cb,ms)方法会不停的调用,知道clearInterval()被调用或关闭窗口

function printHello(){
   console.log( "Hello, World!");
}
// 两秒后执行以上函数
var timer = setInterval(printHello, 2000);

// 清除定时器
clearInterval(timer);

21.清除定时器:clearTimeout() ,clearInterval()

22.js延迟加载的方式有哪些?

js 的加载、解析和执行会阻塞页面的渲染过程,我们希望 js 脚本能尽可能的延迟加载,提高页面的渲染速度

  • 用setTimeout延迟
  • 把js外部引入的文件放到页面底部,让js最后引入,从而加快页面加载速度
  • 加loading
  • 给 js 脚本添加 defer属性,这个属性让脚本的加载与文档的解析同步解析,然后在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了 defer 属性的脚本按规范来说最后是按顺序执行的,但是在一些浏览器中可能不是这样。
  • 给 js 脚本添加 async属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行 js脚本,这时如果文档没有解析完成的话同样会阻塞。多个 async 属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行

23.ajax请求时,如何解析json数据:json.parse 

24.ajax同步,异步的区别:同步async:false;异步async:true 

25.请解释同步和异步函数的区别(高频),怎么解决异步问题(高频)

同步:如果在同一时间内有很多任务的话,这些任务需要排队,一个一个的执行,前一个任务执行完,才会执行下一个任务.

// 同步代码
function fun1() {
  console.log(1);
}
function fun2() {
  console.log(2);
}
fun1();
fun2();

// 输出
1
2

异步:如果在同一时间内有很多任务的话,这些任务不需要排队就可以去完成.任务都完成就结束了(我昨天是不是就是这样说的?公交车那个)

同步:浏览器访问服务器请求,用户看得到页面刷新,重新发请求,等请求完,页面刷新,新内容出现,用户看到新内容,进行下一步操作
异步:浏览器访问服务器请求,用户正常操作,浏览器后端进行请求。等请求完,页面不刷新,新内容也会出现,用户看到新内容

26.JS如何实现异步编程5种?

  • 回调函数(callback)

优点:解决了同步的问题(只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。)

缺点:回调地狱,每个任务只能指定一个回调函数,不能 return.

  • 事件监听
  • Promise
  • Generator
  • 生成器 async/await,是ES7提供的一种解决方案。

27.对async、await的理解,内部原理  

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

async表示函数里面有异步操作,await 这个函数必须得用async修饰,await是等后面的异步完成,等待返回结果,async修饰的函数默认返回promise,其实是.then链式调用的同步写法

  • async:声明异步函数,返回promise对象
  • await: 等待一个async函数的返回值
// async
async function test(){
    return 'test'
}
test()
返回值为 Promise {: "test"}
 
 
// 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()
// 控制台输入结果
something hello async
Promise {: undefined}

async/await 怎么抛出错误异常 ? 

出错代码少可以用try/catch来处理,出错代码多用async函数返回一个promise对象来处理,调用catch方法来处理异常

async/await 使用,异步过程同步化?

async函数返回的是一个Promise对象,函数有返回值,调用这个函数,如果执行成功,内部会调用Promise.solve()方法返回一个Promise对象,如果执行异常,会调Promise.reject()方法返回一个promise 对象

async function fn(){
 return '111'
}
fn().then(data=>{
 console.log(data)//111
})

javascript - 理解 JavaScript 的 async/await_个人文章 - SegmentFault 思否

28.Fetch和Ajax,axios比较优缺点

ajax是一种web数据交互方式

优点:

  • 提高了性能和速度,减少了客户端和服务端之间的流量传输,减少了双方响应的时间

缺点:

  • 增加了设计和开发的时间

ajax的实现主要四个步骤:

  • 创建核心对象XMLhttpRequest;
  • 利用open方法打开与服务器的连接;
  • 利用send方法发送请求;("POST"请求时,还需额外设置请求头)
  • 监听服务器响应,接收返回值。

用法:

    创建xhr
    var xhr=new XMLHTTPRequest()
    侦听通信状态改变的事件
    xhr.addEventListener("readystatechange",readyStateChangeHandler);
    Method 分为 get post put delete等等
    Async 异步同步
    name和password是用户名和密码
    xhr.open(Method,URL,Async,name,password)
    发送内容给服务器
    xhr.send(内容)

    function readyStateChangeHandler(e){
      当状态是4时,并且响应头成功200时,
       if(xhr.readyState===4 && xhr.status===200){
         打印返回的消息 
         console.log(xhr.response)
       }
    }

$.ajax({
  type: 'POST',
  url: url,
  data: data,
  dataType: dataType,
  success: function() {},
  error: function() {}
});

axios:基于promise用于浏览器和node.js的http客户端

优点:

  • 支持Promise API
  • 提供了一些并发请求的接口
  • 支持拦截请求和响应
  • 自动转换JSON数据

用法

axios({
    method: 'post',
    url: '/user/12345',
    data: {
        firstName: 'Fred',
        lastName: 'Flintstone'
    }
})
.then(function (response) {
    console.log(response);
})
.catch(function (error) {
    console.log(error);
});

Fetch基于promise设计的。是原生js,没使用XMLHttpRequest对象,兼容性更好

优点:

  • 更好更方便,更加底层,提供的API丰富(request,response)

缺点:

  • fetch只对网络请求报错,对400,500都当做成功的请求,需要封装去处理
  • fetch没办法原生监测请求的进度,而XHR可以

 用法

try {
  let response = await fetch(url);
  let data = response.json();
  console.log(data);
} catch(e) {
  console.log("Oops, error", e);
}

29.JS有几种类型的值?能画一下他们的内存图吗? 

  • 栈:原始数据类型(Undefined、Null、Boolean、Number、String)
  • 堆:引用数据类型(对象、数组、函数)

两种类型的区别是:存储位置不同

  • 原始数据类型:直接存储在栈(stack)中的简单数据段、占据空间小、大小固定、属于被"频繁"使用的数据
  • 引用数据类型:存储在堆(heap)中、占据空间大、大小不固定、如果存储在栈中、将会影响程序运行的性能、

引用数据类型在栈中存储了指针、该指针指向堆中该实体的起始地址。
当解析器寻找引用值时、会首先检索其在栈中的地址、取得地址后从堆中获得 

30.js事件机制(讲讲事件冒泡和事件捕获以及事件代理,事件委托 ?)

js的事件机制:捕获事件,目标事件,冒泡事件

  • 事件冒泡:事件冒泡可以形象地比喻为把一颗石头投入水中,泡泡会一直从水底冒出水面。也就是说,事件会从最内层的元素开始发生,一直向上传播,直到document对象。(事件的传播顺序是从触发元素找,一直追溯到根节点 )
  • 事件捕获:网景提出另一种事件流名为事件捕获与事件冒泡相反,事件会从最外层开始发生,直到最具体的元素。(事件的传播顺序为从根节点到触发的的具体元素)
  • 事件目标:当到达目标元素之后,执行目标元素该事件相应的处理函数。如果没有绑定监听函数,那就不执行。

在事件捕获的概念下发生click事件的顺序应该是

document -> html -> body -> div -> p

js事件机制 - 天之道利而不害 - 博客园

JS中的事件、事件冒泡和事件捕获、事件委托 - 一粒一世界 - 博客园 

31.什么是事件代理(事件委托) 有什么好处 

就是当事件触发时,把要做的事委托给父元素来处理

优点:减少内存消耗,提高性能;动态绑定事件

缺点:如果把所有事件都用事件代理,可能会出现事件误判

场景1:当多个li标签需要添加点击事件时,当界面上点击li时,会打印它们各自li标签显示的内容。利用事件冒泡的原理,把事件加在父元素(ul)身上!

场景2: 新增元素没有绑定事件的问题(界面上有一个ul里面默认有5个li,并且还有一个按钮,当点击按钮就创建一个新的li,需要不管是默认有的li还是新的li都有点击事件)

//场景1示例
demo
  • item1
  • item2
  • item3
  • item4
  • item5

代码解析:给5个li标签加了点击事件,当界面上点击li时,会打印它们各自li标签显示的内容。

出现的问题:此时5个li,看起来每个li的点击事件触发时调用的都是同一个函数,即:

function () {
    alert(this.innerHTML);
} 

解决办法:利用事件冒泡的原理,把事件加在父元素(ul)身上!

var ul = document.getElementsByTagName('ul')[0];
    // 只用给ul加点击事件即可
    ul.onclick = function (e) {       //事件对象在ie8中要通过window.event才能取到,因此e = e || window.event是做兼容处理
        e = e || window.event;
        // e.target指的是被点击的那个li
        alert(e.target.innerHTML);
    }

 动态绑定事件 因为事件绑定在父级元素 所以新增的元素也能触发同样的事件

32.addEventListener 默认是捕获还是冒泡

默认是冒泡

addEventListener(event,function,useCapture)

  • event:表示监听的事件,如onClcik,touchstart 等
  • function:表示事件触发后调用的函数,可以是外部定义函数,也可以是匿名函数
  • useCapture:选填的,填true或者false,true表示捕获,默认的false表示冒泡。
document.getElementById("myBtn").addEventListener("click", function(){
    document.getElementById("demo").innerHTML = "Hello World";
});

33.原型链

当在一个对象上找一个属性的时候,要先在这个对象本身上找,所有对象上都有个_proto_属性 这个属性值是指向它构造函数的prototype的 如果对象本身没有这个属性 就会去找他的_proto_上找 (也就是构造函数的prototype)如果还没有就去 __proto__的__proto__上找 就这么一直找,找到了则返回,找不到则返回null,这个搜索的过程,就叫做原型链(prototype)

34.原型指针 

就是_proto_使指向它构造函数的原型  

35.原型和原型链存在的意义是什么? 

实例对象可以共享构造函数的原型属性和方法,节省内存,构造函数原型上的属性和方法越多,节省的内存就越大。 

  • 原型模式用于创建重复的对象,同时又能保证性能,这种类型的设计模式属于创建型模式
  • 原型链是原型对象创建过程的历史记录,当访问一个对象的某个属性时,会先在这个对象本身属性上查找。

作用:构造函数原型上的东西可以让实例们共享,从而节省了空间 

36.谈谈JS的运行机制 
  •  所有同步任务都在主线程上执行,形成一个执行栈
  • 主线程之外还有一个“任务队列”。只要异步任务有了结果,就在“任务队列”之中放置一个事件
  • 一旦“执行栈”中所有同步任务都执行完成,系统就会读取“任务队列”,看看里面有哪些事件,哪些对应的异步任务,于是结束等待状态,进入执行栈,开始执行
  • 主线程不断重复上面第三步

37.件循环

主线程从“任务队列”中读取事件,这个过程是不断循环的 ,所以整个的这种运行机制就是事件循环  

38.宏任务和微任务 (事件循环机制)

  • 任务队列里面包括宏任务和微任务
  • 宏任务和微任务就是区分异步里面谁先执行
  • 先执行微任务再执行宏任务(有微则微 无微则宏)
  • 异步里先执行promise这种,最后执行settimeout这种,nexttick也是微任务
  • 同步肯定按顺序执行

39.setTimeout(fn, 0)多久才执行,Event Loop 

setTimeout 按照顺序放到队列里面,然后等待函数调用栈清空之后才开始执行,这些操作进入队列的顺序,则由设定的延迟时间来决定 

说一下eventloop

滑动验证页面

40.浏览器事件流向 

事件流 :从页面中接受事件的顺序

事件流分为两种: 事件捕获,事件冒泡

EventTarget.addEventListener() 方法是将指定的监听器注册到事件目标上,当该对象触发指定的事件时,执行指定的回调函数。

事件目标是一个文档上的元素 ElementDocument和 Window或者任何其他支持事件的对象

41.线程和进程是什么?举例说明

进程:拥有资源和独立运行的最小单位(cpu分配资源的最小单位)
线程:建立在进程基础上的一次程序运行单位,一个进程中可以有多个线程(cpu最小的调度单位)
栗子:比如进程=火车,线程就是车厢

一个进程内有多个线程,执行过程是多条线程共同完成的,线程是进程的部分
一个火车可以有多个车厢
每个进程都有独立的代码和数据空间,程序之间切换会产生较大的开销;线程可以看作轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器,线程之间切换的开销小。
【多列火车比多个车厢更耗资源】
【一辆火车上的乘客很难换到另外一辆火车,比如站点换乘,但是同一辆火车上乘客很容易从A车厢换到B车厢】
同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的
【一辆火车上不同车厢的人可以共用各节车厢的洗手间,但是不是火车上的乘客无法使用别的火车上的洗手间】

为什么js是单线程
JS主要实现浏览器与用户的交互,以及操作DOM。如果JS被设计为多线程,如果一个线程要修改一个DOM元素,另一个线程要删除这个DOM元素,这时浏览器就不知道该怎么办,为了避免复杂的情况产生,所以JS是单线程的。

为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

JS 中级前端面试题_第2张图片

42.javascript的同源策略;跨域? (高频)

同源策略:协议,域名,端口相同
域名:localhost、www.baidu.com
协议:http https ftp

端口:一个网站对应着一个端口, http协议默认端口:80;https协议默认端口是8083

跨域:协议,域名,端口有一样不同都会造成跨域

同源策略带来的麻烦:ajax在不同域名下的请求无法实现,如果说想要请求其他来源的js文件,或者json数据,那么可以通过jsonp来解决
跨域解决方式一

通过CORS解决(最常用的方案,安全,可靠)

跨域资源共享CORS(Cross-Origin Resource Sharing):属于跨源AJAX请求的根本解决方法

Access-Control-Allow-Origin//允许跨域的域名
Access-Control-Allow-Headers//允许的header类型
Access-Control-Allow-Methods//跨域允许的请求方式

xhrFields.withCredentials = true   // 前端设置是否带cookie
  1. 普通跨域请求:只需服务端设置Access-Control-Allow-Origin
  2. 带cookie跨域请求:前后端都需要进行设置
//java中的 hander() 设置,“*”号表示允许任何域向我们的服务端提交请求:
    header("Access-Control-Allow-Origin: *")

3.通过jsonp跨域:服务器与客户端跨源通信的常用方法

  • 优点:简单适用,兼容性好
  • 缺点:只支持get请求,不支持post请求。
// Vue
this.$http.jsonp('http://www.domain2.com:8080/login',{
    params:{},
    jsonp:'handleCallback'
}).then((res) =>{
    console.log(res)
})

3.nginx代理跨域 

通过nginx配置一个代理服务器将客户机请求转发给内部网络上的目标服务器;并将服务器上返回的结果返回给客户端。

4.nodejs中间件代理跨域

5.webpack (在vue.config.js文件中)中 配置webpack-dev-server

devServer: {
        proxy: {
          '/api': {
            target: "http://39.98.123.211",
            changeOrigin: true,  //是否跨域
          },
        },
      },
43.Math的方法
  • Math.abs(): 取绝对值; 
  • Math.floor() : 向下取整 
  • Math.ceil() : 向上取整    console.log(Math.ceil(.95));     // 1
  • Math.max() : 取最大值    // console.log(Math.max(1,3,2)     //  3
  • Math.min() : 取一组数的最小值         // console.log(Math.min(1,3,2)    // 1
  • Math.random() 取随机数,取值范围[0,1) 
  • Math.round(): 四舍五入取整 
  • 取m-n之间的随机整数:Math.round(Math.random()*(n-m)+m) 
  • Math.pow() : 取幂次方 
  • Math.sqrt() : 开平方;

44.如何获取js中三个数的最大值和最小值

  • Math.max(a,b,c)    // 最大值
  • Math.min(a,b,c)     // 最小值
var arr = [22,13,6,55,30];
console.log(Math.max(...arr)); // 55

45.看代码给答案。

var a = new Object();  // 引用数据类型
a.value = 1;
b = a; //b.value=1
b.value = 2;//b.value=2;a.value=2,因为a和b指向同一块引用类型的值
alert(a.value);    // 2
var a = 1;//基本数据类型
b = a;
b = 2;
console.log(a) //1

46.es5类:用的构造函数;es6类:class类(高频)

es5: 

  • 获取对象: new 类名称()
  • 访问:对象.成员

es6:function 类名称(){...}   // 与方法不同的是:类名称首字母要大写

// es5 定义类的方法 向类中添加属性
function Person(name){
    this.name = name;        // 在类内部定义一个属性
    this.say = function(){ alter(this.name)}
}
var p1 = new Person('zh')
p1.say()
alert(typeof (p1));//object
alert(typeof (Person('zhh')));//Undefined 函数没有返回值则为Undefined
alert(typeof(Person));//function


// es6 通过class关键字 定义类
class Point{
    constructor(x,y){
        this.x = x;
        this.y = y;
    }
    toString(){
        return '(' + this.x + ',' + this.y + ')'
    }
}


1.通过class关键字,可以定于类
2.上面代码定义了一个“类”,里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。也就是说,ES5的构造函数Point,对应ES6的Point类的构造方法
3、定义“类”的方法时,前面不需要加上function关键字,直接把函数定义放进去就可以了。方法之间不需要逗号分隔,加了会报错。 
4.构造函数的prototype属性,在ES6的“类”上面继续存在。类的所有方法都定义在类的prototype属性上面。

class Point {
  constructor(){
    // ...
  }

  toString(){
    // ...
  }

  toValue(){
    // ...
  }
}

// 等同于

Point.prototype = {
  toString(){},
  toValue(){}
};

5、Object.assign方法可以很方便地一次向类添加多个方法
class Point {
  constructor(){
    // ...
  }
}

Object.assign(Point.prototype, {
  toString(){},
  toValue(){}
});

JS CLass类 - 天天web学习 - 博客园

47.js的三大特性:继承,封装,多态

1.封装:把公共的属性和功能封装成一个公共的方法,然后通过一个外部可以调用的特定接口进行调用。

好处:

  • 提高代码复用性;解耦;提高网站的打开速度
function Person(name , age , sex){
    this.name = name ; //共有变量 
    var age = age ;  //私有变量
    var sex = sex ; //私有变量
    this.show = function (){
        console.log(age+"===="+sex);
    }
}
var person = new Person('Sunshine',18,'女');
console.log(person.age);    // undefined
console.log(person.name);   // Sunshine
console.log(person.show());     // 18====女

请看代码后的注释,this指向的都是共有的属性和方法,而直接通过var声明的则属于私有变量(即外部不可访问变量),然后通过一个共有的show方法将私有的age和sex输出。当然show方法也要通过this声明才可以哟,否则的话show方法也是不可访问的。 

2.继承:

当多个方法存在相同的属性和方法时,把这些相同的属性和方法提取到一个公共的方法中,通过原型prototype继承该方法。你也可以通过call或apply来继承该方法中的属性和方法。

function Person(name , age , sex){
    this.name = name ; 
    this.age = age ; 
    this.sex = sex ; 
    this.show = function (){
        console.log( this.age + "========"+ this.sex );
    }
}
function Student(name , age , sex , score){
    this.score = score ; 
    Person.apply(this,[name , age , sex]);
}
function Worker(name , age , sex , job){
    this.job = job ; 
    Person.call(this,name , age , sex);
}
Dancer.prototype = new Person('Sunshine',18,'女');
function Dancer(salary ){
    this.salary = salary ;
}
var student = new Student('Sunshine',18,'女',100);
var worker = new Worker('Sunshine',18,'女','IT');
var dancer = new Dancer(20000);
console.log(student);
console.log(worker);
console.log(dancer);

当然,个人感觉那个prototype没有说的很好,如果看到这篇博客的你有更好的建议或意见的话,欢迎给我留言。还有call和apply,其实它们的作用是一样的,都是改变this指向,然后它们的区别也可以从代码中看出,传参方式不同。

3.多态 :

在执行同一操作且作用于不同对象时,返回不同的结果 。其实就是把做什么和由谁去做分开,这样使得代码更容易维护,且条例清晰。

function dwn(s){
    document.write(s+"
"); } function Animal(){ this.bite=function(){ dwn("animal bite!"); } } function Cat(){ this.bite=function(){ dwn("Cat bite!"); } } Cat.prototype=new Animal(); ///inherit Animal function Dog(){ this.bite=function(){ dwn("Dog bite"); } } Dog.prototype=new Animal(); ///inherit Animal function AnimalBite(animal){ if(animal instanceof Animal) //这里是判断animal的原型是否指向Animal animal.bite(); } var cat = new Cat(); var dog = new Dog(); AnimalBite(cat); AnimalBite(dog); //最终输出的结果如下: /* Cat bite! Dog bite */

Js三大特性--封装、继承以及多态_sunshine的博客-CSDN博客_js 多态

48.原生JS中几种继承方式 

原生JS中有四种继承方式,分别为:原型继承,冒充继承,组合继承,寄生组合继承 

JS实现继承的几种方法总结_吴迪98的博客-CSDN博客_js继承的几种方法

49.什么是web worker,为什么我们需要他们web worker? 

web worker是一种开启线程方式;
原因

  • Web Worker可以在浏览器后台运行JS 而不占用浏览器自身线程(Web work实现多线程)
  • Web Worker可以提高应用的总体性能,并且提升用户体验
  • 可以在主线程的下通过web worker开启一个子线程,子线程的执行不会阻塞主线程执行
  • 当我们需要去代码一些高运算的代码时,为了不阻塞主线程,这时就可以开启一个子线程去做这个事件

50.垃圾回收机制 

js觉得这块东西或者变量不需要了 就会发现,然后清除(js自动执行)

怎么解决? 1.标记清除法;2.引用计数:使用完直接赋值为空

内存管理 - JavaScript | MDN

掘金掘金

51.SEO是什么?Search Engine Optimization--搜索引擎优化

根据你输入的内容,浏览器检索出关键字

52.对象的深拷贝?ES6、ES5(高频)

  1. 浅拷贝:当原对象发生变化的时候,拷贝对象也跟着变化;
  2. 深拷贝: 另外申请了一块内存,内容和原对象一样,更改原对象,拷贝对象不会发生变化;
// 浅拷贝
var a = { count: 1, deep: { count: 2 } }
var b = Object.assign({}, a)
// 或者
var b = {...a}

// 浅拷贝的第二种方法

ES6中的Object.assign方法,Object.assign是ES6的新函数。可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。

Object.assign(target, ...sources)
参数:

  • target:目标对象。
  • sources:任意多个源对象。
  • 返回值:目标对象会被返回。
var obj1 = {
    a: "hello",
    b: {
        a: "hello",
        b: 21}
};
 
var cloneObj1= Object.assign({}, obj1);
cloneObj1.a = "changed";
cloneObj1.b.a = "changed";
console.log(obj1.a);  //hello
console.log(obj.b.a); // "changed"

 // 深拷贝:一般需要借助递归实现,如果对象的值还是个对象,要进一步的深入拷贝,完全替换掉每一个复杂类型的引用。

对象中可能又存在对象,所以需要深拷贝。首先需要知道这是一个递归调用,然后要判断一些特殊类型(数组,正则对象,函数)进行具体的操作,可以通过Object.prototype.toString.call(obj)进行判断。

// 手动复制--一个一个赋值
var obj1 = { a: 10, b: 20, c: 30 };
var obj2 = { a: obj1.a, b: obj1.b, c: obj1.c };
obj2.b = 100;
console.log(obj1);
// { a: 10, b: 20, c: 30 } <-- 沒被改到
console.log(obj2);
// { a: 10, b: 100, c: 30 }
var deepCopy = (obj) => {
    var ret = {}
    for (var key in obj) {
        var value = obj[key]
        ret[key] = typeof value === ‘object‘ ? deepCopy(value) : value
    }
    return ret
}

53.讲讲Map和Set?(介绍下 Set、Map、WeakSet 和 WeakMap 的区别?)

Set:成员唯一、无序且不重复
[value, value],键值与键名是一致的(或者说只有键值,没有键名)
可以遍历,方法有:add、delete、has;属性size:返回set成员总数

Map
map和set有很大程度是相似的,但是map是以键值对的形式存储数据的。

属性size:返回map结构成员总数

方法:delete(key):删除某个key,返回true;删除失败,返回false;

//Set

    
        
        数组去重
    
    
        
    

//Map


    
        
        map中的size属性
    
    
        
    

ES6学习总结之Set和Map的使用 - 二郎神杨戬 - 博客园

54.防抖节流

 所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。(多次触发 只执行最后一次

应用场景:

  • 登录、发短信等按钮避免用户点击太快,以致于发送了多次请求,需要防抖
  • 调整浏览器窗口大小时,resize 次数过于频繁,造成计算过多,此时需要一次到位,就用到了防抖
  • 文本编辑器实时保存,当无任何更改操作一秒后进行保存

JS 中级前端面试题_第3张图片

 实例代码

    // 防抖函数
    function debounce(fn, wait) {
        let timer;
        return function() {
            let _this = this;
            let args = arguments;
            if(timer) { clearTimeout(timer) }
            timer = setTimeout(function(){
                fn.apply(_this, args)
            }, wait);      
        }
    }
    // 使用
    window.onresize = debounce(function() {console.log('resize')}, 500)

应用示例:


    
    


所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。(规定时间内 只触发一次)

应用场景:

  • 鼠标连续不断地触发某事件(如点击),单位时间内只触发一次;
  • 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断。例如:懒加载;

 实现思路:

JS 中级前端面试题_第4张图片

 实现代码:

    // 方式1: 使用时间戳
    function throttle1(fn, wait) {
        let time = 0;
        return function() {
            let _this = this;
            let args = arguments;
            let now = Date.now()
            if(now - time > wait) {
                fn.apply(_this, args);
                time = now;
            }
        }
    }
    
    // 方式2: 使用定时器
    function thorttle2(fn, wait) {
        let timer;
        return function () {
            let _this = this;
            let args = arguments;
            
            if(!timer) {
                timer = setTimeout(function(){
                    timer = null;
                    fn.apply(_this, args)
                }, wait)
            }
        }
    }

应用示例:

    
/* 节流 */
    function throttle(func, wait) {
        var previous = 0;
        return function () {
            var now = +new Date();
            if (now - previous > wait) {
                func.apply(this, arguments);
                previous = now;
            }
        }
    }
    function getUserAction() {
        console.log(`每秒1秒内打印一次`)
    }
    document.querySelector('div').addEventListener('click', throttle(getUserAction, 1000))

解决方法:加定时器,加loading

55.暂时性死区

let 和 const 声明的变量不存在变量提升,其作用域都是块级作用域,凡是在声明变量之前使用变量就会报错,所以,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。

if (true) {
  // 死区开始
  lzp = 'lut'; //  ReferenceError
  console.log(lzp); //  ReferenceError
 
  // 开始声明变量,死区结束
  let lzp; 
  console.log(lzp); // undefined
 
  lzp = 520;
  console.log(lzp); // 520
}

56.什么是懒加载和预加载?

懒加载:延迟加载,延迟加载网络资源或符合某些条件时才加载资源。常见的就是图片延时加载。
目的:作为服务器前端的优化,减少请求数或延迟请求数
实现方式:

用setTimeOut或setInterval进行加载延迟.
条件加载,符合某些条件,或触发了某些事件才开始异步下载

可视区加载,即仅加载用户可以看到的区域,主要由监控滚动条来实现,一般会在距用户看到某图片前一定距离遍开始加载,这样能保证用户拉下时正好能看到图片
预加载:提前加载图片,当用户需要查看时可直接从本地缓存中渲染。

两者的行为是相反的,一个是提前加载,一个是迟缓甚至不加载。懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力。预加载应用如广告弹窗等。

57.src和href

src和href都是用在外部资源的引入上,比如图像,CSS文件,HTML文件,以及其他的web页面等等

区别:

1、请求资源类型不同

  •  href表示超文本引用。用来当前文档和引用资源之间确立联系。常用的有:link、a。
  • src 用于替换当前内容,常用的有script,img 、iframe;

 理解src:src是source的缩写,指向外部资源的位置,指向的内容将会嵌入到文档中当前标签所在位置;在请求src资源时会将其指向的资源下载并应用到文档内,例如js脚本,img图片和frame等元素。 

2、 浏览器解析方式不同

  • 文档中添加href ,浏览器会识别文档为 CSS 文件,会并行下载资源并且不会停止对当前文档的处理。这也是为什么建议使用 link 方式加载 CSS,而不是使用 @import 方式。
  • 浏览器解析到src ,会暂停其他资源的下载和处理,直到将该资源加载、编译、执行完毕,图片和框架等也如此,类似于将所指向资源应用到当前内容。这也是为什么建议把 js 脚本放在底部而不是头部的原因。

58.请说出三种减低页面加载时间的方法

  • 优化图片
  • 优化CSS
  • 减少http请求  

59.关于执行环境(上下文)、全局变量对象VO、活动对象AO、全局对象GO 

JS引擎在执行代码的过程中需要先解析再执行。

1.初始化全局对象

首先,JS引擎会在执行代码之前,也就是解析代码时,会在我们的堆内存创建一个全局对象:Global Object(简称GO),观察以下代码,在全局中定义了几个变量:
var name = 'curry'
var message = 'I am a coder'
var num = 30

所有的**作用域(scope)**都可以访问该全局对象;
对象里面会包含一些全局的方法和类,像Math、Date、String、Array、setTimeout等等;
其中有一个window属性是指向该全局对象自身的;
该对象中会收集我们上面全局定义的变量,并设置成undefined;
全局对象是非常重要的,我们平时之所以能够使用这些全局方法和类,都是在这个全局对象中获取的;

var GlobalObject = {
  Math: '类',
  Date: '类',
  String: '类',
  setTimeout: '函数',
  setInterval: '函数',
  window: GlobalObject,
  ...
  name: undefined,
  message: undefined,
  num: undefined
}

2.执行上下文栈(调用栈) 

了解了什么是全局对象后,下面就来聊聊代码具体执行的地方。JS引擎为了执行代码,引擎内部会有一个执行上下文栈(Execution Context Stack,简称ECS),它是用来执行代码的调用栈。

(1)ECS如何执行?先执行谁呢?

无疑是先执行我们的全局代码块;
在执行前全局代码会构建一个全局执行上下文(Global Execution Context,简称GEC);
一开始GEC就会被放入到ECS中执行;

4.函数执行上下文 

在执行全局代码遇到函数如何执行呢?

在执行的过程中遇到函数,就会根据函数体创建一个函数执行上下文(Functional Execution Context,简称FEC),并且加入到执行上下文栈(ECS)中。
函数执行上下文(FEC)包含三部分内容:
AO:在解析函数时,会创建一个Activation Objec(AO);
作用域链:由函数VO和父级VO组成,查找是一层层往外层查找;
this指向:this绑定的值,在函数执行时确定;
其实全局执行上下文(GEC)也有自己的作用域链和this指向,只是它对应的作用域链就是自己本身,而this指向为window。

 5.变量环境和记录

在早期ECMA的版本规范中:每一个执行上下文会被关联到一个变量环境(Variable Object,简称VO),在源代码中的变量和函数声明会被作为属性添加到VO中。对应函数来说,参数也会被添加到VO中。

也就是上面所创建的GO或者AO都会被关联到变量环境(VO)上,可以通过VO查找到需要的属性;
规定了VO为Object类型,上文所提到的GO和AO都是Object类型;

JavaScript的执行过程(深入执行上下文、GO、AO、VO和VE等概念)_MomentYY的博客-CSDN博客_js的go,ao,vo的创建时间

60.defer 和 async区别
  • defer 并行加载 js 文件,会按照页面上 script 标签的顺序执行
  • async 并行加载 js 文件,下载完成立即执行,不会按照页面上 script 标签的顺序执行

单线程是指 JS在执行的时候,有且只有一个主线程来处理所有的任务。目的是为了实现与浏览器交互。

Js中的defer属性和async属性_mengduxiu的博客-CSDN博客_defer js

61.鉴权有了解吗?jwt如果实现踢人,session和jwt鉴权的区别

62.深拷贝的手动实现?

Js基础知识-手动实现深拷贝 - 简书

let obj1={
  name:'王',
  age:23,
  address:{
    city:'河南省郑州市'
  },
  hobby:['play','eat']
}
let obj2=obj1
console.log(obj1.name) // 王
obj2.name='李'
console.log(obj1.name) // 李
// 由于直接将obj1直接赋值给了obj2,此时他们的指针地址是相同的,所以当obj2改变时,obj1的值也会改变。
// 如何将obj1赋值给obj2,修改obj2的值obj1的值不会改变?
1.创建方法deepClone,将返回一个值作为方法的返回值
function deepClone(obj={}){
    return obj
}
2.检查传入的参数类型是否为引用类型,不是的话将参数直接返回
function deepClone(obj={}){
    if(typeof obj !== 'object' || obj === null){
        return obj;
    }
}
3.处理参数为引用类型,检测是否为数组,定义新的变量
function deepClone(obj = {}){
    if(typeof obj !== 'object' || obj === null){
        let result; // 将要返回的变量
        if(obj instanceof Array){
            result = [] // 如果Obj为数组,则将返回定义为空数组
        }else{
            result = {} // 如果Obj为对象,则将返回定义为空对象
        }
    }
}
4.遍历赋值:会用到递归,如果不清楚可以将每一个obj的值进行打印。
function deepClone(obj={}){
  if(typeof obj!=='object'||obj==null){
    return  obj
  }
  let result  // 将要返回的变量
  if(obj instanceof Array){
    result=[]   // 如果Obj为数组,则将返回定义为空数组
    }else{
    result={} // 如果Obj为对象,则将返回定义为空对象
  } 
 // forin可以用于对象和数组的遍历
    for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
       result[key]=deepClone(obj[key]) // 使用递归
    }
  }
}
5.再次声明测试
/**
* 深度克隆
* @param {object} obj 需要克隆的对象/数组
*/
function deepClone(obj={}){
  console.log(obj)
 //obj每次的值
 //王     23     {city: "河南省郑州市"}      河南省郑州市      ["play", "eat"]     play       eat
  if(typeof obj!=='object'||obj==null){
    return  obj
  }
  let result  // 将要返回的变量
  if(obj instanceof Array){
    result=[]   // 如果Obj为数组,则将返回定义为空数组
    }else{
    result={} // 如果Obj为对象,则将返回定义为空对象
  } 
 // forin可以用于对象和数组的遍历
    for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
       result[key]=deepClone(obj[key]) // 使用递归
    }
  }
  return result
}
let obj2=deepClone(obj1)
obj2.name='李'
console.log(obj1.name,obj2.name) // 王 李

63.手写 Proxy 实现数据劫持

简单来说就是在数据改变时对数据进行监听(处理)
其中Proxy是数据劫持的一种方案 

Proxy:
我们可以按照如下方式定义一个 Proxy
const testProxy = new Proxy(target, handler);
target:
要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
handler:
一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 testProxy 的行为
当外界每次对obj进⾏操作时,就会执⾏handler对象的⽅法
handler中常⽤的对象⽅法如下:

get(target, propKey, receiver)
set(target, propKey, value, receiver)
has(target, propKey)
construct(target, args):
apply(target, object, args)
// Proxy是es6新增的,不支持旧版IE
const carInfo = {
  brand: '梅赛德斯',
  model: 'c500',
  price: 5000000,
  handlePrice: 0
}

function editPrice(dom){
	testProxy.price = dom.value;
}
function handlePrice(dom){
	testProxy.handlePrice = dom.value;
}

window.onload = () =>{
	document.getElementById('brand').innerHTML = testProxy.brand;
	document.getElementById('model').innerHTML = testProxy.model;
	document.getElementById('price').innerHTML = testProxy.price;
}

const testProxy = new Proxy(carInfo, {
  // 获取对象属性值时触发
  // target:原对象,key:对应属性
  get: function(target, key) {
    console.log(`车辆信息:${key} 被访问`)
	return target[key];
  },
  // 改变/赋值对象属性值时触发
  // target:原对象,key:对应属性,newValue:改变的值
  set: function(target, key, newValue) {
	if (key === "price"){ // 对价格进行拦截校验
		if (typeof newValue !== "number") {
			alert("价格字段必须为Number类型") // 异常后输入框数值初始化
			document.getElementById('price').value = target[key]
			return 
		}else if (newValue < 300000){
			alert("价格不能低于300000")
			document.getElementById('price').value = target[key]
		}else {
			target[key] = newValue;
			console.log(`车辆信息:${key} 已被更改`)
		}  
	} else if (key === 'handlePrice') {
		testProxy.price = target['price'] + parseInt(newValue)
		console.log(`价格已被更改 ${testProxy.price}`)
		document.getElementById('handlePrice').value = 0;
		document.getElementById('price').innerHTML = target['price'];
	} else {
		target[key] = newValue;
		console.log(`车辆信息:${key} 已被更改`)
	}
    // Proxy仅作为 => 代理 如需要改变原数据,则需要做此赋值操作
	// target[key] = newValue 
	
  }
})
testProxy.brand = '大众',
testProxy.price = 300001

JavaScript-Proxy & 数据劫持_deft_的博客-CSDN博客_react数据劫持

64.怎么封装请求 

1.引入axios

2.创建request.js文件,请求拦截和响应拦截

创建axios实例
import Vue from 'vue'
import axios from 'axios'
import store from '@/store'
const service = axios.create({
    timeout: 6000
})

// 请求拦截:token,在请求头中添加key,value
// request interceptor
service.interceptors.request.use(config => {
  const token = Vue.ls.get(ACCESS_TOKEN)
  if (token) {
    // config.headers['Authorization'] = 'bearer ' + token // 让每个请求携带自定义 token 请根据实际情况自行修改
    config.headers['Authorization'] = token // 让每个请求携带自定义 token 请根据实际情况自行修改
  }
  config.headers['account'] = Vue.ls.get(USER_CODE)
  config.headers['platform_code'] = '77299c0d776ff54d5a5909784079e6e6'
  config.headers['api-version'] = 'v1'
  return config
}, err)

// 响应拦截:在响应头加东西,token过期时间,如果token过期直接退出登录
service.interceptors.response.use((response) => {
  const str = response.headers['content-disposition']
  if (str) {
    sessionStorage.setItem('filename', str.split('=')[1])
  }
  if (response.config.responseType === 'blob') {
    return response
  }
  return response.data
}, err)

 3.创建api文件夹-创建对应的js文件,引入request文件,向外暴露请求的方法

// 引入request文件

/**
 * 平台管理接口
 */
import { axios } from '@/utils/request'
import { toQueryString } from '@/utils/util'

/**
 * 分页查询
 * @param parameter
 * @returns {*}
 */
export function getUserPage (parameter) {
  return axios({
    url: process.env.VUE_APP_REQUEST_MANAGE + '/admin/platform/list/page',
    method: 'post',
    data: parameter
  })
}

/**
 * 详情
 * @param parameter
 * @returns {*}
 */
export function byIdDetailRole (parameter) {
  return axios({
    url: process.env.VUE_APP_REQUEST_MANAGE + '/admin/platform/get',
    method: 'get',
    params: parameter
  })
}

4.在对应页面引入请求方法 

65.写代码:求平均数

66.JavaScript不同类型的存储方式有何区别? 

普遍认为

  • 基础数据类型存于栈内存
  • 引用数据类型存于堆内存 我认为
  • 所有数据都存于堆内存,栈内存只存指针

67.你说字符串存储在栈内存,那如果字符串很长。超过了栈内存最大容量呢?

所以说我觉得所有数据都存于堆内存,毕竟栈内存容量有限。 

68.赋值、深拷贝与浅拷贝有什么不同? 

  • 赋值:赋值指针指向,还是用的同一个内存空间
  • 浅拷贝:只拷贝第一层,两个引用类型指向同一个地址,改变一个,另一个也会随之改变
  • 深拷贝:所有层都会进行拷贝,复制后引用类型指向一个新的内存地址,两个对象改变互不影响

69.class的静态属性,继承?

  • 静态属性:static关键字定义的变量属性,只能通过Class构造函数的属性的方式去访问
  • 继承:使用 Child extends Father {},且配合super对象,完成继承

70.ES5的继承和ES6的继承区别 

  • ES5的继承是通过prototype或构造函数机制来实现。实质上是先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.apply(this))。
  • ES6的继承机制完全不同,实质上是先创建父类的实例对象this(所以必须先调用父类的super()方法),然后再用子类的构造函数修改this。

71.require和import区别

调用时间:

require运行时调用,理论上可以运用代码任何地方,甚至不需要赋值给某个变量之后再使用

import是编译时调用,必须放在文件开头,而且格式也是确定的

规范:

require 是 AMD规范引入方式
import是es6的一个语法标准,如果要兼容浏览器的话必须转化成es5的语法

本质

require是赋值过程,其实require 的结果就是对象、数字、字符串、函数等,再把require的结果赋值给某个变量。
import是解构过程。

72.深拷贝几种方法  

JSON.stringify;JSON.parse;for...in;递归 

73.微前端的几种方案  

路由分发:开发成本低,维护成本低,不限技术栈

iframe

单独的时候怎么去访问子框架的地址

web component 

74.实现继承的方法有哪些

  • 原型链继承
  • 构造函数继承
  • 组合继承
  • 寄生组合继承
  • es6中的calss类继承

75.模块化

模块化开发在现代开发中已是必不可少的一部分,它大大提高了项目的可维护、可拓展和可协作性。通常,我们 在浏览器中使用 ES6 的模块化支持,在 Node 中使用 commonjs 的模块化支持。
分类:

  • es6: import / export
  • commonjs: require / module.exports / exports
  • amd: require / defined

76.require 与 import 的区别 

  • require 支持 动态导入,import 不支持,正在提案 (babel 下可支持)
  • require 是 同步 导入,import 属于 异步 导入
  • require 是 值拷贝,导出值变化不会影响导入值;import 指向 内存地址,导入值会随导出值而变化

 77.babel 编译原理

1.babylon 将 ES6/ES7 代码解析成 AST
2.babel-traverse 对 AST 进行遍历转译,得到新的 AST
3.新 AST 通过 babel-generator 转换成 ES5

AST:抽象语法树 (Abstract Syntax Tree),是将代码逐字母解析成 树状对象 的形式。这是语言之间的转换、代码语法检查,代码风格检查,代码格式化,代码高亮,代码错误提示,代码自动补全等等的基础。 

78.数组求和最快的方法

eval(arr.join(“+”)) 

79.JavaScript里面0.1+0.2 === 0.3是false 解决办法

const withinErrorMargin = (left, right) => {
    return Math.abs(left - right) < Number.EPSILON * Math.pow(2, 2);
}
console.log(withinErrorMargin(0.1 + 0.2, 0.3))

// 第二种方法
console.log(parseFloat((0.1 + 0.2).toFixed(10)) === 0.3)

80.token 的含义 

Token 的引入:Token 是在客户端频繁向服务端请求数据,服务端频繁的去数据库查询用户名和密码并进行对比,判断用户名和密码正确与否,并作出相应提示,在这样的背景下,Token便应运而生。
Token 的定义:Token 是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此 Token 返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。
使用 Token 的目的:Token 的目的是为了减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮。

数组拉平的几种方法

 如何用 CSS 画一个五角星? 

============代码题========== 

53.Javascript 如何实现继承?

实例继承:将子构造函数的 prototype 指向父构造函数的一个实例
原型继承:将子构造函数的 prototype 指向父构造函数的 prototype

 

构造函数绑定:使用 call 或 apply 方法,将父对象的构造函数绑定在子对象上
拷贝继承:如果把父对象的所有属性和方法,拷贝进子对象
ES6 语法 extends:class ColorPoint extends Point {}

11.介绍纯函数 

纯函数:一个函数的返回结果只依赖其参数,并且执行过程中没有副作用

30.ajax返回的状态

31.如何实现ajax请求,假如我有多个请求,我需要让这些ajax请求按照某种顺序一次执行,有什么办法呢?如何处理ajax跨域

32.如何实现一个ajax请求?如果我想发出两个有顺序的ajax需要怎么做?

49.Date的实例

  • console.log(typeof new Date());// "object"
  • console.log(new Date());// 获取本机的系统时间;
  • console.log(time.getFullYear());// 获取时间年;
  • console.log(time.getMonth());// 获取时间月 取值范围【0-11】
  • console.log(time.getDate());// 获取时间日【1-31】
  • console.log(time.getDay());// 获取星期几;【0-6】 星期日是0;

 36.事件代理和事件委托的区别 

 https://www.jianshu.com/p/5d7c76eaf214

61.看下列代码,将会输出什么?

考点:1、变量作用域 2、变量声明提升

var foo = 1;
function f(){
    console.log(foo);  // 它只会找函数里面最近的,代码从上往下加载,所以是undefined
    var foo = 2;
    console.log(foo);     // 2
}
f();
// 输出undefined 和 2。

代码题: 数组去重复,如果考虑复杂类型,考虑引用去重

// 数组去重
// 1. Set
// 2. filter O(n2)
// 3. Map 
// 4. js 对象 { }

const arr = [1, 1, 2, 3, '1', x, y, z];
const x = { a: 100 }
const y = { a : 100 }
const z = x
const arr2 = [x, y, z]

function uniq(arr) {
  // TODO:
}

 如何判断是 new 还是函数调用

function foo() {
     // new 调用 or 函数调用
}

new foo();
foo();

// 思路1:new.target
// 思路2:instanceof
// 思路3:constructor

 typeof类型判断情况,代码执行情况

let a = 0,b = 0;
function fn(a){
    fn = function fn2(b){
        alert(++a+b);
    }
    alert(a++);
}
fn(1);  // 1
fn(2);  // 5 ??

 执行情况

async function async1() {
  console.log('async1 start')
  await new Promise(resolve => {
    console.log('promise1')
  })
  console.log('async1 success')
  return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')

// srcipt start
// async1 start
// promise1
// srcipt end
手写数组扁平化函数  

解决异步回调的问题 

78.Promise中的执行顺序(考察频率:高) 

开发者客栈-帮助开发者面试的平台-顽强网络

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});
 
promise.then(function() {
  console.log('resolved.');
});
 
console.log('Hi!');
 
// Promise
// Hi!
// resolved

// 解析:上面代码中,Promise 新建后立即执行,所以首先输出的是Promise。然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以resolved最后输出。

111.以下函数调用会发生什么情况

function foo() {
   foo();
}

function foo2() {
   setTimeout(() => {
       foo2();
   }, 0);
}

foo();    //会有问题?栈溢出 
foo2();  // 会有问题?不会栈溢出
// 什么原因?
下面用的节流:就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

compose???

 题目描述:实现一个compose函数

function fn1(x){
    retrun x + 1
}
function fn2(x){
    retrun x + 2
}
function fn3(X){
    return x + 3
}
function fn4(x){
    return x + 4
}
const a = compose(fn1,fn2,fn3,fn4)
console.log(a(1)) // 1+4+3+2+1 = 11

实现代码如下:

function compose(...fn) {
  if (!fn.length) return (v) => v;
  if (fn.length === 1) return fn[0];
  return fn.reduce(
    (pre, cur) =>
      (...args) =>
        pre(cur(...args))
  );
}

js 事件循环:nodejs 里面的,koa 用的多吗 

113.数组扁平化 :将一个多维数组,一层一层的转化为层级较少或者只有一层的数组

题目描述:实现一个方法使用多维数组变成一维数组

最常见的递归版本如下:

function flatter(arr) {
  if (!arr.length) return;
  return arr.reduce(
    (pre, cur) =>
      Array.isArray(cur) ? [...pre, ...flatter(cur)] : [...pre, cur],
    []
  );
}
// console.log(flatter([1, 2, [1, [2, 3, [4, 5, [6]]]]]));

实现代码如下:
function flatter(arr) {
  if (!arr.length) return;
  while (arr.some((item) => Array.isArray(item))) {
    arr = [].concat(...arr);
  }
  return arr;
}
// console.log(flatter([1, 2, [1, [2, 3, [4, 5, [6]]]]]));

114.列表转成树形结构

[
    {
        id: 1,
        text: '节点1',
        parentId: 0 //这里用0表示为顶级节点
    },
    {
        id: 2,
        text: '节点1_1',
        parentId: 1 //通过这个字段来确定子父级
    }
    ...
]

转成
[
    {
        id: 1,
        text: '节点1',
        parentId: 0,
        children: [
            {
                id:2,
                text: '节点1_1',
                parentId:1
            }
        ]
    }
]

实现代码如下:
function listToTree(data) {
  let temp = {};
  let treeData = [];
  for (let i = 0; i < data.length; i++) {
    temp[data[i].id] = data[i];
  }
  for (let i in temp) {
    if (+temp[i].parentId != 0) {
      if (!temp[temp[i].parentId].children) {
        temp[temp[i].parentId].children = [];
      }
      temp[temp[i].parentId].children.push(temp[i]);
    } else {
      treeData.push(temp[i]);
    }
  }
  return treeData;
}

115.树形结构转成列表

[
    {
        id: 1,
        text: '节点1',
        parentId: 0,
        children: [
            {
                id:2,
                text: '节点1_1',
                parentId:1
            }
        ]
    }
]
转成
[
    {
        id: 1,
        text: '节点1',
        parentId: 0 //这里用0表示为顶级节点
    },
    {
        id: 2,
        text: '节点1_1',
        parentId: 1 //通过这个字段来确定子父级
    }
    ...
]

代码实现如下:
function treeToList(data) {
  let res = [];
  const dfs = (tree) => {
    tree.forEach((item) => {
      if (item.children) {
        dfs(item.children);
        delete item.children;
      }
      res.push(item);
    });
  };
  dfs(data);
  return res;
}

116.几种常见的JS递归算法题

递归的概念:就是函数子级调用自己本身,或者在自己函数调用的下级函数中调用自己

递归的步骤:

  • 假设递归函数已经写好
  • 寻找递归关系
  • 将推进关系的结构转换为递归体
  • 将临界条件加入到递归体中

 经典案例1:求和

// 求1-100的和
function sum(n){
    if(n==1) retrun 1;  
    return sum(n-1) + 1;   // 为什么把1单拿出来,因为sun(1-1)==>sum(0)等于0,这里求的是1-100的和,一直是0就变成了死循环一直都是1。
}

 经典案例2:斐波拉契数列(黄金分割数列,兔子数列)???不懂

// 1,1,2,3,5,8,13,21,34,55,89...求第n项
// 递归方法
function fib(n){
    if( n===1 || n ===2 ) return n-1;
    return fib(n - 1) + fib(n - 2)
}

// 非递归方法
function fib(n){
    let a = 0;
    let b = 1;
    let c = a + b;
    for(let i = 3; i < n;i++){
        a = b;
        b = c;
        c = a + b;
    }
    return c;
}
console.log(fib(10));

案例3:深拷贝

原理:clone(o) = new Object(),返回一个对象

function clone(o){
    var temp = {}
    for(var key in o){
        if(typeof o[key] === 'object'){
            temp[key] = clone[o(key])]
        }else{
            temp[key] = o[key]
        }
    }
    return temp;
}
下方js执行后的打印值为?
function demo() {
    this.length = 10;
    var fn = function() {
        console.log(this.length);     // 输出多少?
    }
    arr = [fn, 'hello layui'];
    fn.length = 100;
    arr0;
}
 
window.demo()

 console.log(this.length)------打印结果为2
请说出以下结果输出什么?为什么?
for(var i = 0; i < 5; i++) {
    setTimeout(function(){
        console.log(i)
    }, 0)
}

答案:5个5
解释:异步代码需要等同步代码先执行,所以当异步定时器执行时,
同步的for循环已经循环完毕

 请说出以下flag的结果?为什么?

function show(){}
 
function getName() { return '牛夫人' }
 
var flag = show() || getName()
答案:flag值为'牛夫人'
解释:1.函数都会有一个默认的返回值undefined
2.逻辑或如果第一个值成立就直接返回第一个值,否则直接返回第二个值

 执行下面代码打印什么?为什么?

var a = {};
var b = {key: 'b'};
var c = {key: 'c'};
var d = [3,5,6];
a[b] = 123;
a[c] = 345;
a[d] = 333;
console.log(a[b]);  
console.log(a[c]);  
console.log(a[d]);  

console.log(a[b]);  // 打印:345
console.log(a[c]);  // 打印:345
console.log(a[d]);  // 打印:333
为什么:对象转化字符串会变成一个'[object Object]'
请简述Js Bridge

请说一下SSR的单机QPS 

请说一下eggJs的初始化原理 

108.函数柯里化:就是把多参数函数转化为单参数函数

// 没有柯里化
function sum(a,b,c){
    return a + b + 
}
console.log(sum(1,2,3)) // 6

// 转化成柯里化
function sum(a){
    return function(b){
        return function(c){
            return a + b + c
        }
    }
}
console.log(sum(1)(2)(3)) // 6

// 简化
const sum = a => b => c => a + b + c
console.log(sum(1)(2)(3))  // 6

解决遍历对象时,把原型上的属性遍历出来了咋办? 

使用hasOwnProperty判断

function Person(name) {
  this.name = name
}
Person.prototype.age = 23
const person = new Person('Sunshine_lin')
for (const key in person) { console.log(key) } // name age
// 使用 hasOwnProperty
for (const key in person) {
  person.hasOwnProperty(key) && console.log(key)
} // name

109.用Set获取两个数组的交集,如何做 

js数据结构set 取交集和并集_尤小白的博客-CSDN博客_js set取交集

let a = new Set([1,2,3]);
let b = new Set([4,3,2]);

// 并集
let union = [...new Set([...a,...b])];
console.log(union); // 1,2,3,4

// 交集
let intersect = [...new Set([...a].filter((x) => b.has(x)))];
console.log(intersetct) // 2,3

110.手写实现Promise.all 

回答以下代码,alert的值分别是多少?

 怎么降维数组 [[1,2],[3,4]] --> [1, 2, 3, 4]

  1. 使用递归循环,把所有元素放到一个新数组
  2. Array.prototype.concat.apply([],[[1,2],[3,4]]);

102.数组的冒泡排序

var ary = [12,32,56,67,88,99,101];
// 1. sort
/*ary.sort(function (a,b) {
return a-b;// 从小到大
});*/
// 实现从小到大
// 让相邻两项进行比较,如果前面一项比后面一项大,那么让这两项互换位置;如果前比后面一项小,那么不换位置;每循环一整轮,把数组的最大值放到数组的最后面;有多少项,就执行多少次这样的循环;
for(var j = 0;jary[i+1]){//前面一项比后面大
// 借助第三方变量
//如果能进来,说明数组没有排好顺序;如果进不来,说明数组已经是排好的
var temp = ary[i];
ary[i] = ary[i+1];
ary[i+1] = temp;
flag = false;
}
}
if(flag){
break;
}
}
console.log(ary);

100.数组的快速排序(快速排序快速排序的原理: 首先取原有数组中的中间项;接下来循环原有的数组每一项,和中间项进行比较,如果比中间项的小的放在左边的数组中,比中间项大的放在右边的数组中;然后再对左边和右边数组进行刚才的操作;最后把所有的小数组和中间项串在一起就是排好的数组)

var ary = [12,8,89,78,76,56,25,35];
function quickSort(ary) {
console.log(ary);
// 当数组的长度小于等于1;直接return当前的数组;
if(ary.length<=1){
return ary;
}
//获取
var middleIndex = Math.floor(ary.length/2);
//删除数组中中间项;并且获取中间项的值
var middleVal = ary.splice(middleIndex,1)[0];
console.log(middleVal);
var left = [];
var right = [];
for(var i=0;i
如何实现数组的复制 
// 逐一复制

  var arr1=[];
  for(var i=0;i

(32条消息) JS高级_前端-公瑾的博客-CSDN博客_js高级是什么

最全的手写JS面试题 - 掘金

80% 应聘者都不及格的 JS 面试题 - 掘金

最全的手写JS面试题 - 掘金

104.JSONP的优缺点 

 jsonp是json的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题。

优点:

  • 它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制,JSONP可以跨越同源策略;
  • 它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持

缺点:

  • 它只支持GET请求而不支持POST等其它类型的HTTP请求
  • 它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。
  •  jsonp在调用失败的时候不会返回各种HTTP状态码。

​​​82.(微医)Promise 构造函数是同步执行还是异步执行,那么 then 方法呢? 

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

 const promise = new Promise((resolve, reject) => {
        console.log(1)
        resolve()
        console.log(2)
    })

    promise.then(() => {
        console.log(3)
    })

    console.log(4)

//  执行结果是:1243

83. (滴滴、挖财、微医、海康)JS 异步解决方案的发展历程以及优缺点。

1. 回调函数(callback)

setTimeout(() => {
        // callback 函数体
    }, 1000)

缺点:回调地狱,很难处理错误。不能用 try catch 捕获错误,不能 return
回调地狱的根本问题在于

缺乏顺序性: 回调地狱导致的调试困难,和大脑的思维方式不符
嵌套函数存在耦合性,旦有所改动,就会牵一发而动全身,即(控制反转,嵌套函数过多的多话,很难处理错误

ajax('XXX1', () => {
        // callback 函数体
        ajax('XXX2', () => {
            // callback 函数体
            ajax('XXX3', () => {
                // callback 函数体
            })
        })
    })

优点:解决了同步的问题(只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。)

2. Promise
Promise就是为了解决callback的问题而产生的。

Promise 实现了链式调用,也就是说每次 then 后返回的都是一个全新 Promise,如果我们在 then 中 return ,return 的结果会被Promise.resolve() 包装

优点:解决了回调地狱的问题

 ajax('XXX1')
        .then(res => {
            // 操作逻辑
            return ajax('XXX2')
        }).then(res => {
            // 操作逻辑
            return ajax('XXX3')
        }).then(res => {
            // 操作逻辑
        })

缺点:无法取消 Promise ,错误需要通过回调函数来捕获

3. Generator

特点:可以控制函数的执行,可以配合 co 函数库使用

function* fetch() {
        yield ajax('XXX1', () => { })
        yield ajax('XXX2', () => { })
        yield ajax('XXX3', () => { })
    }
    let it = fetch()
    let result1 = it.next()
    let result2 = it.next()
    let result3 = it.next()

4. Async / await
async、await 是异步的终极解决方案

优点是:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题

缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await 会导致性能上的降低。85.说几条写JavaScript的基本规范? 

13.什么是 “use strict”; ? 使用它的好处和坏处分别是什么?

ECMAScript5中引入的严格模式,通过让JavaScript运行环境对一些开发过程中最常见和不易发现的错误做出和当前不同的处理,来让开发者拥有一个”更好”的JavaScript语言。

好处:

  1. 意外的全局变量--(第一种情况是我们由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。)
  2. 消除Javascript语法的一些不合理、不严谨之处;
  3. 提高编译器效率,增加运行速度;
  4. 防止意外为全局变量赋值
  5. 防止重名
  6. 对只读属性修改时抛出异常

坏处:

  1. 一些在“正常模式”下可以运行的语句,在“严格模式”下将不能运行;
  2. 同样的代码,在“严格模式”中,可能会有不一样的运行结果;
"use strict"是ECMAscript5添加的严格运行模式、这种模式使得JavaScript在更严格的条件下运行、使JS编码更加规范化的模式,消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为。
提高编译器效率,增加运行速度;
为未来新版本的Javascript标准化做铺垫。

你可能感兴趣的:(javascript,开发语言,ecmascript)