天天看点

web前端面试之javaScript、ES6

目录

js原理

JavaScript的typeof返回类型有哪些?

类型转换

数组的方法

Math.min()比Math.max()大

js迭代的方法

js数组去重和排序

箭头函数和普通函数的区别是什么?

讲一下let、var、const的区别

写出最简单的去重方式

实现一个new的伪代码

原型、原型链

继承

Object,create做了什么

generate

async await 

闭包,并说明其主要的作用

垃圾回收机制(闭包的延伸)

简述深浅拷贝

函数的节流和防抖

call、apply区别

关于函数的调用

捕获和冒泡

简单介绍一下event loop

instance of原理

typeof

js跨域如何解决

webpack proxy跨域

深度优先和广度优先

setTimeout与setInterval的区别

解释一下requestAnimationFrame

script标签如何实现异步加载

proxy和defineProperty区别

Common、AMD、CMD区别

commonjs与es6的modules的区别

线程和进程

暂存区和缓存区

Promise

浮点数

window.onload和document.ready的区别?哪一个先执行?

JavaScript 启动后,内存中有多少个对象?如何用代码来获得这些信息?

script标签如何实现异步加载

proxy和defineProperty区别

setState同步和异步

js原理

JavaScript的typeof返回类型有哪些?

Object(null和Array)、number、undefined、string、Boolean、function

类型转换

强制转换:parseInt();parseFloat();number();

数组的方法

1 、shift():删除数组的第一个元素,返回删除的值。这里是0
2 、unshift(3,4):把参数加载数组的前面,返回数组的长度。现在list:中是3,4,0,1,2
3、pop():删除数组的最后一个元素,返回删除的值。这里是2.
4、push(3):将参数加载到数组的最后,返回数组的长度,现在List中时:0,1,2,3
5、concat(3,4):把两个数组拼接起来。
6、splice(start,deleteCount,val1,val2,...):
    从start位置开始删除deleteCount项,并从该位置起插入val1,val2,...;splice(0,2);会删除当前数组的前两项
7、reverse:将数组反序
    var a = [1,2,3,4,5]; 
    var b = a.reverse(); //a:[5,4,3,2,1] b:[5,4,3,2,1] 
8、sort(orderfunction):按指定的参数对数组进行排序 var a = [1,2,3,4,5]; var b = a.sort(); //a:[1,2,3,4,5] b:[1,2,3,4,5]
9、slice(start,end):返回从原数组中指定开始下标到结束下标之间的项组成的新数组
    var a = [1,2,3,4,5]; 
    var b = a.slice(2,5); //a:[1,2,3,4,5] b:[3,4,5]
10、join("-");    //1-2-3,数组值转为字符串

           

Math.min()比Math.max()大

Math.min()<Math.max()// false

Math.min()>Math.max()// true 

因为Math.min() 返回 Infinity, 而 Math.max()返回 -Infinity。

js迭代的方法

every() 、fliter()、forEach()、map()、some()具体大家可以查一下!

js数组去重和排序

具体我就不详细描述了。

箭头函数和普通函数的区别是什么?

总结:

  • 普通函数的this指向调用它的那个对象
  • 箭头函数的 this 永远指向其上下文的  this ,任何方法都改变不了其指向,如 call() ,  bind() ,  apply() 

箭头函数:

  • 箭头函数是匿名函数,不能作为构造函数,不能使用new 
  • 箭头函数不绑定arguments,取而代之用rest参数...解决
  • 箭头函数不绑定this,会捕获其所在的上下文的this值,作为自己的this值
  • 箭头函数通过 call()  或   apply() 方法调用一个函数时,只传入了一个参数,对 this 并没有影响。
  • 箭头函数没有原型属性
  • 箭头函数不能当做Generator函数,不能使用yield关键字

普通函数this:

  1. this总是代表它的直接调用者。
  2. 在默认情况下,没找到直接调用者,this指的是window。
  3. 在严格模式下,没有直接调用者的函数中的this是undefined。
  4. 使用call,apply,bind绑定,this指的是绑定的对象。

讲一下let、var、const的区别

  • var 没有块级作用域,支持变量提升。
  • let 有块级作用域,不支持变量提升。不允许重复声明,暂存性死区。不能通过window.变量名进行访问.
  • const 有块级作用域,不支持变量提升,不允许重复声明,暂存性死区。声明一个变量一旦声明就不能改变,改变报错。
(function(){
  var x = y = 1;
})();
console.log(y);//1
console.log(x);//error
y 被赋值到全局。x 是局部变量,全局中获取不到。所以打印 x 的时候会报 ReferenceError
           

写出最简单的去重方式

1、es6的new Set()方式

let array=[0,3,4,5,3,4,7,8,2,2,5,4,6,7,8,0,2,0,90];

[...new Set(array)] 

简写:[...new Set([1,3,4,5,1,2,3,3,4,8,90,3,0,5,4,0])]

2、es5的Array filter()

[1,3,4,5,1,2,3,3,4,8,90,3,0,5,4,0].filter(function(elem,index,Array){

    return index === Array.indexOf(elem);

})

实现一个new的伪代码

  • 创建一个对象
  • 连接原型
  • 绑定this
  • 返回该对象
function _new(){
  let obj = new Object();
  let Con = [].shift.call(arguments);
  obj.__proto__ = Con.prototype;
  let result = Con.apply(obj,arguments);
  return typeof result === 'object' ? result : obj
}
           

原型、原型链

原型链:每个被实例对象都有

__proto__

对象,它指向了构造该对象的构造函数的

prototype

属性。同时该对象可以通过

__proto__

对象来寻找不属于自身的属性,

原型:就是实现继承过程中产生的一个概念。

继承

原理是:复制父类的属性和方法来重写子类的原型对象

  • 原型继承
  • 构造函数继承
  • 组合继承
  • 寄生继承
  • 寄生组合继承
  • class
  • 等等
// 寄生组合继承方法
function Father(...arr) {
    this.some = '父类属性';
    this.params = arr;
}
Father.prototype.someFn = function() {
    console.log(1);
}
Father.prototype.someValue = '2';
function Son() {
    Father.call(this, 'xxxx');
    this.text = '2222';
}
Son.protptype = Object.create(Father.prototype);
Son.prototype.constructor = Son;
           

Object,create做了什么

Object._create = function(obj){
  function F(){}; // 创建了一个新的构造函数F
  F.prototype = obj; // 然后将构造函数F的原型指向了参数对象obj
  return new F(); // 返回构造函数F的实例对象,从而实现了该实例继承obj的属性。
}
           

generate

async await 

闭包,并说明其主要的作用

闭包就是有权访问一个函数内部变量的函数,也就是常说的函数内部嵌套函数,内部函数访问外部函数变量,从而导致垃圾回收机制没有将当前变量回收掉。这样的操作,有可能会带来内存泄漏。好处就是可以设计私有的方法和变量。

闭包有两个主要用途,一是实现嵌套的回调函数,二是隐藏对象的细节。

垃圾回收机制(闭包的延伸)

js拥有特殊的垃圾回收机制,当一个变量在内存中失去引用,js会通过特殊的算法将其回收,并释放内存。

分为以下两个阶段:

  1. 标记阶段:垃圾回收器,从根对象开始遍历,访问到的每一个对象都会被标示为可到达对象。
  2. 清除阶段:垃圾回收器在对内存当中进行线性遍历,如果发现该对象没有被标记为可到达对象,那么就会被垃圾回收机制回收。

    这里面牵扯到了引用计数法,每次引用都被会‘➕1’ 如果标记清零,那么就会被回收掉。

简述深浅拷贝

浅拷贝

通常需要拷贝的对象内部只有一层的这种对象。

常用的方法

  1. Object.assign

    方法来实现
  2. 扩展运算符

    ...obj

深拷贝

通常是嵌套二层或以上的复杂对象

常用方法

  1. JSON.parse(JSON.stringfy(object))

    ; 该方法忽略掉undefined、忽略Symbol、忽略function。只适合简单深拷贝
  2. 手写递归方法去实现。
  3. 通过第三方库提供的深拷贝实现。

函数的节流和防抖

防抖函数:将多次触发变成最后一次触发;

function debounce(fn,wait){
  let timer = null;
  return function (){
    let arg = arguments;
    if(timer){
      clearTimeout(timer);
      timer = null;
    }
    timer = setTimeout(()=>{
       fn.apply(this,arg)
    },wait)
  }
}
function clg(){
  console.log('clg')
}
window.addEventListener('resize',debounce(clg,1000))
           

节流函数:将多次执行变成每隔一个时间节点去执行的函数

function throttle(fn,time){
  let lastTime = null;
  return function(){
    let nowTime = Date.now();
    if(nowTime - lastTime > time || !lastTime){
      fn();
      last = nowTime
    }
  }
}
function sayHi(){
  console.log('hi')
}
setInterval(throttle(sayHi,1000),500)
           

call、apply区别

相同点:都是重定向this指针的方法。

不同点:call和apply的第二个参数不相同,call是若干个参数的列表。apply是一个数组

手写一个call方法

关于函数的调用

  1. 作为一个正常的函数调用
  2. 函数作为方法调用
  3. 使用构造函数调用函数
  4. 作为函数方法调用函数

捕获和冒泡

捕获:就是从根元素开始向目标元素递进的一个关系;从上而下

冒泡:是从目标元素开始向根元素冒泡的过程;想象一下水里的泡泡从下而上。

⚠️stopPropagation 通常理解它是用来阻止事件冒泡的,其实该函数也可以阻止捕获事件。

简单介绍一下event loop

js作为单线程语言。在执行过程中,会产生执行环境。这些执行环境中的代码被顺序的加入到执行栈中,如果遇到异步代码,会被挂起并加入到任务队列当中,等到主线程任务执行完毕,event loop就会从任务队列取出需要执行的代码放入到执行栈中执行。所以本质上来讲,js中的异步还是同步的行为。

任务队列有分为宏任务和微任务队列。

一次正确的event loop执行顺序如下:

  1. 执行所有同步代码
  2. 执行栈为空,查询是否有需要执行的微任务。
  3. 微任务(有:则执行,无:则跳出)
  4. 必要的话开始渲染UI
  5. 开始下一轮的任务队列执行宏任务中的异步代码。
web前端面试之javaScript、ES6

instance of原理

instanceOf用来判断右边的prototype是否在左边的原型链上,告诉我们左边是否是右边的实例。、

function instanceof(left, right) {
    // 获得类型的原型
    let prototype = right.prototype
    // 获得对象的原型
    left = left.__proto__
    // 判断对象的类型是否等于类型的原型
    while (true) {
        if (left === null){
            return false  
        }
        if (prototype === left){
            return true
        }
        left = left.__proto__
    }
}
           

typeof

typeof 检测对象,除开函数是function类型之外。像常见的数组,对象或者是正则,日期等等都是object;

需要注意一下:

typeof Symbol() // 'symbol'
typeof null // object
typeof undefined // undefined
           

typeof null

检测输出

object

因为js最初版本,使用的是32位系统,类型的标签存储在每个单元的低位中000是

object

类型。

null

全是0,所以当我们使用typeof进行检测的时候js错误的判断位object

js跨域如何解决

目前暂时已知的跨域方法是:

  1. jsonp跨域,原理:script标签没有跨域限制的漏洞实现的一种跨域方法,只支持get请求。安全问题会受到威胁。
  2. cors跨域,通过后端服务器实现,

    Access-Control-Allow-Origin

  3. postMessage

    window

    的一个属性方法。
  4. websocket
  5. nginx反向代理
  6. iframe跨域

webpack proxy跨域

首先需要明白webpack proxy跨域只能用作与开发阶段,临时解决本地请求服务器产生的跨域问题。并不适合线上环境。配置在webpack的devServer属性中。webpack中的devsever配置后,打包阶段在本地临时生成了一个node服务器,浏览器请求服务器相当于请求本地服务。

深度优先和广度优先

广度优先:尝试访问尽可能靠近它的目标节点,然后逐层向下遍历,直至最远的节点层级。

深度优先:从起始节点开始,一直向下找到最后一个节点,然后返回,又继续下一条路径。知道找遍所有的节点。

setTimeout与setInterval的区别

setTimeout表示间隔一段时间之后执行一次调用,

setInterval是每隔一段时间循环调用,直至清除。

内存方面,setTimeout只需要进入一次宏队列,setInterval不计算代码执行时间,有可能多次执行多次代码

解释一下requestAnimationFrame

  1. 浏览器专门为DOM动画,canvas动画,SVG动画等等有一个统一的刷新机制。
  2. 按帧对网页进行重绘。该方法告诉浏览器希望执行动画并请求浏览器在下一次重绘之前调用回调函数来更新动画,
  3. 由系统来决定回调函数的执行时机,在运行时浏览器会自动优化方法的调用。

script标签如何实现异步加载

  1. defer:等到整个页面在内存中华正常渲染结束(DOM结构完全生成,以及其他脚本执行完成),才会执行;
  2. async是一旦下载完成,渲染就会中断,执行这个脚本之后,再继续渲染。

总结就是:defer是渲染完在执行。async是下载完就执行。

另外值得注意的就是:deger脚本会按照在页面出现的顺序加载,而async是不能保证加载顺序的。

proxy和defineProperty区别

Object.defineProperty缺点:

  1. 无法监控数组下标的变化,导致直接通过数组的下标给数组设置值。不能事实响应。vue内部通过数组的一些方法来监听。
  2. 只能劫持对象的属性,因此要对每个对象的属性进行遍历。 vue2.x版本之后是通过递归和遍历实现对data对象的数据监控。

proxy:

  1. 可以劫持整个对象,并返回一个新的对象
  2. 有多种劫持操作

Common、AMD、CMD区别

common AMD CMD
同步 异步 异步
依赖前置 就近依赖

commonjs与es6的modules的区别

commonjs modules
运行时加载 编译时输出接口
输出的是值拷贝 值的引用
倒入模块的路径可以是表达式 字符串
this指向当前模块 undefined

线程和进程

暂存区和缓存区

Promise

promise是 JavaScript 语言提供的一种标准化的异步管理方式,它的总体思想是,需要进行 io、等待或者其它异步操作的函数,不返回真实结果,而返回一个“承诺”,函数的调用方可以在合适的时机,选择等待这个承诺兑现(通过 Promise 的 then 方法的回调)。

Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息。

Promise 对象有以下两个特点:

1、对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:

  • pending: 初始状态,不是成功或失败状态。
  • fulfilled: 意味着操作成功完成。
  • rejected: 意味着操作失败。

只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来,它的英语意思就是「承诺」,表示其他手段无法改变。

2、一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变,只有两种可能:从 Pending 变为 Resolved 和从 Pending 变为 Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。

就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

优点:

  • 有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。
  • 此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。

缺点:

  • 首先,无法取消 Promise,一旦新建它就会立即执行,无法中途取消。
  • 其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。
  • 第三,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

async 函数

浮点数0.1+0.2为什么不等于0.3

因为JS中采用的IEEE 754的双精度标准,计算机内部存储数据的编码的时候,0.1在计算机内部根本就不是精确的0.1,而是一个有舍入误差的0.1。当代码被编译或解释后,0.1已经被四舍五入成一个与之很接近的计算机内部数字,以至于计算还没开始,一个很小的舍入错误就已经产生了。这也就是 0.1 + 0.2 不等于0.3 的原因。

另外要注意,不是所有浮点数都有舍入误差。二进制能精确地表示位数有限且分母是2的倍数的小数,比如0.5,0.5在计算机内部就没有舍入误差。所以0.5 + 0.5 === 1

解决浮点数计算的方法:

  • 规避掉小数计算时的精度,浮点数转化成整数计算,0.1*1000+0.2*1000)/1000==0.3
  • 在进行运算时,需要将其他进制的数值转换成二进制,然后再进行计算,因为计算机只认识二进制。

window.onload和document.ready的区别?哪一个先执行?

一般情况一个页面响应加载的顺序是,域名解析-加载html-加载js和css-加载图片等其他信息。

window.onload是在DOM文档树加载完和所有文件加载完之后执行一个函数,也就是在页面响应加载的顺序中的“加载图片等其他信息”之后,可以操作DOM。只能执行一次,如果有多个,那么第一次的执行会被覆盖

document.ready是在DOM加载完成后就可以可以对DOM进行操作,也就是在在“加载js和css”和“加载图片等其他信息”之间,就可以操作DOM了。可以执行多次

所以,document.ready函数只需对 DOM 树的等待,而无需对图像或外部资源加载的等待,从而执行起来更快

JavaScript 启动后,内存中有多少个对象?如何用代码来获得这些信息?

script标签如何实现异步加载

  1. defer:等到整个页面在内存中华正常渲染结束(DOM结构完全生成,以及其他脚本执行完成),才会执行;
  2. async是一旦下载完成,渲染就会中断,执行这个脚本之后,再继续渲染。

总结就是:defer是渲染完在执行。async是下载完就执行。

另外值得注意的就是:deger脚本会按照在页面出现的顺序加载,而async是不能保证加载顺序的。

proxy和defineProperty区别

Object.defineProperty缺点:

  1. 无法监控数组下标的变化,导致直接通过数组的下标给数组设置值。不能事实响应。vue内部通过数组的一些方法来监听。
  2. 只能劫持对象的属性,因此要对每个对象的属性进行遍历。 vue2.x版本之后是通过递归和遍历实现对data对象的数据监控。

proxy:

  1. 可以劫持整个对象,并返回一个新的对象
  2. 有多种劫持操作

setState同步和异步

  1. setState只是在合成事件和生命周期函数中是异步更新
  2. 在settimeout、原生事件、async函数中是同步更新。

继续阅读