js面试知识点总结

1,203 阅读37分钟

1.介绍 js 的基本数据类型

js 一共有六种基本数据类型,分别是 Undefined、Null、Boolean、Number、String,还有在 ES6 中新增的 Symbol 类型

2.介绍 js 有哪些内置对象?

js 中的内置对象主要指的是在程序执行前存在全局作用域里的由 js 定义的一些全局值属性、函数和用来实例化其他对象的构造函数对象。一般我们经常用到的如全局变量值 NaN、undefined,全局函数如 parseInt()、parseFloat() 用来实例化对象的构造函数如 Date、Object 等,还有提供数学计算的单体内置对象如 Math 对象。

3.null 和 undefined 的区别?

首先 Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。
undefined 代表的含义是未定义,null 代表的含义是空对象。一般变量声明了但还没有定义的时候会返回 undefined,null
主要用于赋值给一些可能会返回对象的变量,作为初始化。

4.JavaScript 原型,原型链? 有什么特点?

在 js 中我们是使用构造函数来新建一个对象的,每一个构造函数的内部都有一个 prototype 属性值,这个属性值是一个对象,当我们使用构造函数新建一个对象后,在这个对象的内部将包含一个指针,这个指针指向构造函数的 prototype 属性对应的值,在 ES5 中这个指针被称为对象的原型
当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链
特点:
JavaScript 对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。

5.js 获取原型的方法?

p.proto
p.constructor.prototype
Object.getPrototypeOf(p)

6.javascript 创建对象的几种方式?

  1. 第一种是工厂模式。
  2. 第二种是构造函数模式。
  3. 第三种模式是原型模式
  4. 第四种模式是组合使用构造函数模式和原型模式,
  5. 第五种模式是动态原型模式
  6. 第六种模式是寄生构造函数模式

7.JavaScript 继承的几种实现方式?

我了解的 js 中实现继承的几种方式有:
(1)第一种是以原型链的方式来实现继承
(2)第二种方式是使用借用构造函数的方式
(3)第三种方式是原型式继承
(4)第四种方式是寄生式继承
(5)第五种方式是组合继承
(6)第六种方式是寄生式组合继承

8.谈谈 This 对象的理解

this的指向不是在编写时确定的,⽽是在执⾏时确定的,同时,this不同的指向在于遵循了⼀定的规则。
⾸先,在默认情况下,this是指向全局对象的,⽐如在浏览器就是指向window。

name = "Bale";
function sayName (){
  console.log(this.name);
};
sayName(); //"Bale"

 其次,如果函数被调⽤的位置存在上下⽂对象时,那么函数是被隐式绑定的。

function f() {
console.log( this.name );}
var obj = {name: "Messi",f: f};
obj.f(); //被调⽤的位置恰好被对象obj拥有,因此结果是Messi

再次,显示改变this指向,常⻅的⽅法就是call、apply、bind
以bind为例

function f() {
console.log( this.name );
}
var obj = {name: "Messi",};
var obj1 = {name: "Bale"};
f.bind(obj)();
//Messi ,由于bind将obj绑定到f函数上后返回⼀个新函数,因此需要再在后⾯加上括号进⾏执⾏,
这是bind与apply和call的区别

最后,也是优先级最⾼的绑定 new 绑定。
⽤ new 调⽤⼀个构造函数,会创建⼀个新对象, 在创造这个新对象的过程中,新对象会⾃动绑定到Person对象的this上,那么 this ⾃然就指向这个新对象。

function Person(name) {
this.name = name;
console.log(name);}
var person1 = new Person('Messi'); 
//Messi


9.eval 是做什么的?

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

应该避免使用 eval,不安全,非常耗性能(2次,一次解析成 js 语句,一次执行)。

10.["1", "2", "3"].map(parseInt) 答案是多少?

parseInt(string,radix) 解析一个字符串,并返回一个整数。
第一次:parseInt("1",0) 
//第二参数为0相当于默认的方法,所以得到了1第二次:parseInt("2",1)
//由于第二参数为1小于2了,所以得到了NaN第三次:parseInt("3",2)
//虽然第二参数2符合条件,但是参数一3并不能当成二进制解释,所以得到了NaN
 答案 : [1,NaN,NaN]

11.什么是闭包,为什么要用它?(重点)

闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,创建的函数可以
访问到当前函数的局部变量。

(function() {
var a = 1;
function add() {
var b = 2
var sum = b + a
console.log(sum); // 3
}
add()
})()

add 函数本身,以及其内部可访问的变量,即 a = 1 ,这两个组合在⼀起就被称为闭包,仅此⽽已。
闭包作用:闭包最⼤的作⽤就是隐藏变量,闭包的⼀⼤特性就是内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回(寿命终结)了之后
基于此特性,JavaScript可以实现私有变量、特权变量、储存变量等我们就以私有变量举例,私有变量的实现⽅法很多,有靠约定的(变量名前加_),有靠Proxy代理的,也有靠Symbol这种新数据类型的。但是真正⼴泛流⾏的其实是使⽤闭包。 

function Person(){
var name = 'cxk';
this.getName = function(){
return name;
}
this.setName = function(value){
name = value;
}}
const cxk = new Person()
console.log(cxk.getName()) //cxk
cxk.setName('jntm')
console.log(cxk.getName()) //jntm
console.log(name) //name is not defined
//函数体内的 var name = 'cxk' 只有 getName 和 setName 两个函数可以访问,
外部⽆法访问,相对于将变量私有化。

12.对于 JSON 的了解?

JSON 是一种数据交换格式,基于文本,优于轻量,用于交换数据。

JSON 可以表示数字、布尔值、字符串、null、数组(值的有序序列),以及由这些值(或数组、对象)所组成的对象(字符串与
值的映射)。

JSON 使用 JavaScript 语法,但是 JSON 格式仅仅是一个文本。文本可以被任何编程语言读取及作为数据格式传递。

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

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

我了解到的几种方式是:

第一种方式是我们一般采用的是将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行。

第二种方式是给 js 脚本添加 defer 属性,这个属性会让脚本的加载与文档的解析同步解析,然后在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了 defer 属性的脚本按规范来说最后是顺序执行的,但是在一些浏览器中可能不是这样。

第三种方式是给 js 脚本添加 async 属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行 js 脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个 async 属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行。

第四种方式是动态创建 DOM 标签的方式,我们可以对文档的加载事件进行监听,当文档加载完成后再动态的创建 script 标签来引入 js 脚本

14.同步和异步的区别?

同步指的是当一个进程在执行某个请求的时候,如果这个请求需要等待一段时间才能返回,那么这个进程会一直等待下去,直到消息返回为止再继续向下执行。

异步指的是当一个进程在执行某个请求的时候,如果这个请求需要等待一段时间才能返回,这个时候进程会继续往下执行,不会阻塞等待消息的返回,当消息返回时系统再通知进程进行处理。

15.什么是浏览器的同源政策?

我对浏览器的同源政策的理解是,一个域下的 js 脚本在未经允许的情况下,不能够访问另一个域的内容。这里的同源的指的是两个域的协议、域名、端口号必须相同,否则则不属于同一个域。
同源政策主要限制了三个方面
第一个是当前域下的 js 脚本不能够访问其他域下的 cookie、localStorage 和 indexDB。

第二个是当前域下的 js 脚本不能够操作访问操作其他域下的 DOM。

第三个是当前域下 ajax 无法发送跨域请求。

同源政策的目的主要是为了保证用户的信息安全,它只是对 js 脚本的一种限制,并不是对浏览器的限制,对于一般的 img、或者script 脚本请求都不会有跨域的限制,这是因为这些操作都不会通过响应结果来进行可能出现安全问题的操作。

16.如何解决跨域问题?

(1)将 document.domain 设置为主域名,来实现相同子域名的跨域操作,这个时候主域名下的 cookie 就能够被子域名所访问。同时如果文档中含有主域名相同,子域名不同的 iframe 的话,我们也可以对这个 iframe 进行操作。

如果是想要解决不同跨域窗口间的通信问题,比如说一个页面想要和页面的中的不同源的 iframe 进行通信的问题,我们可以使用 location.hash 或者 window.name 或者 postMessage 来解决。

(2)使用 location.hash 的方法,我们可以在主页面动态的修改 iframe 窗口的 hash 值,然后在 iframe 窗口里实现监听函数来实现这样一个单向的通信。因为在 iframe 是没有办法访问到不同源的父级窗口的,所以我们不能直接修改父级窗口的 hash 值来实现通信,我们可以在 iframe 中再加入一个 iframe ,这个 iframe 的内容是和父级页面同源的,所以我们可以 window.parent.parent 来修改最顶级页面的 src,以此来实现双向通信。

(3)使用 window.name 的方法,主要是基于同一个窗口中设置了 window.name 后不同源的页面也可以访问,所以不同源的子页面可以首先在 window.name 中写入数据,然后跳转到一个和父级同源的页面。这个时候级页面就可以访问同源的子页面中 window.name 中的数据了,这种方式的好处是可以传输的数据量大。

(4)使用 postMessage 来解决的方法,这是一个 h5 中新增的一个 api。通过它我们可以实现多窗口间的信息传递,通过获取到指定窗口的引用,然后调用 postMessage 来发送信息,在窗口中我们通过对 message 信息的监听来接收信息,以此来实现不同源间的信息交换。

如果是像解决 ajax 无法提交跨域请求的问题,我们可以使用 jsonp、cors、websocket 协议、服务器代理来解决问题。

(5)使用 jsonp 来实现跨域请求,它的主要原理是通过动态构建 script 标签来实现跨域请求,因为浏览器对 script 标签的引入没有跨域的访问限制 。通过在请求的 url 后指定一个回调函数,然后服务器在返回数据的时候,构建一个 json 数据的包装,这个包装就是回调函数,然后返回给前端,前端接收到数据后,因为请求的是脚本文件,所以会直接执行,这样我们先前定义好的回调函数就可以被调用,从而实现了跨域请求的处理。这种方式只能用于 get 请求。

(6)使用 CORS 的方式,CORS 是一个 W3C 标准,全称是"跨域资源共享"。CORS 需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,因此我们只需要在服务器端配置就行。浏览器将 CORS 请求分成两类:简单请求和非简单请求。对于简单请求,浏览器直接发出 CORS 请求。具体来说,就是会在头信息之中,增加一个 Origin 字段。Origin 字段用来说明本次请求来自哪个源。服务器根据这个值,决定是否同意这次请求。对于如果 Origin 指定的源,不在许可范围内,服务器会返回一个正常的 HTTP 回应。浏览器发现,这个回应的头信息没有包含 Access-Control-Allow-Origin 字段,就知道出错了,从而抛出一个错误,ajax 不会收到响应信息。如果成功的话会包含一些以 Access-Control- 开头的字段。

非简单请求,浏览器会先发出一次预检请求,来判断该域名是否在服务器的白名单中,如果收到肯定回复后才会发起请求。

(7)使用 websocket 协议,这个协议没有同源限制。

(8)使用服务器来代理跨域的访问请求,就是有跨域的请求操作时发送请求给后端,让后端代为请求,然后最后将获取的结果发返回。

17.js 的几种模块规范?

js 中现在比较成熟的有四种模块加载方案。
  1. 第一种是 CommonJS 方案
  2. 第二种是 AMD 方案
  3. 第三种是 CMD 方案
  4. 第四种方案是 ES6 提出的方案,使用 import 和 export 的形式来导入导出模块。

18.AMD 和 CMD 规范的区别?

它们之间的主要区别有两个方面。

(1)第一个方面是在模块定义时对依赖的处理不同。AMD 推崇依赖前置,在定义模块的时候就要声明其依赖的模块。而 CMD 推崇 就近依赖,只有在用到某个模块的时候再去 require。

(2)第二个方面是对依赖模块的执行时机处理不同。首先 AMD 和 CMD 对于模块的加载方式都是异步加载,不过它们的区别在于 模块的执行时机,AMD 在依赖模块加载完成后就直接执行依赖模块,依赖模块的执行顺序和我们书写的顺序不一定一致。而 CMD 在依赖模块加载完成后并不执行,只是下载而已,等到所有的依赖模块都加载好后,进入回调函数逻辑,遇到 require 语句 的时候才执行对应的模块,这样模块的执行顺序就和我们书写的顺序保持一致了。

// CMD
define(function(require, exports, module) {
  var a = require("./a");
  a.doSomething();
  // 此处略去 100 行
  var b = require("./b"); // 依赖可以就近书写
  b.doSomething();
  // ...
});

// AMD 默认推荐
define(["./a", "./b"], function(a, b) {
  // 依赖必须一开始就写好
  a.doSomething();
  // 此处略去 100 行
  b.doSomething();
  // ...
});

19.简单介绍一下 JS的垃圾回收机制?

  Js具有自动垃圾回收机制。垃圾收集器会按照固定的时间间隔周期性的执行。JS中最常见的垃圾回收方式是标记清除

工作原理:是当变量进入环境时,将这个变量标记为“进入环境”。当变量离开环境时,则将其标记为“离开环境”。标记“离开环境”的就回收内存

工作流程: 

 1. 垃圾回收器,在运行的时候会给存储在内存中的所有变量都加上标记。 

 2. 去掉环境中的变量以及被环境中的变量引用的变量的标记。 

 3. 再被加上标记的会被视为准备删除的变量。 

 4. 垃圾回收器完成内存清除工作,销毁那些带标记的值并回收他们所占用的内存空间。 

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

1.意外的全局变量引起的内存泄漏。 

 原因:全局变量,不会被回收。 

 解决:使用严格模式避免。 

 2. 闭包引起的内存泄漏 

 原因:闭包可以维持函数内局部变量,使其得不到释放。 

解决:将事件处理函数定义在外部,解除闭包,或者在定义事件处理函数的外部函数中,删除对dom的引用。 

 3. 没有清理的DOM元素引用 

原因:虽然别的地方删除了,但是对象中还存在对dom的引用 

 解决:手动删除。 

 4. 被遗忘的定时器或者回调 

原因:定时器中有dom的引用,即使dom删除了,但是定时器还在,所以内存中还是有这个dom。 

 解决:手动删除定时器和dom。 

 5. 子元素存在引用引起的内存泄漏 

 原因:div中的ul li 得到这个div,会间接引用某个得到的li,那么此时因为div间接引用li,即使li被清空,也还是在内存中,并且只要li不被删除,他的父元素都不会被删除。

 解决:手动删除清空。 

21.js 中的深浅拷贝实现?

// 浅拷贝的实现;

function shallowCopy(object) {
  // 只拷贝对象
  if (!object || typeof object !== "object") return;

  // 根据 object 的类型判断是新建一个数组还是对象
  let newObject = Array.isArray(object) ? [] : {};

  // 遍历 object,并且判断是 object 的属性才拷贝
  for (let key in object) {
    if (object.hasOwnProperty(key)) {
      newObject[key] = object[key];
    }
  }

  return newObject;
}

// 深拷贝的实现;

function deepCopy(object) {
  if (!object || typeof object !== "object") return;

  let newObject = Array.isArray(object) ? [] : {};

  for (let key in object) {
    if (object.hasOwnProperty(key)) {
      newObject[key] =
        typeof object[key] === "object" ? deepCopy(object[key]) : object[key];
    }
  }

  return newObject;
}
浅拷贝指的是将一个对象的属性值复制到另一个对象,如果有的属性的值为引用类型的话,那么会将这个引用的地址复制给对象,因此两个对象会有同一个引用类型的引用。浅拷贝可以使用 Object.assign 和展开运算符来实现。

深拷贝相对浅拷贝而言,如果遇到属性值为引用类型的时候,它新建一个引用类型并将对应的值复制给它,因此对象获得的一个新的引用类型而不是一个原有类型的引用。深拷贝对于一些对象可以使用 JSON 的两个函数来实现,但是由于 JSON 的对象格式比 js 的对象格式更加严格,所以如果属性值里边出现函数或者 Symbol 类型的值时,会转换失败。

22.call、apply、bind区别

call 接收多个参数,第一个为函数上下文也就是this,后边参数为函数本身的参数。

apply接收两个参数,第一个参数为函数上下文this,第二个参数为函数参数只不过是通过一个数组的形式传入的。

bind 接收多个参数,第一个是bind返回值返回值是一个函数上下文的this,不会立即执行。

23.聊聊es6的promise

Promise 是一种对异步操作的封装,可以通过独立的接口添加在异步操作执行成功、失败时执行的方法。主流的规范是 Promises/A+。

Promise中有几个状态:

* pending: 初始状态, 非 fulfilled 或 rejected;

* fulfilled: 成功的操作,为表述方便,fulfilled 使用 resolved 代替;

* rejected: 失败的操作。

24.一个页面从输入URL到页面加载显示完成,这个过程中都发生了什么?

  1. 域名解析
  2. 发起TCP的3次握手
  3. 建立TCP连接后发起http请求
  4. 服务器端响应http请求,浏览器得到html代码
  5. 浏览器解析html代码,并请求html代码中的资源

25.var let const的区别

  1. let
    • let 只在let命令所在的代码块内有效。
    • 不存在变量提升。
    • 暂时性死区(只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。)
    • 不允许在相同作用域内,重复声明同一个变量。
    var
    • var命令声明的,在全局范围内都有效
    • 会发生变量提升
    • 可以重复定义
    • 没有块级作用域
    const
    • 声明一个只读的常量。一旦声明,常量的值就不能改变。
    • 一旦声明变量,就必须立即初始化,
    • 只在声明所在的块级作用域内有效。
    • 不存在变量提升。
    • 不可重复声明

26.数组去重(经常问)

// 数组去重 es5 方法

var arr = [1, 1, 2, 3, 3, 3, 5, 6, 6, 6, 6, 3]
var newArry = [];
for (var i = 0; i < arr.length; i++) {
       if(newArry.indexOf(arr[i])==-1)
         {newArry.push(arr[i])
            }
          }
console.log(newArry,'wwwww')

//es6方法

var arr = [1, 1, 2, 3, 3, 3, 5, 6, 6, 6, 6, 3]
var newArry = new Set(arr);
var filterArry = Array.from(newArry)
console.log(filterArry ,'sss') 
//[1, 2, 3, 5, 6] "sss"

27.获取并解析当前页面的url中的参数

 function getUrlkey(url) {        
  var params = {} // 定义空对象        
   var arr = url.split('?') //分割字符串        
   console.log(arr,'ssss') 
   //["http://www.chenwenbo.info","key0=0&key1=1&key2=2"] "ssss 
    if (arr.length <= 1 ) 
      return params;
      arr = arr[1].split("&"); 
    // 分割第二个数组下的字符串        
     console.log(arr,'xxx') 
    // ["key0=0", "key1=1", "key2=2"] "xxx        
    for (var i = 0 ; i < arr.length; i++) {            
        var a = arr[i].split("=");            
        console.log(a,'aaaav')            
        params[a[0]] = a[1];        
     }        
     return params;    
   }    
var url = "http://www.chenwenbo.info?key0=0&key1=1&key2=2",    
ps = getUrlkey(url);  
console.log(ps); 
//{key0: "0", key1: "1", key2: "2"}

28.去掉变量 var str = ' Langer Beijing com! '中的所有空格

// 数组去重 es6 方法
 var str = ' Langer Beijing com!  '   
 NewStr = str.replace(/\s*/g,""); 
 console.log(NewStr,'NewStr')//八s*/g

29.判断一个字符串出现个最多字符 并判断出个数

 var obj = 'aaaabbbcadadvvvddssssss';
 var i;    
var tempObj = {};    
for (var i = 0; i < obj.length; i++) {
        var charAt = obj.charAt(i);
         if (tempObj[charAt]) {
            tempObj[charAt]++        
        } else {
            tempObj[charAt] = 1        
}    
}    
console.log(tempObj) //{a: 6, b: 3, c: 1, d: 4, v: 3}   
   
var max = 0;//初始化一个最大数    
var maxStr;    
var obj1;    
for (obj1 in tempObj) {        
if (tempObj[obj1] > max) {            
max = tempObj[obj1];            
maxStr = obj1;        
}    
}    console.log(maxStr + ':' + max); //a : 6

30.数组排序

var arr = [1, 2, 5, 6, 43, 111, 3]
for (var i = 0; i < arr.length; i++) {       
for (var j = i; j < arr.length; j++){            
if (arr[i] < arr[j]) {               
 max = arr[j];                
arr[j] = arr[i];                
arr[i] = max;            
   }        
  }    
 }
console.log(arr, '由大到小排序')    
var min;   
for (var i = 0; i < arr.length; i++) {       
for (var j = i; j < arr.length; j++){           
if (arr[i] > arr[j]) {              
min = arr[j];               
arr[j] = arr[i]               
arr[i] = min;          
   }       
  }   
 }
console.log(arr,'由小到大')

31.箭头函数和普通函数的区别

  1. 箭头函数就是没有function关键字,而是一个类似箭头的函数:
  2. 箭头函数作为匿名函数,是不能作为构造函数的,不能使用new
  3. 箭头函数不绑定arguments,取而代之用rest参数…解决
  4. 箭头函数会捕获其所在上下文的 this 值,作为自己的 this 值
  5. 箭头函数没有原型属性
  6. 箭头函数不能换行

32.实现数组的深拷贝

// es6 方法实现深拷贝    
    let arr1 = [1,2,3,4,5];    
    let arr2 = [...arr1];    
 console.log(arr2,'ssss')   
// es5 slice 方法    
   var arr1 = [1,2,3,4,5];    
   var arr2 = arr1.slice(0)    
console.log(arr2,'es6')    
// es5 concat 方法    
   var arr1 = [1,2,3,4,5];    
  var arr2 = arr1.concat()    
console.log(arr2,'ssss')

33.get和post 区别

  1. 使用Get请求时,参数在URL中显示,而使用Post方式,则不会显示出来
  2. 使用Get请求发送数据量小,Post请求发送数据量大
  3. get请求需注意缓存问题,post请求不需担心这个问题
  4. 发送请求时,因为get请求的参数都在url里,所以send函数发送的参数为null,而post请求在使用send方法时,却需赋予其参数
  5. get请求是带着参数的,post请求的url则不带.
  6. Get请求的目的是给予服务器一些参数,以便从服务器获取列

34.求数组的最值

var arr = [3,43,23,45,65,90];
var max = Math.max.apply(null,arr);
console.log(max);

35.数组的反转

var arr = [1,2,3,4];
var arr2 = arr1.reverse()
console.log(arr2)
// [1,2,3,4]

36.求数组的和?

var arr1=[1,2,3,4,5,6,7,8,9];
var sum1=0;
for (var i=0;i<=arr1.length;i++) {if (typeof arr1[i]=="number") {      
sum1+=arr1[i];      
}}
console.log(sum1)

37.for循环闭包组合(被问过2次)

    1,对于一个基本的for循环,顺序输出变量值。

  for(var i = 1; i < 4; i++){
            console.log(i);//结果不多说了吧
        }
// 0 1 2 3

   2,如果for循环中有定时器,如下代码。

      for (var i = 1; i < 4; i++) {
            setTimeout(function() {
                console.log(i);//3个4
            }, 3000);
        }
 // 输出3个4

 初衷想要3s后输出1,2,3。但是3s后,输出3个4。原因是定时器的异步执行,for循环的执行速度很快,当真正执行到函数体时,此时i早已变成4,所以结果不想而知。

 3,如果要得到正确结果,就要引入闭包来保存变量i不被销毁。

for (var i = 1; i < 4; i++) {
            (function(a) {
                setTimeout(function() {
                    console.log(a);//操纵变量a,和i无关
                }, 3000);
            })(i)
        }

这样引入闭包,变量i保存下来,3s后函数体执行,输出1,2,3.

  4,如果要实现,没隔3s输出一个数字,即,3s输出1,3s后再输出2...,就要对定时器时间设置

        for (var i = 1; i < 4; i++) {
            (function(a) {
                setTimeout(function() {
                    console.log(a);
                }, a*3000); //.....
            })(i)
        }

      实际上,for循环很快,上述代码类似于同时启动3个定时器,只需要确保时间不一样即可。在此,时间分别是3s,6s,9s,由于同时启动,但是执行时间不同,各个时间间隔都是3s。巧妙地达到了目的。

38.什么是深拷贝,什么是浅拷贝

深拷贝,就是在对某个对象进行拷贝的时候,如果这个对象持有其他对象的引用,在拷贝的时候会将要拷贝的对象以及引用的对象,一起拷贝。

而浅拷贝只拷贝当前对象和持有的索引,不拷贝索引指向的对象。举个例子说明一下,比如当前有个列表a = [1,2,3], b = [3,4,a],[3,4,a]对象持有了[1,2,3]对象的引用,在对b进行深拷贝的时候,会将a对象一起拷贝一份,而浅拷贝的时候则不会。


a = [1,2,3]

b = [4,5,6,a]

对b进行浅拷贝

c = copy.copy(b)

这个时候对a对象进行修改,会影响c

a.append(8)
 c
[4, 5, 6, [1, 2, 3, 8]]

对b进行深拷贝之后,再对a进行修改,则不会影响到d

d = copy.deepcopy(b)

d
[4, 5, 6, [1, 2, 3, 8]]

a.append(66)

 d
 [4, 5, 6, [1, 2, 3, 8]

38.将数组扁平化并去除其中重复数据,最终得到一个升序且不重复的数组

已知如下数组: var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10]; 编写一个程序将数组扁平化去并除其中重复部分数据,最终得到一个升序且不重复的数组

let newArr = [...new Set(arr.flat(Infinity)].sort((a,b)=>a-b)
console.log(newArr) 
//[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

39.使用 sort() 对数组 [3, 15, 8, 29, 102, 22] 进行排序,输出结果

let arr = [3, 15, 8, 29, 102, 22]
arr.sort((a, b)=>{
    return a-b
})
// [3, 8, 15, 22, 29, 102]

40.给定两个数组,写一个方法来计算它们的交集

var nums1 = [1, 2, 2, 1], nums2 = [2, 2, 3, 4];
var newArr2 = nums1.filter((item) => {
     return nums2.includes(item);
});
console.log(newArr2); // [2,2]

41.如何把一个字符串的大小写取反(大写变小写小写变大写),例如 ’AbC' 变成 'aBc' 。

function processString (s) {
    var arr = s.split('');
    var new_arr = arr.map((item) => {
        return item === item.toUpperCase() ? item.toLowerCase() : item.toUpperCase();
    });
    return new_arr.join('');
}
console.log(processString('AbC'));

42.javascript让(a==1&&a==2&&a==3)为true

1、改写valueOf

let a = {
    i : 1,
    valueOf: function(){
        return a.i++
    }
}
console.log(a == 1 && a == 2 && a == 3);

2、改写toString:

let a = {
 toString: (function() {
 let i = 1;
 //闭包的特性之一:i 不会被回收
 return function() {
   return i++;
 }
 })()
}
console.log(a == 1 && a == 2 && a == 3); //true

3、重写数组的 join 方法

let a = [1, 2, 3];
a.join = a.shift;
console.log(a == 1 && a == 2 && a == 3); //true

43.闭包

// ====== 例子1 ======
for (var i = 0; i < 10; i++) {
  setTimeout (function () {
    console.log (i); 
  }, 1000);
}
// 10 10 10 ....


// ====== 例子2 ======
for (var i = 0; i < 10; i++) {
  (function (e) {
    setTimeout (function () {
      console.log (e);
    }, 1000);
  })(i);
}
// 1 2 3 4 5 6 7....


// ====== 例子3 ======
for (var i = 0; i < 10; i++) {
    setTimeout (function (i) {
        console.log (i); 
    }, 1000);
}
// undefined undefined undefined ... 


// ====== 例子4 ======
var x = 'global'
function test1() {
    console.log(x)
}
function test2() {
    x = 'local'
    test1()
}
test2()
// local


// ====== 例子5 ======
var x = 'global'
function test1() {
    console.log(x)
}
function test2() {
    var x = 'local'
    test1()
}
test2()

44.作用域

  • 分为:全局作用域(定义在函数外部的变量)和局部作用域(定义在函数内部的变量),每个函数在被调用时都会创建一个新的域。ECMAScript 6引入了let和const关键字,利用let和const可以形成块级作用域。
  • 块语句(if, switch, for, while)不会创建新的作用域,定义的变量保存在已经存在的作用域中,let和const支持在局部作用域块语句中声明

if (true) {    
     var a = 1;    
     let b = 2;    
     const c =3;
}
console.log(a) // 1
console.log(b) // Uncaught ReferenceError: b is not defined
console.log(c) // Uncaught ReferenceError: c is not defined

作用域链:作用域链是在变量对象之后创建的,作用域链用于解析变量,当变量被解析时,javascript先从代码嵌套的最内层开始找,如果内层没有找到,会转到上一层父级作用域查找,直到找到该变量

45.节流和防抖

节流:高频事件触发n秒内执行一次,如果这个时间点内触发多次函数,只有一次生效。

   function throttle(fn) {        
           var flag = true       
           return function() {            
               if (!flag) return;            
               flag = false;            
               setTimeout(function () {
                fn()                
                flag = true            
                }, 1000)         
              }    
         }

防抖:高频事件触发n秒之后执行,如果n秒之内再次被触发,重新记时。

function debounce(fn) {
 var timeout = null;
return function() {
       clearTimeout(timeout)
        timeout = setTimeout(function (){
            fn()
        }, 1000)
    }
}

46.get和post区别

  1. 最直观的区别get把参数包含在URL中,post通过request body传递参数,相对于get比较安全
  2. get请求URL传参有长度限制,post没有
  3. get在浏览器回退是无害的,post会再次提交请求
  4. get请求会被浏览器主动缓存,post不会
  5. get和post报文格式不同
  6. get请求是幂等性的,而post请求不是(新增和删除数据一般不用post请求就是这个原因)

47..Js的事件委托是什么,原理是什么

  • 通俗点说将元素事件委托给他的父级或者更外级来处理
  • 事件委托是利用冒泡机制来实现的(事件冒泡:事件由具体的元素接受,然后逐渐向上传播到不具体的节点)

// 每个列表点击弹出内容
// 不使用事件委托需要给每个列表添加点击事件(消耗内存,不灵活,添加动态元素时需要重新绑定事件)
   这里不做介绍
<ul id="myLink">
  <li id="1">aaa</li>  
  <li id="2">bbb</li>
<li id="3">ccc</li>
</ul>
// 使用事件委托(减少内存占用,提升性能,动态添加元素无需重新绑定事件)
var myLink = document.getElementById('myLink');
myLink.onclick = function(e) {
  var e = event || window.event;
  var target = e.target || e.srcElement;
  if(target.nodeName.toLowerCase() == 'li') {
    alert(target.id + ':' + target.innerText);
  }
};

48.什么是原型链

  • 原型:每个javascript创建的时候都会关联另一个对象,这个对象就是原型,对象会从原型继承属性
  • 构造函数可以通过prototype去寻找他关联的原型,A.prototype就是它关联的原型对象,原型对象可以通过构造器constructor来寻找与自身关联的构造函数

function A () {
}
A.prototype.constructor === A //true

  • 原型链:原型链是由原型对象组成,每个对象都有__proto__属性,指向该构造函数的原型,__proto__将对象连接起来组成了原型链
  • 原型链查找机制:当查找对象的属性时,如果实例对象不存在该属性,沿着原型链向上一级查找,直到找到object.prototype(也就是对象原型object.prototype为null),停止查找到返回undefined
function A () {
}
new A().__proto__ === A.prototype //true

原型上的属性和方法被实例共享

function A () {
}A.prototype.name = 'a'
var a = new A()
var b = new A()
a.name === b.name // true
a.__proto__.name === b.__proto__.name // true

instanceOf原理:instamceOf可以判断实例对象的__proto__属性与构造函数的prototype是不是同一地址(如果网页中有多个全局环境就会不准确)

function _instanceOf(obj, type) {
    var obj = obj.__proto__
    var type = type.prototype
    while(true) {
        if (obj == null) {
            return false
        }
        if (obj == type) {
            return true
        }
        obj = obj.__proto__
    }
}
var a = [1, 2, 3]
_instanceOf(a, Array)

49.深拷贝和浅拷贝

浅拷贝只是复制引用,新旧对象共享一块内存,一般把第一层拷贝到一个对象上,改变其中一个另一个也会改变

var obj = {      
    name: "a",
    age: 18,      
    arr: [1, 2]    
   };    
function shallowCopy(obj) {
      var newObj = {};      
      for (var prop in obj) {        
       if (obj.hasOwnProperty(prop)) {
          // 过滤掉原型上的属性          
          newObj[prop] = obj[prop];        
           }      
       }      
      return newObj;    
  }    
var obj1 = shallowCopy(obj);  
var obj2 = obj;    
obj1.name = "b";   
obj2.age = 20;  
obj2.arr[0] = 3;    
obj1.arr[0] = 4;    
console.log(obj); // {name: "a", age: 20, arr: [4, 2]}    
console.log(obj1); // {name: "b", age: 18, arr: [4, 2]}    
console.log(obj2); // {name: "a", age: 20, arr: [4, 2]}

我们通过浅拷贝得到obj1,改变obj1的name,obj不会发生改变,通过赋值得到obj2,obj2改变age值obj的值也会被改变。说明赋值得到的对象只是改变了指针,浅拷贝是创建了新对象。

我们通过obj2和obj1都改变的值,发现obj,ob1,obj2都发生了改变,所以无论是浅拷贝还是赋值都会改变原始数据(浅拷贝只拷贝了一层对象的属性)

深拷贝:复制并创建一个一摸一样的对象(递归复制了所有层级),不共享内存,改变其中一个另一个不会改变

var obj = {   
name: "a",   
age: 18,   
arr: [1, 2] 
}; 
function copy(obj) {
    var newobj = Array.isArray(obj) ? [] : {};
    if (typeof obj !== "object") {
       return;     
}  for (var i in obj) {
     if (obj.hasOwnProperty(i)) {
       newobj[i] = typeof obj[i] === "object" ? copy(obj[i]) : obj[i];      
}   
}   return newobj; 
}  
var copyObj = copy(obj);  
copyObj.arr[0] = 3;  
console.log(copyObj);  
//{name: "a", age: 20, arr: [3, 2]}  
console.log(obj);  
//{name: "a", age: 20, arr: [1, 2]}

50.实现继承

  • 原型继承
  • 构造继承
  • 实例继承
  • call/apply继承(组合继承)
  • ES6 使用class extends继承

51.如何判断一个对象是数组

面试官希望的答案: Object.prototype.toString.call([]) 返回 "[object Array]"

扩展答案

  • [].slice (能力判断 )
  • [] instanceof Array(类型判断)
  • [].__proto__ === Array.prototype

    在考虑兼容性的情况下可以⽤toString的⽅法 
    if(!Array.isArray){
    Array.isArray = function(arg){
    return Object.prototype.toString.call(arg)==='[object Array]'
     } 
    } 
    

  • Array.isArray([]) 

    //es6中加⼊了新的判断⽅法 
    if(Array.isArray(value)){ return true; } 
    

52.谈谈你对原型链的理解?

原型对象:绝⼤部分的函数(少数内建函数除外)都有⼀个 prototype 属性,这个属性是原型对象⽤来创建新对象实例,⽽所有被创建的对象都会共享原型对象,因此这些对象便可以访问原型对象的属性。

例如 hasOwnProperty() ⽅法存在于Obejct原型对象中,它便可以被任何对象当做⾃⼰的⽅法使⽤

⽤法: object.hasOwnProperty( propertyName ) hasOwnProperty() 
函数的返回值为 Boolean 类型。如果对象 object 具有名称为 propertyName 的属性,
则返 回 true ,否则返回 false

var person = { 
name: "Messi", 
age: 29, 
profession: "football player" 
}; 
console.log(person.hasOwnProperty("name")); //true 
console.log(person.hasOwnProperty("hasOwnProperty")); //false 
console.log(Object.prototype.hasOwnProperty("hasOwnProperty")); //true 

由以上代码可知, hasOwnProperty() 并不存在于 person 对象中,但是 person 依然可以拥有此⽅法.所以 person 对象是如何找到 Object 对象中的⽅法的呢?靠的是原型链。

原型链:原因是每个对象都有 __proto__ 属性,此属性指向该对象的构造函数的原型。对象可以通过 __proto__ 与上游的构造函数的原型对象连接起来,⽽上游的原型对象也有⼀个 __proto__ ,这样就形成了原型链。 

53.那么箭头函数的this指向哪⾥?

箭头函数不同于传统JavaScript中的函数,箭头函数并没有属于⾃⼰的this,它的所谓的this是捕获其所在上下⽂的 this值,作为⾃⼰的 this 值,并且由于没有属于⾃⼰的this,⽽箭头函数是不会被new调⽤的,这个所谓的this也不会被改变.我们可以⽤Babel理解⼀下箭头函数: 

// ES6const obj = {getArrow() 
{return () => {console.log(this === obj);  
  }; 
  }
 }

转化后

// ES5,由 Babel 转译 
var obj = {getArrow: function getArrow() 
{var _this = this;return function () 
{console.log(_this === obj);    
  };  
}}

54.async/await是什么? 

async 函数,就是 Generator 函数的语法糖,它建⽴在Promises上,并且与所有现有的基于Promise的API兼容。
1. Async—声明⼀个异步函数(async function someName(){...})
2. ⾃动将常规函数转换成Promise,返回值也是⼀个Promise对象
3. 只有async函数内部的异步操作执⾏完,才会执⾏then⽅法指定的回调函数
4. 异步函数内部可以使⽤await
1. Await—暂停异步的功能执⾏(var result = await someAsyncCall();)
2. 放置在Promise调⽤之前,await强制其他代码等待,直到Promise完成并返回结果
3. 只能与Promise⼀起使⽤,不适⽤与回调
4. 只能在async函数内部使⽤

55.async/await相⽐于Promise的优势? 

代码读起来更加同步,Promise虽然摆脱了回调地狱,但是then的链式调⽤也会带来额外的阅读负担

Promise传递中间值⾮常麻烦,⽽async/await⼏乎是同步的写法,⾮常优雅

错误处理友好,async/await可以⽤成熟的try/catch,Promise的错误捕获⾮常冗余

调试友好,Promise的调试很差,由于没有代码块,你不能在⼀个返回表达式的箭头函数中设置断点,如果你在⼀个.then代码块中使⽤调试器的步进(step-over)功能,调试器并不会进⼊后续的.then代码块,因为调试器只能跟踪同步代码的『每⼀步』。

56.JavaScript的参数是按照什么⽅式传递的?

基本类型传递⽅式
由于js中存在复杂类型和基本类型,对于基本类型⽽⾔,是按值传递的.

var a = 1;function test(x) {
x = 10;
console.log(x);
}
test(a); // 10
console.log(a); // 1

 虽然在函数 test 中 a 被修改,并没有有影响到 外部 a 的值,基本类型是按值传递的.

复杂类型按引⽤传递 

我们将外部 a 作为⼀个对象传⼊ test 函数.

var a = { a: 1,   b: 2};
function test(x) {
x.a = 10;
console.log(x);
}
test(a); // { a: 10, b: 2 }
console.log(a); // { a: 10, b: 2 }

 可以看到,在函数体内被修改的 a 对象也同时影响到了外部的 a 对象,可⻅复杂类型是按引⽤传递的.

可是如果再做⼀个实验:

var a = { a: 1, b: 2 };
function test(x) {
x = 10;
 console.log(x);
}test(a); 
// 10
console.log(a); // { a: 1, b: 2 }

 外部的 a 并没有被修改,如果是按引⽤传递的话,由于共享同⼀个堆内存, a 在外部也会表现为 10 才对. 此时的复杂类型同时表现出了 按值传递 和 按引⽤传递 的特性. 

57.什么是变量提升?

JavaScript引擎的⼯作⽅式是,先解析代码,获取所有被声明的变量,然后再⼀⾏⼀⾏地运⾏。这造成的结果,就是所
有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升(hoisting)。

console.log(a) // undefined
var a = 1
function b() {  
console.log(a)
}
b() // 1

上⾯的代码实际执⾏顺序是这样的:
第⼀步: 引擎将 var a = 1 拆解为 var a = undefined 和 a = 1 ,并将 var a = undefined 放到最顶端, a = 1 还在原
来的位置
这样⼀来代码就是这样:

var a = undefined
console.log(a) // undefined
a = 1
function b() {
console.log(a)
}
b() // 1

 第⼆步就是执⾏,因此js引擎⼀⾏⼀⾏从上往下执⾏就造成了当前的结果,这就叫变量提升。 

58.JavaScript的作⽤域链理解?

JavaScript属于静态作⽤域,即声明的作⽤域是根据程序正⽂在编译时就确定的,有时也称为词法作⽤域。
其本质是JavaScript在执⾏过程中会创造可执⾏上下⽂,可执⾏上下⽂中的词法环境中含有外部词法环境的引⽤,我们可以通过这个引⽤获取外部词法环境的变量、声明等,这些引⽤串联起来⼀直指向全局的词法环境,因此形成了作⽤域链。

59.ES6模块与CommonJS模块有什么区别?

ES6 Module和CommonJS模块的区别:
CommonJS是对模块的浅拷⻉,ES6 Module是对模块的引⽤,即ES6 Module只存只读,不能改变其值,具体点就是指针指向不能变,类似const
import的接⼝是read-only(只读状态),不能修改其变量值。 即不能修改其变量的指针指向,但可以改变变量内部指针指向,可以对commonJS对重新赋值(改变指针指向),但是对ES6 Module赋值会编译报错。ES6 Module和CommonJS模块的共同点:
CommonJS和ES6 Module都可以对引⼊的对象进⾏赋值,即对对象内部属性的值进⾏改变。 

60.为什么0.1+0.2为什么不等于0.3?

JS 的 Number 类型遵循的是 IEEE 754 标准,使⽤的是 64 位固定⻓度来表示。
IEEE 754 浮点数由三个域组成,分别为 sign bit (符号位)、exponent bias (指数偏移值) 和 fraction (分数值)。64 位中,
sign bit 占 1 位,exponent bias 占 11 位,fraction 占 52 位。
通过公式表示浮点数的值 value = sign x exponent x fraction
**
当⼀个数为正数,sign bit 为 0,当为负数时,sign bit 为 1.
以 0.1 转换为 IEEE 754 标准表示为例解释⼀下如何求 exponent bias 和 fraction。转换过程主要经历 3 个过程:
1. 将 0.1 转换为⼆进制表示
2. 将转换后的⼆进制通过科学计数法表示
3. 将通过科学计数法表示的⼆进制转换为 IEEE 754 标准表示
将 0.1 转换为⼆进制表示
回顾⼀下⼀个数的⼩数部分如何转换为⼆进制。⼀个数的⼩数部分,乘以 2,然后取整数部分的结果,再⽤计算后的⼩
数部分重复计算,直到⼩数部分为 0 。

61.类型转换的规则有哪些? 

在if语句、逻辑语句、数学运算逻辑、==等情况下都可能出现隐⼠类型转换。

62.map和set区别

Set类似于数组,但是它里面每一项的值是唯一的,没有重复的值,Set是一个构造函数,用来生成set的数据结构。

let s = new Set();
let arr = [2, 3, 5, 4, 5, 2, 2];
arr .forEach(item => arr.add(item)); //向set添加重复的值for (let i of s) {
  console.log(i);
}
// 2 3 5 4 结果set不会添加重复的值

Map类似于对象,也是键值对的集合,但是“键”的范围不限制于字符串,各种类型的值(包含对象)都可以当作键。Map 也可以接受一个数组作为参数,数组的成员是一个个表示键值对的数组。注意Map里面也不可以放重复的项。

let map = new Map([['js','react']]);
map.set('js','react');//看看是否可以放重复的项
map.set('javaScript','vue');
console.log(map)//Map {'js' => 'react','javaScript' => 'vue'} 不可以放重复项