前端JavaScript面试100问(上)

1、解释一下什么是闭包 ?

  • 闭包:就是能够读取外层函数内部变量的函数。
  • 闭包需要满足三个条件:
    • 访问所在作用域;
    • 函数嵌套;
    • 在所在作用域外被调用 。
  • 优点: 可以重复使用变量,并且不会造成变量污染 。
  • 缺点: 会引起内存泄漏
  • 使用闭包的注意点:
    • 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
    • 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象
      (object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

2、解释一下原型和原型链 ?

原型

原型就是一个为对象实例定义了一些公共属性和公共方法的对象模板。

原型链

对象之间的继承关系通过构造函数的prototype指向父类对象,直到指向Object对象为止形成的指向链条。

通俗讲: 原型链是原型对象创建过程的历史记录。 

注:在javascript中,所有的对象都拥有一个__proto__属性指向该对象的原型(prototype) 。

3、说一下 ES6 中你熟悉的一些内容 ?

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

  • async、await使用 async/await, 搭配promise,可以通过编写形似同步的代码来处理异步流程, 提高代码的简洁性和可读性async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成

  • Promise是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理、强大

  • Symbol是一种基本类型。Symbol 通过调用symbol函数产生,它接收一个可选的名字参数,该函数返回的symbol是唯一的

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

  • Set是类似于数组的数据集合,无序,插入删除速度快,元素不重复,查找速度快。

  • Map是一个类似对象的数据结构,和对象不同的在于它的key可以是任意类型,但是对象只能使用string和symbol类型,Map的存储关联性更强

  • 生成器函数可以进行阻断函数执行的过程,通过传参可以传入新的值进入函数继续执行,可以用于将异步变为阻塞式同步

4、数组排序的方式 ?

  • 冒泡排序:
    for(var i=0;i<arr.length-1;i++){
    
    
            for(var j=0;j<arr.length-i-1;j++){
    
    
                if(arr[j]>arr[j+1]){
    
    
                    var temp=arr[j];
                    arr[j]=arr[j+1];
                    arr[j+1]=temp;
                }
            }
        if(arr[j]===arr[j-1]) i++;
        }
  • 选择排序:
 for(var i=0;i<arr.length;i++){
    
    
            var min=i;
            for(var j=i+1;j<arr.length;j++){
    
    
                if(arr[j]<arr[min]) min=j;
            }
            if(min!==i){
    
    
                var temp=arr[i];
                arr[i]=arr[min];
                arr[min]=temp;
            }
        if(arr[i]===arr[i+1])i++;
}
  • 快速排序:
function quickSort(arr) {
    
    
   if (arr.length <= 1) return arr;
    var centerIndex = ~~(arr.length / 2);
    var left = [];
    var right = [];
    for (var i = 0; i < arr.length; i++) {
    
    
        if (i === centerIndex) continue;
        if (arr[i] < arr[centerIndex]) left.push(arr[i]);
        else right.push(arr[i]);
        }
        return quickSort(left).concat(arr[centerIndex], quickSort(right));
    }

5、什么是事件轮询(EventLoop) ?

一个用来等待和发送消息和事件的程序结构。

  • 1、所有任务都在主线程上执行,形成一个执行栈。
  • 2、主线程发现有异步任务,如果是微任务就把他放到微任务的消息队列里,如果是宏任务就把他放到宏任务的消息队列里。
  • 3、执行栈所有同步任务执行完毕。
  • 4、执行微任务队列,之后再执行宏任务队列。
  • 5、轮询第4步。

6、数组的一些API, 哪些能够改变原数组, 那些不能 ?

  • 改变原数组的方法:
shift()
unshift()
pop()
push()
reverse()
sort()
splice()
  • 不改变原数组的方法:
concat()
every()
filter()
forEach()
indexOf()
join()
lastIndexOf()
map()
some()
every()
slice()
reduce()
reduceRight() 
flat()
flatMap()
find()

7、for 循环与 forEach 的区别 ?

  • 1.for循环可以使用break跳出循环,但forEach不能。
  • 2.for循环可以控制循环起点(i初始化的数字决定循环的起点),forEach只能默认从索引0开始。
  • 3.for循环过程中支持修改索引(修改 i),但forEach做不到(底层控制index自增,无法左右它)。

8、深浅拷贝 ?

  • 深拷贝:
function cloneObject(source, target) {
    
    
      if (target === undefined) {
    
    
        if (Node.prototype.isPrototypeOf(source)) {
    
    
          target = document.createElement(source.nodeName);
          target.style = source.style.cssText;
        } else if (source.constructor === Uint8Array) {
    
    
          target = new source.constructor(Array.from(source));
        } else if (source.constructor === Date || source.constructor === RegExp || source.constructor === Set || source
          .constructor === Map) {
    
    
          target = new source.constructor(source);
        } else if (source.constructor === Function) {
    
    
          var arg = source.toString().match(/\((.*?)\)/)[1];
          var content = source.toString().replace(/\n|\r/g, "").match(/\{
    
    (.*)\}/)[1];
          target = new Function(arg, content)
        } else {
          target = new source.constructor();
        }
      }
      var names = Object.getOwnPropertyNames(source).concat(Object.getOwnPropertySymbols(source));
      for (var i = 0; i < names.length; i++) {
    
    
        if (names[i] === "constructor") {
    
    
          Object.defineProperty(target, "constructor", {
    
    
            value: source.constructor
          });
          continue;
        }
        var desc = Object.getOwnPropertyDescriptor(source, names[i]);
        if ((typeof desc.value === "object" && desc.value !== null) || typeof desc.value === "function") {
    
    
          var o = cloneObject(desc.value)
          Object.defineProperty(target, names[i], {
    
    
            value: o,
            enumerable: desc.enumerable,
            writable: desc.writable,
            configurable: desc.configurable
          })
        } else {
    
    
          Object.defineProperty(target, names[i], desc);
        }
      }
      return target;
    }
  • 浅拷贝:
    1、Object.assign(目标对象,源对象)
    2、
    var obj1={
    
    }
    for(var key in obj){
    
    
        obj1[key]=obj[key]
    }
    3、obj1={
    
    ...obj};

9、url 的组成 ?

 http:/https:    协议
 www.baidu.com  域名
 :8080  端口
 /sf/vsearch  路径
 ?wd=百度热搜   查询(可有可无)
 #a=1&b=2  哈希值(可有可无)                         

10、常见的跨域方式 ?

  • JSONP:
    JSONP是利用外链脚本,没有跨源限制的特点,来实现跨源请求的一种技术。
  • CORS:
    cors:跨域资源共享,是一种实现跨源请求数据的技术。这就是跨源问题的解决方案之一。也是广泛的解决方案。
  • 正向代理
    先搭建一个属于自己的代理服务器
    • 1、用户发送请求到自己的代理服务器
    • 2、自己的代理服务器发送请求到服务器
    • 3、服务器将数据返回到自己的代理服务器
    • 4、自己的代理服务器再将数据返回给用户
  • 反向代理
    • 1、用户发送请求到服务器(访问的其实是反向代理服务器,但用户不知道)
    • 2、反向代理服务器发送请求到真正的服务器
    • 3、真正的服务器将数据返回给反向代理服务器
    • 4、反向代理服务器再将数据返回给用户
  • 通过postMassage,

11、Promise 的使用场景 ?

  • 场景1:获取文件信息。
  • 场景2:配合AJAX获取信息
  • 场景3:解决回调地狱,实现串行任务队列。
  • 场景4: node中进行本地操作的异步过程

12、let, const, var 的区别 ?

声明方式 变量提升 暂时性死区 重复声明 初始值 作用域
var	     允许	  不存在	   允许  不需要 非块级
let	    不允许	   存在	  不允许	 不需要	块级
const	不允许	   存在	  不允许	  需要	块级

13、对 this 的理解, 三种改变 this 的方式 ?

  • 1.任何情况下直接在script中写入的this都是window。

  • 2.函数中的this 非严格模式:this指向window, 严格模式时:this指向undefined。

  • 3.箭头函数的this
    this都指向箭头函数外上下文环境的this指向

  • 4.对象中this
    对象属性的this 指向对象外上下文环境的this
    对象方法(普通函数)中的this,指向当前对象(谁执行该方法,this就指向谁)

  • 5.回调函数的this指向

    • 1)、 setTimeout,setInterval回调函数不管是否是严格模式都会指向window。
    • 2)、通过在函数内执行当前回调函数 非严格模式:this指向window, 严格模式时:this指向undefined。
    • 3)递归函数中的this 非严格模式:this指向window, 严格模式时:this指向undefined。
      1. 使用arguments0执行函数时 this指向arguments。
    • 5)事件中的回调函数,this指向事件侦听的对象(e.currentTarget);
  • 6、call,apply,bind方法执行时this的指向

    • 如果call,apply,bind传参时,第一个参数传入的不是null或者undefined,传入什么this指向什么
    • 如果第一个参数传入的是null或者undefined ,非严格模式下指向window
  • 7、在ES6的类中this的指向

    • 构造函数中的this指向实例当前类所产生的新的实例对象
    • 类中实例化方法中this指向谁执行该方法,this指向谁
    • 类中静态方法中this执行该类或者该类的构造函数
    • 类中实例化箭头方法,this仍然指向当前类实例化的实例对象
  • 8、ES5的原型对象中this的指向

    • 在原型的方法中,this指向实例化当前构造函数的实例化对象(谁执行该方法,this指向谁);
    • 三种改变this指向的方式
      • 函数名.call(this,…)this写谁就指谁。
      • 函数名.apply(this,[参数1,参数2,…]) this写谁就指谁。
      • 函数名. bind (this,1,2,3) this写谁就指谁。

14、cookie, localStorage,sessionStorage 的区别 ?

存储方式 作用与特性 存储数量及大小

  • cookie
存储方式
 存储用户信息,获取数据需要与服务器建立连接。
 以路径存储,上层路径不能访问下层的路径cookie,下层的路径cookie可以访问上层的路径cookie 
 作用与特性 

可存储的数据有限,且依赖于服务器,无需请求服务器的数据尽量不要存放在cookie 中,以免影响页面性能。

可设置过期时间。

存储数量及大小 将cookie控制在4095B以内,超出的数据会被忽略。
IE6或更低版本 最多存20个cookie; 
IE7及以上
版本 多可以有50个;
Firefox多 50个;
chrome和Safari没有做硬性限制。

cookie最大特征就是可以在页面与服务器间互相传递,当发送或者接受数据时自动传递

localStorage
 存储客户端信息,无需请求服务器。

 数据永久保存,除非用户手动清理客户端缓存。

 开发者可自行封装一个方法,设置失效时间。	5M左右,各浏览器的存储空间有差异。

 任何地方都可以存都可以取

 操作简单

sessionStorage	

存储客户端信息,无需请求服务器。

数据保存在当前会话,刷新页面数据不会被清除,结束会话(关闭浏览器、关闭页面、跳转页面)数据失效。	

5M左右,各浏览器的存储空间有差异。

同页面不同窗口中数据不会共享

15、输入 url 到打开页面 都做了什么事情 ?

  • 输入URL
  • 访问hosts解析,如果没有解析访问DNS解析
  • TCP握手
  • HTTP请求
  • HTTP响应返回数据
  • 浏览器解析并渲染页面

16、原生 ajax 的流程 ?

    创建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)
       }
    }

17、如何实现继承 ?

  • 对于 JavaScript 来说,继承有两个要点:

      1. 复用父构造函数中的代码
      1. 复用父原型中的代码第一种实现复用父构造函数中的代码,我们可以考虑调用父构造函数并将 this 绑定到子构造函数。
  • 第一种方法:复用父原型中的代码,我们只需改变原型链即可。将子构造函数的原型对象的 proto 属性指向父构造函数的原型对象。

  • 第二种实现
    使用 new 操作符来替代直接使用 proto 属性来改变原型链。

  • 第三种实现
    使用一个空构造函数来作为中介函数,这样就不会将构造函数中的属性混到 prototype 中

function A(x,y){
    
    
  this.x = x
  this.y = y
}
A.prototype.run = function(){
    
    }
// 寄生继承 二者一起使用
function B(x,y){
    
    
  A.call(this,x,y)  // 借用继承
}
B.prototype = new A() // 原型继承
// 组合继承
Function.prototype.extends = function(superClass){
    
    
  function F(){
    
    }
  F.prototype = superClass.prototype
  if(superClass.prototype.constructor !== superClass){
    
    
    Object.defineProperty(superClass.prototype,'constructor',{
    
    value:superClass})
  }
  let proto = this.prototype
  this.prototype = new F()
  let names = Reflect.ownKeys(proto)
  for(let i = 0; i < names.length;i++){
    
    
    let desc = Object.getOwnPropertyDescriptor(proto,names[i])
    Object.defineProperty(this.prototypr,name[i],desc)
	}
  this.prototype.super = function(arg){
    
    
    superClass.apply(this,arg)
	}
  this.prototype.supers = superClass.prototype
}
  • 第四种实现
    es6类的继承extends。

18、null 和 undefined 的区别 ?

  • null是一个表示"无"的对象(空对象指针),转为数值时为0;
  • undefined是一个表示"无"的原始值,转为数值时为NaN。
    拓展:
  • null表示"没有对象",即该处不应该有值。典型用法是:
    • 作为函数的参数,表示该函数的参数不是对象。
    • 作为对象原型链的终点。
  • undefined表示"缺少值",就是此处应该有一个值,但是还没有定义。典型用法是:
    • 变量被声明了,但没有赋值时,就等于undefined。
    • 调用函数时,应该提供的参数没有提供,该参数等于undefined。
    • 对象没有赋值的属性,该属性的值为undefined。
    • 函数没有返回值时,默认返回undefined。

19、函数的节流和防抖 ?

  • 节流
节流是指当一个事件触发的时候,为防止事件的连续频繁触发,设置定时器,达到一种一段事件内只触发一次的效果,在当前事件内不会再次触发,当前事件结束以后,再次触发才有效.
function thro(cb,wait){
    
    
  let timeOut
  return function(){
    
    
    if(timeOut) return
    timeOut = setTimeout(function(){
    
    
      cb()
      clearTimeout(timeOut)
      timeOut = null
    },wait)
  }
}
  • 防抖
防抖是指当一个事件触发的时候, 为防止频繁触发事件, 设置定时器,以达到一种 频繁触发期间不处理, 只有当最后一次连续触发结束以后才处理
function debounce(cb,wait){
    
    
	let timer
  return function(){
    
    
    clearTimeout(timer)
    timer = setTimeout(()=>cb(),wait)
	}
}

20、什么是 Promise ?

  Promise 是异步编程的一种解决方案:从语法上讲,promise是一个对象,从它可以获取异步操作的消息;
  
  从本意上讲,它是承诺,承诺它过一段时间会给你一个结果。
  
  promise有三种状态: pending(等待态),fulfiled(成功态),rejected(失败态);状态一旦改变,就不会再变。创造promise实例后,它会立即执行

  promise是用来解决两个问题的:

  回调地狱,代码难以维护, 常常第一个的函数的输出是第二个函数的输入这种现象

  promise可以支持多个并发的请求,获取并发请求中的数据

  这个promise可以解决异步的问题,本身不能说promise是异步的

21、普通函数与箭头函数的区别 ?

普通函数和箭头函数的区别:

  • 1.箭头函数没有prototype(原型),箭头函数没有自己的this,继承的是外层代码块的this。
  • 2.不可以当做构造函数,也就是说不可以使用new命令,否则会报错的。
  • 3.不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
  • 4.不可以使用yield命令,因此箭头函数不能用作 Generator(生成器) 函数。
  • 5.因为没有this,所以不能使用call、bind、apply来改变this的指向。

22、设计模式有哪些, 分别说一说 ?

共23种设计模式,介绍其中6种应用较为广泛的模式。

  • 发布订阅模式:
    这种设计模式可以大大降低程序模块之间的耦合度,便于更加灵活的扩展和维护。

  • 中介者模式 :
    观察者模式通过维护一堆列表来管理对象间的多对多关系,中介者模式通过统一接口来维护一对多关系,且通信者之间不需要知道彼此之间的关系,只需要约定好API即可。

  • 代理模式 :
    为其他对象提供一种代理以控制对这个对象的访问。
    代理模式使得代理对象控制具体对象的引用。代理几乎可以是任何对象:文件,资源,内存中的对象,或者是一些难以复制的东西。

  • 单例模式 :
    保证一个类只有一个实例,并提供一个访问它的全局访问点(调用一个类,任何时候返回的都是同一个实例)。

  • 工厂模式 :
    工厂模式定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类。该模式使一
    个类的实例化延迟到了子类。而子类可以重写接口方法以便创建的时候指定自己的对象类型

  • 装饰者模式 : 装饰者(decorator)模式能够在不改变对象自身的基础上,在程序运行期间给对像动态的添加职责(方法或属性)。与继承相比,装饰者是一种更轻便灵活的做法。

23、Promsie 和 async/await 的区别和使用 ?

区别:

  • 1)函数前面多了一个async关键字。await关键字只能用在async定义的函数内。async函数会隐式地返回一个promise,该promise的reosolve值就是函数return的值。
  • 2)第1点暗示我们不能在 外层代码中使用await,因为不在async函数内。使用:
    • 1.async和await是配对使用的,await存在于async的内部。否则会报错 。
    • 2.await表示在这里等待一个promise返回,再接下来执行。
    • 3.await后面跟着的应该是一个promise对象,(也可以不是,如果不是接下来也没什么意义了…)

24、谈一谈垃圾回收机制 ?

垃圾回收是动态存储管理技术,会自动地释放“垃圾‘’(不再被程序引用的对象),按照特定的垃圾收集算法来实现资源自动回收的功能。
回收的两种机制

  • 1.标记清除(make-and-sweep)
  • 2.引用计数 垃圾回收器会按照固定的时间间隔周期性的执行。

25、数组去重 ?

  • 第一种:
for(var i=0;i<arr.length;i++){
    
     
for(var j=i+1;j<arr.length;){
    
     
if(arr[i]===arr[j]) arr.splice(j,1); 
else j++; // 核心 
} 
} 
  • 第二种:
var arr1=[]; 
xt: for(var i=0;i<arr.length;i++){
    
     
for(var j=0;j<arr1.length;j++){
    
     
if(arr1[j]===arr[i]) continue xt; 
}
arr1.push(arr[i]); 
} 
  • 第三种:
var arr1=[]; 
for(var i=0;i<arr.length;i++){
    
     
if(arr1.indexOf(arr[i])<0) arr1.push(arr[i]) 
} 
  • 第四种:
var arr1=[]; 
for(var i=0;i<arr.length;i++){
    
     
if(!(~arr1.indexOf(arr[i]))) arr1.push(arr[i]) 
} 
  • 第五种:
var arr1=[]; 
for(var i=0;i<arr.length;i++){
    
     
if(!arr1.includes(arr[i])) arr1.push(arr[i])
} 
  • 第六种:
arr=[1,2,3,1,2,3,1,2,3] 
new Set(arr); 

26、判断对象为空 ?

  • 第一种
使用JSON.stringify()将对象转换为json字符串; 
JSON.stringify(obj) === '{}' 
  • 第二种
使用for...in循环遍历对象除Symbol以外的所有可枚举属性,当对象有属性存在返回false, 否则返回 
true。 
const obj = {
    
    } 
function isObjectEmpty(obj){
    
     
for(var key in obj){
    
     
return false 
}
return true 
}
console.log(isObjectEmpty(obj)) 
  • 第三种
Object.getOwnPropertyNames() 方法会返回该对象所有可枚举和不可枚举属性的属性名(不含Symbol 
属性)组成的数组。然后再通过判断返回的数组长度是否为零,如果为零的话就是空对象。 
Object.getOwnPropertyNames(obj).length === 0 
  • 第四种
Object.keys() 是 ES5 新增的一个对象方法,该方法返回一个数组,包含指定对象自有的可枚举属性(不 
含继承的和Symbol属性)。用此方法只需要判断返回的数组长度是否为零,如果为零的话就是空对象。

27、如何用一次循环找到数组中两个最大的值 ?

var arr=[1,4,10,11,11,2,5,7,2,3,4];
  var [max,second]=arr[0]>arr[1] ? [arr[0],arr[1]] : [arr[1],arr[0]];
  for(var i=2;i<arr.length;i++){
    
    
    if(arr[i]>max){
    
    
      second=max;
      max=arr[i];
    }else if(arr[i]<=max && arr[i]>second){
    
    
      second=arr[i];
    }
  }

28、new 一个对象的过程 ?

  • 1.开辟一个堆内存,创建一个空对象
  • 2.执行构造函数,对这个空对象进行构造
  • 3.给这个空对象添加__proto__属性

29、箭头函数为什么不能用 new ?

因为箭头函数没有prototype也没有自己的this指向并且不可以使用arguments。

30、如何实现数组的复制 ?

  • for循环逐一复制;
  var arr1=[];
  for(var i=0;i<arr.length;i++){
    
    
    if(i in arr) arr1[i]=arr[i]
  }
  • …方式
   var arr1=[...arr];
  • slice方法
  var arr1=arr.slice();
  • concat方法
  var arr1=arr.concat();
  • map方法
var arr1=arr.map(item=>item);
  • reduce
var arr1=arr.reduce((v,t)=>v.push(t),[])

猜你喜欢

转载自blog.csdn.net/sdasadasds/article/details/131976118