😭又要面试了,该回顾一下 JavaScript 了

53 阅读13分钟

前言

javascript 几乎每天都要用到,用倒是会用了,但是概念就记不太清楚了,今天来回顾一下

数据类型

javascript 共有巴中数据类型:undefined 、 null 、 boolean 、number 、 string 、 object 、 symbel 、 bigint

数据类型可以分为 基本数据类型 和 引用数据类型 :

  • 基本数据类型:(undefined 、 null 、boolean 、number 、string)栈
  • 引用数据类型:(对象、数组和函数) 堆

两种数据类型的区别在于存储位置不同

  • 基本数据类型直接存储在栈(stack)中的简单数据段,占据空间小,大小固定,属于被频繁使用数据,所以放入栈中存储
  • 引用数据类型是存储在堆(heap)中的对象,占据空间大、大小不固定。引用数据类型在栈中存储了指针,该指针指向堆中实体的起始地址。当解释器寻找引用值时,会首先检索它在栈中的地址,取得地址后从堆中获得实体

在数据结构中

  • 在数据结构中,栈中数据的存储方式为先进后出
  • 堆是一个优先队列,是按优先级来进行排序的,优先级可以按照大小来规定

在操作系统中,内容被分为栈区和堆区:

  • 栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式于数据结构中的栈
  • 堆区内容一般由开发着分配释放,若开发者不释放,程序结束时可能由垃圾回收机制回收

symbal 和 BigInt 是 ES6 中新增的数据类型

Symbal 代表创建后独一无二且不可变的数据类型,它主要是为了解决可能出现的全局变量冲突的问题

BigInt是一种数字类型的数据,它可以表示任何精度格式的整数,使用 BigInt 可以安全存储和操作大整数,即使这个数已经超出了 Number 能够表示的安全整数范围

数据类型的检测:

  1. typeof

用于判断基本数据类型,因为数组、对象 和 null 都会被判断为 object

console.log(typeof 2);            //number
console.log(typeof true);         //boolean
console.log(typeof 'str');         //string
console.log(typeof function(){});  //object
console.log(typeof {});           //object
console.log(typeof undefined);    //undefined
console.log(typeof null);        //object
  1. Instanceof

Instanceof可以正确判断对象的类型,其内部运行机制是判断在原型链中能否找到该类型的原型,但是 Instanceof只能正确判断引用数据类型,而不能判断数据类型。Instanceof运算符可以用来测试一个对象在其中原型链中是否存在一个构造函数的 prototype属性

console.log(2 instanceof Number)  //false
console.log(true instanceof Boolean)  //fasle
console.log('str' instanceof String)   //false
console.log([] instanceof Array)   //true
console.log(function(){} instanceof Function)   //true
console.log({} instanceof Object)   //true
  1. constructor

constructor有两个作用,一是判断数据的类型,二是对象实例通过 constructor 对象访问它的构造函数。

console.log((2).constructor === Number); // true 
console.log((true).constructor === Boolean); // true 
console.log(('str').constructor === String); // true 
console.log(([]).constructor === Array); // true 
console.log((function() {}).constructor === Function); // true 
console.log(({}).constructor === Object); // true
  1. null 和 undefined 区别

首先 undefinednull 都是基本数据类型,这两个基本数据类型分别都有一个值,就是 undefinednull

undefined 代表的含义是未定义,null 代表的含义是空对象。一般变量声明了但还没定义的时候会返回 undefined ,null 主要用于赋值给一些可能会返回对象的变量,作为初始化,使用 typeof 进行判断,返回 “object” 。

  1. object.is() 与操作 操作符 "===" 、 "==" 的区别
  • 使用双等号 (==)进行比较相等判断时,如果两边的类型不一致,则会进行强制类型转化后再进行比较
  • 使用三等号 (===)进行比较相等判 断时,如果两边的类型不一致,不会进行强制类型转化后,直接返回false
  • 使用object.is 来进行相等判断时,一般情况下和三等号的判断相同,它处理了一些特殊的情况,比如 -0 和 +0 不再相等,两个 NaN 是相等的
  1. 深拷贝和浅拷贝
  • 深拷贝:复制并创建一个一模一样的对象,不共享内存,修改新对象,不会影响旧对象的值
  • 浅拷贝:浅拷贝是指对基本数据类型的值拷贝,以及对[对象类型的地址拷贝],也就是说它是将引用对象的所有值引用下来,但是仍旧指向同一个引用地址,拷贝的数据发生改变之后,旧的数据也会随着改变

主要使用 object.assign 、拓展运算符 、`json.parse(json.stringify(obj))

object.assign

可以用于拷贝复杂类型,但是拷贝对象的数据会有限制,基本数据类型是深拷贝,引用类型是深拷贝,也就是说深拷贝第一层数据,第二层之后都是浅拷贝(一深二浅),不能用于拷贝循环引用类型

let obj = {name:"hua",age :{a:18}}
let obj1 = Object.assign({},obj)
obj1.name = "张三"
obj1.age.a = 20
console.log('obj',obj)  //{ name: 'hua', age: { a: 20 } }
console.log('obj1',obj1)  //{ name: '张三', age: { a: 20 } }

由上述代码可以看出,对象里的 name是基本数据类型,object.assign对于基本数据类型是深拷贝,所以修改 obj1的数据 name也不会影响到 obj的。但是 age 是引用数据类型,obj1发生改变会影响到 obj 的数据

拓展运算符

拓展运算符可用于对象 和数组的拷贝,在拷贝中也具有 [一深二浅]的特性

拓展运算符(...)用于取出参数对象中所有可遍历的属性,拷贝到当前对象中

let obj = {name:"张三",age:{a:18}}
let arr = [1,2,3,[5,4,6,[11,12,13]]]
let newObj = {...obj}
let newArr = [...arr]
newObj.name = "李四"
newObj.age.a = 20
console.log(obj,newObj) //{ name: '张三', age: { a: 20 } }
// { name: '李四', age: { a: 20 } }

newArr[0] = 9
newArr[3][0] = 88
console.log(arr) //[ 1, 2, 3, [ 88, 4, 6, [ 11, 12, 13 ] ] ]
console.log(newArr) //[ 9, 2, 3, [ 88, 4, 6, [ 11, 12, 13 ] ] ]

Json.parse(Json.stringify(obj))

Json.parse(Json.stringify(obj)) 在将对象转化成字符串,然后再转化成对象的过程中新创建了一个空间,把原对象的数据复制过来,同时这也是日常开发用到最多的深拷贝

let obj = {name:"张三",age:{a:18}}
let  newObj = Json.parse(Json.stringify(obj))
console.log(obj,newObj) 
//{ name: '张三', age: { a: 18 } }
// { name: '李四', age: { a: 20 } }

但是也存在了一些局限性

  • 如果对象中存在时间对象,时间对象会变成字符串
  • 如果对象里有RegExp、Error对象,则序列化的结果将只得到空对象
  • 如果obj里有函数,undefined,则序列化的结果会把函数, undefined丢失
  • 如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null
  • 如果对象中存在循环引用的情况也无法正确实现深拷贝
function Person (age){
    this.age = age
}

let obj = {
    data:new Date(),
    RegExp: new RegExp(),
    err: new Error(),
    fn: ()=>{
        console.log(222)
    },
    un:undefined,
    nan:NaN,
    c:{
        fn: new Date()
    },
    age: new Person()
}
// obj.a = obj ;
let json = JSON.parse(JSON.stringify(obj))
console.log('obj', obj)
console.log('json', json)
//结果打印
obj {
  data: 2023-04-06T05:31:42.864Z,
  RegExp: /(?:)/,
  err: Error
      at Object.<anonymous> (C:\Users\Administrator\Desktop\易\jsTest.js:28:10)
      at Module._compile (internal/modules/cjs/loader.js:1063:30)
      at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
      at Module.load (internal/modules/cjs/loader.js:928:32)
      at Function.Module._load (internal/modules/cjs/loader.js:769:14)
      at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)
      at internal/main/run_main_module.js:17:47,
  fn: [Function: fn],
  un: undefined,
  nan: NaN,
  c: { fn: 2023-04-06T05:31:42.864Z },
  age: Person { age: undefined }
}
json {
  data: '2023-04-06T05:31:42.864Z',
  RegExp: {},
  err: {},
  nan: null,
  c: { fn: '2023-04-06T05:31:42.864Z' },
  age: {}
}

** new 操作符的实现原理**

  1. 首先创建一个空对象 {}
  2. 设置原型,将对象的原型设置为函数的 prototype对象
  3. 让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)
  4. 判断函数是否有返回值,如果是基本数据类型,返回创建时的对象,如果是引用数据类型,则返回这个引用类型的对象
function objectFactory(){
    let newObject = null
    let constructor = Array.prototype.shift.call(arguments)
    let result = null
    //判断参数是否是一个函数
    if(typeof constructor !== "function"){
        console.error("type error")
        return
    }
    //新建一个空对象,对象的原型为构造函数的 prtotype对象
    newObject = Object.create(constructor.prototype)
    //将this 指向新建对象,并执行函数
    result = constructor.apply(newObject , arguments)
    //判断返回对象
    let flag = result && (typeof result === 'object' || typeof result === "function")

    return falg ? result : newObject
}
let fn = objectFactory()

JSON的理解

JSON 是一种基于文本的轻量级的数据交换格式,它可以被任何的编程语言读取和作为数据格式来传递

在项目开发中使用JSON作为前后端数据交换的方式,在前后端通过将一个符合 JSON 格式的数据结构序列化成 JSON 字符串,然后将它传递到后端,后端通过JSON 格式放入字符串解析后生成对应的数据结构,以此来实现前后端数据的一个传递

JSON.stringify函数,将对象转化为JSON格式的字符串

JSON.parse()函数将JSON格式的字符串转换为一个 js 数据结构(对象)

常用的数组操作方法

改变原数组的方法:

splice() 添加/删除数组元素

splice()方法向/从数组添加/删除 元素,然后返回被删除的元素

array.splice(index,num,item1,....)

  1. index :必填。整数,规定添加/删除元素的位置,使用负数从数组结尾处规定位置
  2. num:可选,要删除元素的数量,如果设置为0,则不删除数量
  3. item1...,可选。向数组添加新的元素
let arr = [1,2,3,4,5,6]
let item = arr.splice(0,3,8,9,10)
console.log(item) //[ 1, 2, 3 ]
console.log(arr)  //[ 8, 9, 10, 4, 5, 6 ]

sort()数组排序

sort()方法对数组元素进行排序,并返回这个数组

sort 的比较函数有两个默认参数,要在函数中接收这两个参数,这两个参数是数组中两个要比较的元素,通常我们用 a 和 b来表示要比较的元素

  • 若比较函数返回值 < 0,那么 a 将排在 b 的前面;
  • 若比较函数返回值 = 0 ,那么 a 和 b 相对位置不变
  • 若比较函数返回值 > 0 ,那么 a 将排在 b 的前面
let arr = [1,2,3,10,20,17,12,16,36]
arr.sort((a,b) =>{
    return a-b
})
console.log(arr) //[ 1,  2,  3, 10, 12,16, 17, 20, 36 ]
arr.sort((a,b) =>{
    return b-a
})
console.log(arr) //[ 36, 20, 17, 16, 12, 10,  3,  2,  1]

pop()删除数组中的最后一个元素

pop()方法删除数组中的最后一个元素,并返回这个元素

let arr = [1,2,3]
let item = arr.pop() //3
console.log(arr)  //[1,2]

shift()删除数组中的最后一个元素

shift()方法删除数组中的第一个元素,并返回这个元素

let arr = [1,2,3]
let item = arr.shift() //1
console.log(arr)  //[2,3]

pop()向数组的末尾添加元素

push()方法向数组的末尾添加元素,并返回新的长度

let arr = [1,2,3]
let item = arr.push(4,5) //5
console.log(arr)  //[1,2,3,4,5]

unshift()向数组的开头添加元素

push()方法向数组的开头添加元素,并返回新的长度

let arr = [1,2,3]
let item = arr.unshift(4,5) //5
console.log(arr)  //[4,5,1,2,3]

reverse()颠倒数组中元素的顺序

reverse()方法用于颠倒数组中的元素的顺序

let arr = [1,2,3]
let item = arr.unshift(4,5) //5
console.log(arr)  //[4,5,1,2,3]

copyWithin()指定位置的成员复制到其他位置

在当前数组内部,将指定的成员复制到其他位置

let arr = [1,2,3,4,5
let item = arr.copyWithin(0,-2,-1) //[4,2,3,4,5]
let a=['OB1','Koro1','OB2','Koro2','OB3','Koro3','OB4','Koro4','OB5','Koro5']
let a=['OB1','Koro1','OB2','Koro2','OB3?4','Koro3','OB4','Koro4','OB5','Koro5']
a.copyWithin(2,4,6)
console.log(a) //[ 'OB1', 'Koro1','OB3'?2, 'Koro3','OB3?4', 'Koro3' ?5, 'OB4', 'Koro4','OB5', 'Koro5' ]

arr.copyWithin(target,start,end)

  • aiget (必须):从该位置开始替换数据,如果为负数,表示倒数
  • start (可选):从该位置开始读取数据,如果为负数,表示倒数
  • end (可选):从该位置前停止读取数据,默认等于数组长度

也就是说 从下标为2的位置开始替换,替换的数据从下标为4的开始,到6前结束,替换的数据就是下标第四第五个,这两个数据从2替换到5

fill()填充数组

fill()使用给定值,填充一个数组

  • 第一个元素:要填充数组的值
  • 第二个元素:填充的开始位置,默认值为0
  • 第三个元素:填充的结束位置,默认是数组长度
let arr = [1,2,3]
 arr.unshift(‘a’,1,2) //5
console.log(arr)  //[1,'a',2]

不会修改原数组的方法

  • slice() 方法返回一个从开始到结束(不包括结束)选择的数组的一部分浅拷贝到一个新数组,且原数组不会被修改
  • join()方法用于把数组中的所有元素通过指定的分隔符进行分隔放入一个字符串,返回生成的字符串
  • cancat()方法用于合并两个或多个数组,返回一个数组
  • indexOf()查找数组是否存在某个元素的第一个索引,如果不存在,则返回-1
  • lastIndexOf()查找数组是否存在某个元素的最后一个索引,如果不存在,则返回-1
  • includes()查找数组是否包含某个元素,返回布尔值

数组遍历方法

  • forEach 按照升序为数组中含有效值的每一项执行一次回调函数,没有返回值,会改变原数组
  • every 用于检测数组内所有元素都符合函数定义的条件
  • some 只要有一个元素满足条件,则表达式返回true,剩余的元素不会再执行检测
  • filter 筛选返回所有满足条件的元素
  • map 创建一个新数组,其结果是该数组中每一元素都调用一个提供的函数后返回的结果,有返回值,不会改变原数组

arguments 参数是类数组

arguments是一个对象,它的属性是从0开始依次递增的数字,还有calleelength 等属性,与数组相似,但是它却没有数组常见的方法属性,如 forEach ,reduce等,所以它叫类数组

遍历类数组,有三个方法:

  • 将数组的方法用到类数组上,这时候就可以使用 callapply 方法
function foo(){
Array.protottypr.forEach.call(arguments,a => console.log(a))
}
  • 使用 Array.from 方法将将类数组转化成数组:
function foo(){
const arrArgs = Array.from(arguments)
arrArgs.forEach(a => console.log(a)) }
  • 使用拓展运算符将类数组转化成数组
function foo(){
const arrArgs = [...arguments]
arrArgs.forEach(a => console.log(a)) }

DOM 和 BOM

DOM指的是文档对象模型,它值的是把文档当做一个对象,这个对象主要定义了处理网页内容的方法和接口

BOM 指的是浏览器对象模型,把浏览器当做一个对象来对待

闭包

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

闭包的用途

  • 闭包的第一个用途是使我们在函数外部能够访问到函数内部的变量。通过使用闭包,可以通过在外部调用闭包函数,从而在外部访问到内部的变量,可以使用这种方法来创建私有变量
  • 闭包的另一个用途是使已经运行结束的函数上下文中的变量对象继续留在内存内,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收

最后:

本文纯属是为了学习当做笔记,后续还会对面试题做一个整理,如果哪里写的不好,欢迎指正,勿喷