前端面试-js基础题型

78 阅读17分钟

1.javaScript有哪些数据类型,它们的区别

js有基本数据类型和引用数据类型

  • 基本数据类型为null、undefined、symbol、boolean、string、number
  • 引用数据类型为object 区别:
  • 基本数据类型是存储在栈空间中,
    • 占据的空间小,且大小一定
  • 引用数据类型是存储在堆空间中
    • 引用数据存放在堆空间中,但保存数据的变量存储的是一个地址指针,该指针指向堆空间中的数据位置

栈和堆的理解:

  • 栈采用先进后出原则,其中存储的是基本数据类型和引用数据的地址,不直接存储引用数据
  • 堆用于存放引用数据,如对象、数组
  • 在堆中分配的内存需要手动进行释放,否则容易导致内存泄露
  • 对象在堆中分配的内存由垃圾回收器负责管理,通过标记清除法等算法来识别不再使用的对象并释放

2.数据类型检测的方法有哪些

  • typeof:只能判断基本数据类型
  • instanceof:只能判断引用数据类型,用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。
    • 缺点是当原型链被改后,无法准确进行判断
  • Object.prototype.toString.call:可以用于判断基本数据类型和引用数据类型
  • constructor:用于判断对象是否为某个构造函数的实例
    • obj.proto.constructor === Object

3.判断数组的方式

  • Object.prototype.call
  • constructor
  • instanceof
  • Array.isArray
  • 原型链判断 arr.proto === Array.prototype
  • Array.prototype.isPrototypeOf(arr):用于检测对象是否是另一个对象的原型
    • prototypeObj.isPrototypeOf(object)
    • prototypeObj: 用于检查的原型对象。
    • object: 要检查的对象。
    • isPrototypeOf 返回一个布尔值,如果prototypeObj 是 object 的原型,那么返回 true,否则返回 false。

4.typeof null的结果

typeof null为对象,这是js的一个bug

5.instanceof操作符的实现原理及实现

instanceof用于判断构造函数的prototype是否在对象的原型链上

function myInstanceof(obj,constructor){
    // 获取对象的原型
    let objPro = Object.getPrototypeOf(obj);
    // 获取构造函数的原型
    const constPro = constructor.prototype;
    while(objPro) {
        if(constPro === objPro) {
            return true
        }
        objPro = Object.getPrototypeOf(objPro)
    }
    return false
}

6.如何安全的获取undefined

因为undefined不是js的关键字,所以undefined可以用作变量名,可以通过void 0 来安全的获取

7.typeof NaN

NaN表示不是一个数字,用来指出数字类型中的错误情况,typeof NaN结果为number

8.isNaN和Number.isNaN的区别

  • isNaN:在运算时,它会先尝试能否转换为数字,若不能则返回true
    • isNaN('abc') 会返回true
  • Number.isNaN:运算时不会进行数字转换,即只有当结果真的为NaN时才会返回true
    • Number.isNaN('avc') 返回false

9.==操作符的转换规则

10.其他值到字符串的转换规则

  • 使用String() 对其他值进行包裹,将会
    • 字符串 -> 字符串
    • 数字 -> 用引号包裹的数字
    • 布尔值
      • true -> 'true'
      • false -> 'false'
    • null -> 'null'
    • undefined -> 'undefined'
    • 对象
      • 先调用对象的toString方法
        • 若返回的是基本数据类型,则调用String方法
      • 若返回的是对象,则调用对象的valueOf方法
        • 若返回的是基本数据类型,则调用String方法
      • 若返回的是对象,则直接报错
    • 数组(相当于就是把数组左右的花括号去掉)
      • 空数组 -> '' 空字符串
      • [1,2,3] -> '1,2,3'
      • ['a','b','c'] -> 'a,b,c'

11.其他值到数字的转换规则

  • 使用Number() 对其他类型进行包裹,将会
    • 数字 -> 数字
    • 字符串
      • 判断字符串中除了数字是否有其他类型的
        • 若为空字符串 -> 0
        • 若为纯数字的字符串 -> 转为数字
        • 若含有非数字的成员 -> NaN
    • 布尔值
      • true -> 1
      • false -> 0
    • null -> 0
    • undefined -> NaN
    • 对象
      • 先调用对象的valueOf方法
        • 若返回的是基本数据类型,则调用Number进行判断
      • 若返回的是对象,则继续调用对象的toString方法
        • 若返回的是基本数据类型,则调用Number进行判断
      • 若返回的还是对象,则直接报错
    • 数组
      • 空数组 -> 0
      • 长度为1的数组
        • 若为数字 -> 直接就是数字
        • 若为非数字 -> NaN
      • 长度大于1的数组 -> NaN
    // 正常情况下 对象的toString会返回一个字符串,表示该对象的类型 [object Object]
    Number({
        valueOf() { return { a: 123 } },
        toString() { return '[object object]' }
    })
    // 若最后的toString返回的还是对象,则报错
    Number({
        valueOf() { return { a: 123 } },
        toString() { return { age: 1232 } }
    })

当然,对应的valueOf和toString方法也可以自定义

const obj = {
    valueOf(){
        return 1212
    },
    toString(){
        return '1212'
    }
}

12.其他值到布尔值的转换规则

  • 使用Boolean()进行包裹,将会
    • 以下五个将转为false
      • '' 空字符串
      • 0、-0、+0
      • null
      • undefined
      • NaN
      • false
    • 其余将转为true

13.Object.is()和 ==、=== 的区别

  • == 操作符进行比较时,当两侧数据类型不一致时,会尝试进行类型转换,当转换后仍然不一致,才返回false
  • === 操作符进行比较时,不会进行类型转换,直接进行比较,结果不一致直接返回false,缺点是
    • -0和+0 结果是false
    • NaN和NaN比较也是false
  • Object.is() 比较时,在===基础上,对-0和+0、NaN进行优化,即它们都是true

14.什么是包装类型

当对基本类型数据进行对象操作时,js会隐式的将基本类型包装成对象类型,然后进行对象操作后,再将包装类型移除

const a = 'abc';
a.length // 3
// js会隐式的包装成对象
{
    let str = String(a)
    str.length
    str = null
}

15.js如何进行隐式类型转换

在js中,隐式转换是建立在强制转换的基础上的。

  • 自动转换为布尔值
    • 在if判断条件中,会自动进行布尔值转换
    • !!变量,也会进行布尔值转换
  • 自动转换为字符串
    • 当使用+运算符且有一边是字符串时,会自动进行字符串转换,然后进行拼接
      • 如,一边是字符串,另一边是数字,则会先将数字进行字符串隐式转换,然后再进行拼接
  • 自动转为数字
    • 当使用除+外的运算符时,会自动进行数字类型转换,然后进行计算
    • 若使用+,则必须保证两边都是数字,若一边为字符串,则进行字符串转换,然后进行拼接
    • 在变量前面加个运算符,也会进行数字转换

16.let、const、var的区别

  • var
    • 存在变量提升,即进行预编译时,会将var定义的变量提前,并赋值为undefined
    • 可以重复声明,也可以重复赋值
    • 在全局定义时,会将变量添加到window上
  • let
    • 块级作用域
      • 若在函数内定义,函数外无法访问到
    • 只能声明一次,若没有赋值,其值为undefined
    • 存在暂时性死区,即未定义前无法访问
  • const
    • 定义时必须赋值,否则会报错
    • 无法修改值
    • 块级作用域,存在暂时性死区

17.const对象的属性可以修改吗

const定义的变量不能修改值,即定义的是基本数据类型,则不能修改,若定义的是引用数据类型,因为const定义的变量保存是引用数据的指针地址,所以可以修改引用数据内部的数据,但不能修改指针地址,即重新赋值

18.new一个对象会发生什么

  • 先创建一个空对象
  • 将this指向这个对象(为这个对象添加属性和方法)
  • 将空对象的原型指向构造函数的原型
  • 返回这个对象

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

  • 箭头函数没有自己的this,它的this为当前的上下文
  • 箭头函数的this无法改变,即无法通过call、apply、bind改变
  • 箭头函数没有自己的arguments,若箭头函数是在普通函数内部,则它的arguments为普通函数的arguments
var id = 'GLOBAL';
var obj = {
  id: 'OBJ',
  a: function(){
    console.log(this.id);
  },
  b: () => {
    console.log(this.id);
  }
};
obj.a();    // 'OBJ'
obj.b();    // 'GLOBAL'
new obj.a()  // undefined
new obj.b()  // Uncaught TypeError: obj.b is not a constructor

对象obj的方法b是使用箭头函数定义的,这个函数中的this就永远指向它定义时所处的全局执行环境中的this,即便这个函数是作为对象obj的方法调用,this依旧指向Window对象。需要注意,定义对象的大括号{}是无法形成一个单独的执行环境的,它依旧是处于全局执行环境中。

20.对rest参数的理解

当拓展运算符用在函数形参上时,它会把剩余的形参合并成一个数组,常用于不确定参数个数时使用

21.new操作符的实现原理

  • 创建一个空对象,并将空对象的原型指向构造函数
  • 将this指向空对象,用于给对象添加属性和方法
  • 根据构造函数执行结果
    • 若结果是对象,则返回这个执行的结果
    • 若是其他,则返回这个对象
function myNew(constructor,...args){
    if(typeof constructor !=='function') throw new Error('')
    // 创建对象,并指向原型
    const obj = Object.create(constructor.prototype)
    const res = constructor.apply(obj,args)
    return typeof res === 'object' ? res : obj
}

22.call、apply、bind实现

    // call实现
    Function.prototype.call2 = function (context, ...args) {
        context = context || window
        const symbol = Symbol()
        context[symbol] = this;
        const res = context[symbol](...args)
        delete context[symbol]
        return res
    }
    // apply实现
    Function.prototype.apply2 = function (context, args) {
        context = context || window
        const symbol = Symbol()
        context[symbol] = this;
        const res = context[symbol](...args)
        delete context[symbol]
        return res
    }
    // bind实现
    Function.prototype.bind2 = function (context, ...args) {
        context = context || window
        const self = this;
        function Fn(...args2) {
            return self.apply(this instanceof Fn ? this : context, [...args, ...args2])
        }
        function middle() { }
        middle.prototype = this.prototype
        Fn.prototype = new middle()
        return Fn
    }

23.Map和Object的区别

  • Map是有序的,其键可以是任意类型
  • Object是无序的,其键只能是字符串或者symbol
  • Map可以通过size属性获取键值对个数
  • Object只能自己去获取
  • Map是可迭代的
  • Object迭代需要先获取键然后才能迭代

24.Map和WeakMap的区别

  • Map的键可以是任意类型
  • WeakMap的键只能是对象(null除外)的若引用
  • Map的键引用的对象被释放后,但因为Map中存在该对象,所以垃圾回收机制不会回收
  • WeakMap则与Map不同,它存放的键外若引用,即当对象的其他引用被释放后,WeakMap对应的对象也会被释放(即不作为垃圾回收机制的引用)

25.JSON的理解

JSON是一种文本的轻量级格式,可以被任何编程语言读取,并作为数据格式来传递。
JSON提供了两个api(JSON.stringify、JSON.parse)来进行js和json之间的转换,但优缺点:

  • 不能转换具有symbol类型的属性
  • 不能转换函数、日期
  • 不支持循环引用
  • 性能问题,因为它会遍历整个对象

26.js脚本延迟加载的方式有哪些

  • async
    • 当html解析遇到带有async的script标签时,会另开一个线程去下载js脚本,不会阻塞DOM的解析,但当async标签的脚本下载完毕后,会立即执行该脚本,此时会阻塞HTML的解析
    • 当存在多个async修饰的script标签时,它们不会按书写顺序执行,而是谁先下载完毕谁先执行
  • defer
    • 当html解析遇到带有defer的script标签时,会另开一个线程去下载js脚本,不会阻塞html的解析,且只有当html解析完毕后,才会执行对应的脚本
    • 当存在多个defer修饰的script标签时,会按照书写顺序执行脚本
  • 动态生成script标签
  • 定时器延迟加载js脚本
  • 让js脚本放在文档底部

27.js类数组对象的定义

一个拥有length属性和若干索引属性的对象就可以成为类数组,类数组和数组类似,但它不能调用数组的方法,如arguments和DOM操作获取的数组对象,函数也可以看作类数组,因为它含有lenght属性(接收形参的个数)
若想要调用数组的方法,可以通过call、apply、bind等实现,如Array.prototype.slice.call()

28.数组的原生方法

  • length
  • slice
  • indexOf
  • sort
  • reverse
  • splice
  • join
  • Array.prototype.isArray
  • push
  • pop
  • shift
  • unshift
  • includes
  • concat
  • forEach
  • reduce
  • map
  • every
  • some
  • filter

29.如何将类数组转为数组

  • Array.from()
  • Array.prototype.slice.call()
  • Array.prototype.slice.apply()
  • 拓展运算符 [...xxx]
  • Array.prototype.concat.call([],类数组)

30.什么是DOM和BOM

  • DOM:文档对象模型,它指的是把文档当作一个对象,这个对象定义了处理网页内容的方法和接口
  • BOM:浏览器对象模型,它指的是把浏览器当作一个对象,这个对象定义了与浏览器进行交互的方法和接口。 BOM的核心是window,window对象具有双重角色,它即是全局对象,又是通过js访问浏览器窗口的一个接口,在网页中定义的任意对象、变量和函数,都作为全局对象的一个属性或方法存在,window有location、navigator、document对象等

31.对ajax的理解,并实现一个ajax

ajax是指通过js的异步通信,从服务器获取XML文档,并从文档中提取对应数据,最终操作DOM进行数据更新,从而实现不用刷新整个页面

const ajax = options => {
    return new Promise((resolve, reject) => {
        const { method = 'get', data = {}, url } = options
        let xhr

        function objToString(data) {
            let arr = []
            data.t = new Date().getTime()
            for (let key in data) {
                arr.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))
            }
            return arr.join('&')
        }
        const str = objToString(data)

        if (window.XMLHttpRequest) {
            xhr = new XMLHttpRequest()
        } else {
            xhr = new ActiveXObject('Mircrosoft.xmlHttp')
        }

        if (method.toLowerCase() === 'get') {
            // get请求
            xhr.open(method, url + '?' + str, true)
            xhr.send()

        } else {
            // post请求
            xhr.open(method, url, true)
            xhr.setRequestHeaher('Content-Type', 'application/www-form-urlencoded')
            xhr.send(str)
        }

        xhr.onreadystatechange  = function () {
            if (xhr.readyState === 4) {
                if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
                    resolve(xhr.responseText)
                } else {
                    reject(xhr.responseText)
                }
            }
        }
    })
}

32.js为什么会进行变量提升,它导致了什么问题

33.ES6模块与CommonJS模块有什么异同?

  • CommonJS是浅拷贝,ES6是对模块的引用,即只能读不能改,类似于const声明的变量,即指针不能改变
  • 也就是说CommonJs可以修改引用指针,也可以修改引用数据内部的变量
  • ES6只能修改引用数据内部的数据,无法重新赋值
  • CommonJs使用require和module.export
  • ES6使用import和export

34.常见的DOM操作有哪些

1.DOM节点的获取

  • document.querySelect()
  • document.querySelectAll()
  • document.getElementById()
  • document.getElementsByClassName()
  • document.getElementsByTagName()

2.DOM节点的创建

  • document.createElement() 创建节点
  • el.appendChild(dom) 添加节点到页面上

3.删除DOM节点

// 获取目标元素的父元素
var container = document.getElementById('container')
// 获取目标元素
var targetNode = document.getElementById('title')
// 删除目标元素
container.removeChild(targetNode)

35.如何判断一个对象是否属于某个类?

  • instanceof
  • 通过constructor来判断,但constructor容易被篡改
  • Object.prototype.toString() 来判断

36.for in 和 for of 的区别

for in 主要用于遍历对象,它会遍历对象的整个原型链,性能较差 for of是es6新出的,可以用于遍历带有iterator接口的数据结构(如对象、数组、set、map)

37.ajax、fetch、axios的区别

38.原型和原型链

在js中,每个对象都有与之对应的原型,可以通过__proto__访问到,当我们通过构造函数创建对象时,实例对象的__proto__ 指向构造函数的原型,这个原型包含所有通过这个构造函数创建的实例的共享属性和方法。
当访问对象的某个属性或方法时,会先在对象自身上查找是否存在这个属性,若不存在,则沿着原型链(__proto__)继续往上查找,一直找到原型为null时为止

39.执行上下文/作用域链/闭包

1.对闭包的理解

在js中,闭包是指在当前作用域访问另一个作用域中的变量,即一个函数中返回另一个函数,另一个函数中使用到上一个函数的变量。 用途:

  • 通过闭包可以访问另一个函数作用域中的变量的特点,我们可以通过函数返回一个函数,内部使用上一个函数的变量,从而使得可以在外部访问函数内部的私有的变量
  • 正常情况下,函数执行完毕(即函数执行上下文)后会被弹出栈,但由于闭包的特性可以访问函数内部的变量,导致执行上下文的变量仍然存在内存中,因为闭包保存了对这个变量的引用,所以这个变量不会被垃圾回收机制回收,容易造成内存溢出

2.对作用域、作用域链的理解

  • 作用域分为全局作用域、函数作用域、块级作用域。

    • 全局作用域

      • window对象上的属性拥有全局作用域
      • 没有声明直接使用的变量具有全局作用域
      • 最外层函数及最外层函数外声明的变量(var)具有全局作用域
      • 过多的全局作用域变量容易造成变量污染
    • 函数作用域

      • 在函数内部声明的变量(var)具有函数作用域
      • 内部的作用域可以访问外部作用域的变量
      • 反之外部作用域无法访问到内部的作用域
    • 块级作用域

      • 使用let、const声明的变量具有块级作用域
      • 比较适合用于for循环
  • 作用域链

    • 在当前作用域中查找某个属性时,若没有该属性,则沿着作用域链(即外层/父级作用域)上查找,一直找到顶级作用域(全局作用域),这一层层关系就是作用域链
    • 作用域链本质就是一个指向变量对象的指针列表,变量对象包含当前作用域定义的变量、函数、参数。第一位是当前作用域,最后一位是全局作用域
  • 自由变量

    • 在当前作用域查找所需的变量,但当前作用域没有该变量,则这个变量就是自由变量

40.对执行上下文的理解

1.执行上下文类型

  • 全局执行上下文
    • 在非函数内部的都是全局执行上下文,它会在全局全局一个window对象,并且this指向这个window
  • 函数执行上下文
    • 函数执行上下文是通过函数调用创建的,它可以有多个
  • eval执行上下文

2.执行上下文栈

代码执行时,会先创建全局执行上下文,并压入到栈底,然后代码执行过程中遇到函数调用,会创建函数执行上下文,并压入栈中,当函数执行完毕后,会进行出栈操作,在这一个过程中,全局执行上下文只能有一个,且一直是栈底,只有当页面销毁时才会释放,栈顶是当前正在执行的函数上下文

3.创建执行上下文

创建执行上下文有两个阶段:创建阶段和执行阶段

  • 创建阶段会为执行阶段做一些准备
    • 变量对象
      • 首先会确定函数形参
      • 确定arguments(形参个数length、形参赋值)
      • 确定函数字面量声明,并赋值
      • var变量声明,但不赋值
    • this
    • 作用域
  • 执行阶段是进行代码的执行(对创建阶段中的变量进行赋值,执行代码)

在执行一点JS代码之前,需要先解析代码。解析的时候会先创建一个全局执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来,变量先赋值为undefined,函数先声明好可使用。这一步执行完了,才开始正式的执行程序。 在一个函数执行之前,也会创建一个函数执行上下文环境,跟全局执行上下文类似,不过函数执行上下文会多出this、arguments和函数的参数。

全局上下文:变量定义,函数声明 函数上下文:变量定义,函数声明,this,arguments