基础问题

  • Javascript 中有什么数据类型?他们的区别的有什么?

    数据类型:基本类型引用类型
    基本类型:Number、String、Boolean、Null、Undefined、Symbol、BigInt(ES6 新增)
    引用类型:Object、Array、Function、Date
    区别:
    声明变量时不同的内存地址分配:简单类型的值存放在栈中,在栈中存放的是对应的值;引用类型对应的值存储在堆中,在栈中存放的是指向堆内存的地址
    不同的类型数据导致赋值变量时的不同:简单类型赋值,是生成相同的值,两个对象对应不同的地址;复杂类型赋值,是将保存对象的内存地址赋值给另一个变量。也就是两个变量指向堆内存中同一个对象

  • 谈谈 Javascript 中的类型转换机制

    类型:自动转换(隐式转换)强制转换(显示转换)

  • 谈谈 Javascript 中的闭包,闭包有什么特点?

    定义:闭包就是一个函数和对其周围状态的引用的组合。

    1
    2
    3
    4
    5
    6
    function funcOutside() {
    let a = 1;
    function funcInside() {
    console.log(a); //内部函数调用外部函数的变量,由于闭包特性是可以访问到的
    }
    }

    特点: 创建私有变量;延长变量的生命周期

  • 操作符’==’和’===’的区别

    ==操作符,即为等于操作符,在变量进行比较的时候会先进行隐式转换,然后再进行变量之间的比对。

    1
    2
    3
    let num1 = 1;
    let str1 = "1";
    console.log(num1 == str1); // true

    ===操作符,即为全等操作符,在变量进行比较的时候会直接进行变量之间的比对,不会如等于操作符一样先进行隐式转换。

    1
    2
    3
    let num1 = 1;
    let str1 = "1";
    console.log(num1 === str1); // false
  • 深拷贝和浅拷贝的区别

    浅拷贝

    浅拷贝:创建新的数据,这份新的数据拥有原始数据属性值的一份精准拷贝。如果是基本类型拷贝,拷贝的是属性的值;如果是引用类型拷贝,拷贝的是内存地址。即拷贝的数据和原始数据共享同一内存地址。

    实现一个浅拷贝

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function copy(obj) {
    const newObj = {}; // const不可以修改内存地址,但可以修改属性
    for (let prop in obj) {
    if (obj.hasOwnProperty(prop)) {
    newObj[prop] = obj[prop];
    }
    }
    return newObj;
    }

    在 Javascript 中存在浅拷贝现象的方法还有:

    • Object.assign()
    • Array.prototype.slice(), Array.prototype.concat()

    深拷贝

    深拷贝:开辟一个新的栈,两个对象属性完全相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性。

    实现一个深拷贝

    1
    2
    3
    function(){
    ....
    }

    在 Javascript 中存在深拷贝现象的方法还有:

    • _.cloneDeep()
    • jQuery.extend()
    • JSON.stringify()
  • 什么是作用域链?

    作用域

    作用域,即为变量和函数生效的区域或集合。
    作用域一般分为:全局作用域、函数作用域和块级作用域(ES6+)

    全局作用域

    任何不在函数中或是大括号中声明的变量,都是在全局作用域下,全局作用域下声明的变量可以在程序的任意位置访问。

    1
    2
    3
    4
    5
    let num1 = 1; // 全局变量
    function func() {
    console.log(num1);
    }
    func(); // 输出 1

    函数作用域

    函数作用域另称局部作用域,当一个变量是声明在函数内部时,它只能被函数内部进行访问,不能由函数外部进行访问。

    1
    2
    3
    4
    5
    6
    function func() {
    let num1 = 1;
    console.log(num1); //输出 1
    }
    func();
    console.log(num1); //Undefined

    块级作用域

    块级作用域是由 ES6 中新增的 let 和 const 关键字,在一对大括号中声明的 let 和 const 关键字存在于块级作用域中,在大括号之外不可访问。

    1
    2
    3
    4
    5
    6
    {
    let num1 = 1;
    const num2 = 2;
    console.log(num1, num2); // 1, 2
    }
    console.log(num1, num2); // Undefined, Undefined

    词法作用域

    词法作用域就是静态作用域,一个变量一旦被创建就会被定义好其静态作用域,而不是代码执行过程中定义好的。

    作用域链

    作用域链就是当 Javascript 去使用一个变量的时候,会从当前作用域下开始寻找,如果没有找到则会依次向上寻找,直至最高层全局作用域。如果没有找到,则会隐式创建变量或者报错。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    let num1 = 1;
    function func1() {
    let num2 = 2;
    function func2() {
    let num3 = 3;
    console.log(num3); // 当前作用域
    console.log(num2); // 往上找到num2,闭包
    console.log(num1); // 在全局作用域找到num1
    }
    console.log(num3); // 不可往下找,报错Undefined
    }
    func1();
  • 谈谈 Javascript 中的原型和原型链

    原型

    Javascript 是一种基于原型的语言,每个对象都会有对应的原型。对象的属性和方法定义在 Object 的构造器函数的 prototype 属性上。

    原型对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {
    constructor: ƒ 'YourFunction'(),
    __proto__: {
    constructor: ƒ Object(),
    hasOwnProperty: ƒ hasOwnProperty(),
    isPrototypeOf: ƒ isPrototypeOf(),
    propertyIsEnumerable: ƒ propertyIsEnumerable(),
    toLocaleString: ƒ toLocaleString(),
    toString: ƒ toString(),
    valueOf: ƒ valueOf()
    }
    }

    原型链

    原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法。

    总结

    • 一切对象都继承自 Object 对象,Object 对象直接继承根源对象 Null。
    • 一切函数对象都继承自 Function 对象。
    • Object 直接继承自 Function 对象。
    • Function 的proto指向自己的原型对象,最终指向 Object 对象。
  • 在 Javascript 中如何实现继承?

    常见的继承方式:extends 关键字(ES6+)、原型链继承、构造函数继承、组合继承、原型式继承、寄生式继承、寄生组合式继承

    原型链继承

    原型链继承是比较常见的继承方式之一,其中涉及的构造函数、原型和实例,三者之间存在着一定的关系,即每一个构造函数都有一个原型对象,原型对象又包含一个指向构造函数的指针,而实例则包含一个原型对象的指针。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function Parent() {
    this.name = "parent1";
    this.play = [1, 2, 3];
    }
    function Child() {
    this.type = "child2";
    }
    Child1.prototype = new Parent();
    console.log(new Child());

    构造函数继承

    父类的引用属性不会被共享,优化了第一种继承方式的弊端,但是只能继承父类的实例属性和方法,不能继承原型属性或者方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function Parent() {
    this.name = "parent1";
    }
    Parent.prototype.getName = function () {
    return this.name;
    };
    function Child(){
    Parent1.call(this)
    this.type = 'child'
    }
    let child = new Child()
    console.log(child) // 正常运行
    console.log(chile.getName()) // 报错

    组合继承

    组合继承则将前两种方式继承起来。

    原型式继承

    借助Object.create方法实现普通对象的继承。

    寄生式继承

    寄生式继承在原型式继承基础上进行优化,利用这个浅拷贝的能力再进行增强,添加一些方法。

    寄生组合式继承

    借助解决普通对象的继承问题的Object.create 方法,在前面几种继承方式的优缺点基础上进行改造,这也是所有继承方式里面相对最优的继承方式。

  • 谈谈你对this对象的理解

    this关键字是什么

    this关键字是函数运行的时候自动生成的一个内部对象,只能在函数内部使用,this总指向调用他的对象。并且,this关键字一旦确定,不可更改。

    绑定规则和优先级

    • 默认绑定
    • 隐式绑定
    • 显示绑定
    • new绑定

      new绑定>显示绑定>隐式绑定>默认绑定

    箭头函数

    与传统函数不同,箭头函数中的this关键字指向该箭头函数被定义的对象

  • Javascript中执行上下文和执行栈是什么?

    执行上下文

    执行上下文是一种对Javascript的代码执行环境的一种抽象概念,即只要有Javascript代码则会有执行上下文。

    执行上下文的类别

    • 全局执行上下文:变量在全局内,只有一个全局执行上下文,浏览器的全局对象是window。
    • 函数执行上下文:可以有无数个,当函数被调用执行的时候,就会创建一个对应的函数执行上下文。
    • Eval函数执行上下文:运行在eval函数中的代码,使用频率很低。

    执行上下文的生命周期:

    graph LR
      创建阶段-->执行阶段-->回收阶段

    执行栈

    执行栈又称调用栈,它拥有后进先出的结构,用于存储所有的执行上下文。
    当Javascript引擎开始执行你第一行脚本代码的时候,它就会创建一个全局执行上下文然后将它压到执行栈中,后续每当引擎碰到一个函数的时候,它就会创建一个函数执行上下文,然后将这个执行上下文压到执行栈中,引擎会执行位于执行栈栈顶的执行上下文(一般是函数执行上下文),当该函数执行结束后,对应的执行上下文就会被弹出,然后控制流程到达执行栈的下一个执行上下文。

  • 谈谈typeof和instanceof的区别

    typeof

    • typeof返回的是一个变量的基本类型。
    • typeof可以返回除(Null)外的基本类型,除(Function)外的引用类型。

    instanceof

    • instanceof返回的是布尔值。
    • instanceof可以准确判断变量的引用类型,但不可以判断变量的基本类型。
  • 说说new操作符具体做了什么?

    • 创建一个新的对象。
    • 把新的对象的_proto_指向原函数的构造函数。
    • 将构造函数内的this指向到新对象上。
    • 检查构造函数返回值。

    new操作符具体流程:

    graph LR
      新建对象-->_proto_绑定-->this绑定-->检查返回值
  • 说说Javascript中的事件模型

    事件

    在HTML文档或浏览器中发生的一系列交互操作,就称为事件。

    事件流

    由于DOM是一个树结构,因此在出现父子节点绑定事件,触发子节点的时候会引起顺序问题,由此有事件流之概念。

    事件流有三个阶段:

    • 事件捕获阶段
    • 处于目标阶段
    • 事件冒泡阶段

    事件模型

    事件模型分为三种:

    • 原始事件模型
    • 标准事件模型
    • IE事件模型

    原始事件模型

    没有事件冒泡阶段

    1
    <button onclick="func()"> //此处的onclick绑定事件就是原始事件绑定

    标准事件模型

    1
    2
    3
    4
    //绑定事件
    addEventListener(eventType, handler, useCapture)
    //删除事件
    removeEventListener(eventType, handler, useCapture)

    IE事件模型

    没有事件冒泡阶段

    1
    2
    3
    4
    //绑定事件
    attachEvent(eventType, handler)
    //删除事件
    detachEvent(eventType, handler)
  • 谈谈什么是事件代理?

    事件代理就是指把事件委托在目标元素的外层元素上,当触发目标元素时,通过事件冒泡机制触发到它的外层绑定元素上,执行相应函数。

    整体有两大优点

    • 减少内存使用。
    • 动态绑定,避开重复工作。
  • ajax原理是什么?

    • 创建核心XmlHttpRequest(Xhr)对象。
    • 通过Xhr对象的open方法,与服务器连接。
    • 构造请求所需要的数据内容,通过Xhr的send方法发送给服务端。
    • 通过Xhr对象提供的onreadystagechange监听服务端变化。
    • 接收并处理服务端返回的结果数据。
    • 将处理结果用javascript操作DOM实现更新。
    graph LR
        创建Xhr对象-->|open方法|连接服务器-->|send方法|发送请求-->|onreadystagechange|监听变化-->接收并处理结果-->|操作DOM|更新页面
  • 谈谈bind,apply和call三者的区别

    共同点

    • 目的都是修改this关键字的指向。
    • 三者第一个参数都是this要指向的对象,如果如果没有这个参数或参数为undefined或null,则默认指向全局window。

    不同点

    • apply和call是单次传参,bind可多次传参。
    • apply的参数为数组,call的参数为参数列表.
    • bind返回绑定this参数后的函数,apply和call立即执行函数。
  • 说说你对事件循环的理解

    同步任务进入主线程,即主执行栈,异步任务进入任务队列,主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。上述过程的不断重复就是事件循环。

    任务分为

    • 同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行。
    • 异步任务:异步执行的任务,比如ajax网络请求,setTimeout定时函数等
      • 微任务:一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前。
      • 宏任务:宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合。
  • 说说Javascript中常见的内存泄露情况

    • 意外的全局变量
      1
      2
      3
      4
      5
      6
      7
      function func1(){
      wrong = 1 // 变量没有在任何作用域有定义,会在全局作用域中隐式定义
      }
      function func2(){
      this.wrong = 2
      }
      func2() //此时函数内this指向window,又会隐式生成一个全局变量
    • 闭包的私有变量
      1
      2
      3
      4
      5
      6
      7
      function funcOutside(){
      const obj = {......}
      function funcInside(){
      console.log(obj) // 没有清理对私有变量的引用
      obj = null // 需要这样正确清理
      }
      }
    • 未正确释放的DOM引用
      1
      2
      3
      4
      const refA = document.getElementById('refA')
      console.log(refA)
      //需要清理
      refA = null
    • 计时器中的引用
      1
      2
      3
      4
      5
      6
      7
      8
      var someResource = getData();
      setInteral(function(){
      let node = document.getElementById('node')
      if(node){
      //外部引用
      node.innerHTML = JSON.stringify(someResource)
      }
      }, 1000)
  • 谈谈Javascript中的几种本地存储

    • cookie
    • sessionStorage
    • localStorage

    不同点

    • 存储大小:cookie只能存储最多4K大小的文本数据,而后两者可以存储5MB以上的数据。
    • 生命周期:cookie的生命周期取决于设置的过期时间,sessionStorage是在当前窗口关闭的时候会自动删除,localStorage存储持久数据,关闭浏览器不会丢失数据。
    • 交互方法:cookie的数据会自动传输给服务器,服务器也可以写cookie给客户端。localStorage和sessionStorage只能在本地存储。
  • 谈谈Javascript中数字精度丢失的问题

    简单的说,就是十进制数字需要先转为二进制数,然后再进行计算后转回十进制数得到结果。其中,进制的转换会由于位数限制、某些数转二进制出现无限循环造成数值转换后会出现偏差,导致结果不符合直觉。

  • 什么是防抖和节流?

    防抖

    防抖就是指在n秒后再执行事件,如果在n秒内重复触发,则重新计时。

    节流

    节流就是指在n秒内只执行一次事件,如果在n秒内重复触发,只有一次生效。

进阶问题