IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> JavaScript面试题 -> 正文阅读

[JavaScript知识库]JavaScript面试题

JavaScript

var let const

var

  • var声明的范围是函数作用域 变量会在函数退出时被销毁
  • var存在变量声明提升,也就是把所用变量都拉到函数作用域的顶部
  • 此外,反复多次var声明同一个变量也没有问题

let

  • let声明的范围是块级作用域
  • 同一个块作用域不能重复声明

const

  • const用来声明不改变的常量 尝试修改会报错TypeError
  • 也不允许重复声明 也是块作用域

变量和函数声明提升

  • 对所有函数声明进行提升(除了函数表达式和箭头函数),引用类型的赋值
  • 对变量进行提升,只声明,不赋值,值为undefined

作用域 作用域链

  • 规定变量和函数的可使用范围称作作用域
  • 每个函数都有一个作用域链,查找变量或者函数时,需要从局部作用域到全局作用域依次查找,这些作用域的集合称作作用域链

new创建对象

new内部实现了什么

  • 创建一个新对象
  • 使新对象的__proto__指向原函数的prototype
  • 改变this指向(指向新的obj)并执行该函数,执行结果保存起来作为result
  • 判断执行函数的结果是不是null或Undefined,如果是则返回之前的新对象,如果不是则返回result

手写一个new

   // 手写一个new
function myNew(fn, ...args) {
      // 创建一个空对象
    let obj = {}
      // 使空对象的隐式原型指向原函数的显式原型
    obj.__proto__ = fn.prototype
      // this指向obj
    let result = fn.apply(obj, args)
      // 返回
    return result instanceof Object ? result : obj
}

字面量和new创建的对象和Object.create(null)创建的对象区别

  • 字面量和new创建的对象会继承Object的方法和属性,他们的隐式原型会指向Object的显式原型
  • 而Object.create(null)创建出来的对象原型为null,作为原型链的顶端,自然也没有继承Object的方法和属性

数据类型

简单数据类型(也称原始类型)

  • Undefined
  • Null
  • Boolean
  • Number
  • String
  • Symbol (ECMAScript6新增)

复杂数据类型

  • Object

Object可以细分为Object、Function、Array、Date、RegExp

判断数据类型的几种方式

  1. typeof

typeof是一个操作符而不是一个函数,typeof null的值为Object

原因:javascript 数据在底层的存储是2进制的,存储的前三位标识类型,Object类型的前三位是000,而null所有数字都是000,所以typeof null 值为Object

  1. instanceof

操作符instanceof 用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上

  1. Object.prototype.toString.call()

Object.prototype.toString(o) 是 Object 的原型方法

  1. constructor

Symbol

symbol是一个ES6标准种新增的一种基本数据类型,每一个 symbol 的值都是唯一的,并且 symbol 类型的值可以作为对象的属性标识符使用,这也是 symbol 类型设计的目的。

用法

创建一个 symbol 的值需要使用 Symbol() 函数,而不能使用 new 命令。

let s1 = Symbol('sym');

由于生成的 symbol 是一个值而不是对象,所以不能为其添加属性

Symbol() 函数可以接受一个字符串作为参数,表示对该值的描述,因此即使定义 symbol 使用相同的参数互相之间也不是相同的:

let s1 = Symbol('sym');
let s2 = Symbol('sym');
s1 === s2 ; // false

数组

将类数组转化为数组

  1. 通过Array.from()方法来实现转化

Array构造函数有两个ES6新增的用于创建数组的静态方法:from()和of() 。from()用于将类数组结构转化为数组实例,而of()用于将一组参数转化为数组实例

Array.from(arrayLike);
console.log(Array.from("Matt")); //输出为["M", "a", "t", "t"]

const a1 = [1, 2, 3, 4];    //Array.from()还可以对数组a1进行浅复制
const a2 = Array.from(a1);
console.log(a2);      

Array.from()可以接受3个参数 第一个参数是一个类数组对象,或者有一个length数字那个和可索引元素的结构。第二、第三个为可选参数,详见JS红宝书P139

  1. 通过call 调用数组的slice方法来实现转换

Array.prototype.slice.call(arrayLike);

  1. 通过call调用数组的splice方法来实现转换

Array.prototype.splice.call(arrayLike);

  1. 通过apply调用数组的concat方法来实现转换

Array.prototype.concat.apply([], arrayLike);

数组的方法

栈方法

  • push()

push()接受任意数量的参数,并将他们添加到数组末尾,返回数组的最新长度

let colors = new Array();
let count = colors.push('red', 'green');
alert(count);       //count 2
  • pop()

pop()方法用于删除数组的最后一项,同时减少数组的length值,返回被删除的项

队列方法

  • shift()

删除数组的第一项返回数组的一项,然后数组长度减1

  • unshift()

在数组开头添加任意多个值,然后返回新的数组长度

排序方法

  • reverse() 将数组元素反向排列
  • sort()

默认情况下,sort()会按照升序重新排列数组,sort()会在每一项上调用String()转型函数,然后比较字符串来决定顺序,即使数组的元素都是数值,也会先把数组转换为字符串再比较、排序,但这在多数情况下是不合适的

为此,sort()可以接受一个比较函数,用于判断哪个值应该排在前面。

//比较函数接受两个参数,如果第一个参数应该排在第二个参数前面,就返回负值;如果第一个参数应该排在第二个参数后面,就返回正值
function compare(value1, value2){      
    if(value1 < value2){
        return -1;
    }else if (value1 > value2){
        return 1;
    }else {
        return 0;
    }
}

操作方法

  • concat()

可以在现有数组全部元素的基础上创建一个新的数组。它首先会创建一个当前数组的副本,然后再把它的参数添加到副本末尾,最后返回这个新构建的数组。如果参数不是数组,则直接把它们添加到结果数组的末尾。

  • slice()

不影响原数组,slice()用于创建一个包含原有数组中一个或多个元素的新数组。

slice()方法可以接受一个或两个参数:返回元素的开始索引和结束索引。如果只有一个参数,则sliec()会返回该索引到数组末尾的所有元素。

  • splice()

splice()的主要目的是在数组中间插入元素,但又3种不同的方式使用:

  1. 删除 需要给splice()传2个参数:要删除的第一个元素位置和要删除的元素数量 比如:splice(0, 2)
  2. 插入 需要给splice()传3个参数:开始位置、0(要删除的元素数量)、要插入的元素(可以任意多个) 比如:splice(2, 0, “red”, “green”)
  3. 替换 删除元素的同时在指定位置插入新元素,同样要传入3个参数:开始位置、要删除元素的数量和要插入的任意多个元素 比如:splice(2, 1, “red”, “green”)

迭代方法

forEach() 和 map() 的区别

forEach(): 对数组的每一项都运行传入的函数,没有返回值

map(): 对数组的每一项都运行传入的函数,返回由每次函数调用的结果构成的数组

数组去重 数组展平

var arr = [1, 2, 1, 1, 1, 2, 3, 3, 3, 2]

// 最low1
let newArr2 = []
for (let i = 0; i < arr.length; i++) {
  if (!newArr2.includes(arr[i])) {
    newArr2.push(arr[i])
  }
}
console.log(newArr2);
// 最low2
let arr2 = [1, 2, 1, 1, 1, 2, 3, 3, 3, 2]
for (let i = 0; i < arr2.length; i++) {
  var item = arr2[i]
  for (let j = i + 1; j < arr2.length; j++) {
    var compare = arr2[j];
    if (compare === item) {
      arr2.splice(j, 1)
      j--
    }
  }
}
console.log(arr2);


// 基于对象去重
let arr3 = [1, 2, 1, 1, 1, 2, 3, 3, 3, 2]
let obj = {}
for (let i = 0; i < arr3.length; i++) {

  let item = arr3[i]
  if (obj[item]) {
    arr3[i] = arr3[arr3.length - 1]
    arr3.length--
    i--
    continue;
  }
  obj[item] = item

}
console.log(arr3);
console.log(obj);

// 利用Set
let newArr1 = new Set(arr)
console.log([...newArr1]);


let arr4 = [1, 2, 1, 1, 1, 2, 3, 3, 3, 2]

//利用reduce
newArr4 = arr4.reduce((prev, curr) => prev.includes(curr)? prev : [...prev,curr],[])
console.log(newArr4);
console.log(document);

rest参数

rest参数,形式为:…变量名。arguments对象不是数组,只是可以下标访问而已,而rest参数是一个真正的数组。

function f(a, b, ...args) {
  
}

原型和原型链

原型

每个函数都有 prototype 属性,指向原型对象,这个就是显式原型

通过函数实例化出来的对象有个_proto_属性,指向原型对象,这个就是隐式原型

原型有一个 constructor 属性,指回与之关联的构造函数。

原型链

原型链为ECMAScript的主要继承方式。其基本思想就是通过原型继承多个引用类型的属性和方法

js继承问题

原型继承

核心:将父类实例作为子类原型

优点:方法复用 由于方法定义在父类的原型上,复用了父类构造函数的方法

缺点:不能传参 子类实例共享了父类构造函数的引用属性 无法实现多继承

Parent.prototype.say = function () { // --- 将需要复?、共享的?法定义在?类原型上
    console.log('hello')
}

Child.prototype = new Parent() // 核? 第?次
Child.prototype.constructor = Child // 修正constructor指向

构造函数继承

核心:借用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类

优点:实例间独立

  • 创建子类实例,可以像父类构造函数传参数
  • 子类实例不共享父类构造函数的引用属性
  • 可实现多继承(通过多个call或者apply继承多个父类)

缺点:

  • 父类的方法不能复用 由于方法在父构造函数中定义,导致方法不能复用(因为每次创建子类实例都要创建一遍方法)
  • 子类实例,继承不了父类原型上的属性。(因为没有用到原型)
function Parent(name) {
    this.name = name; // 实例基本属性 (该属性,强调私有,不共享)
    this.arr = [1]; // (该属性,强调私有)
}
function Child(name, like) {
    Parent.call(this, name, like) // 核? 第一个参数是this现在指向哪里+name(父函数的参数)
    this.like = like;//孩子可以添加自己的属性
}

组合继承

核心:通过调??类构造函数,继承?类的属性并保留传参的优点;然后通过将?类实例作为?类原型,实现函数复?。

优点:

  • 保留构造函数的优点:创建?类实例,可以向?类构造函数传参数。
  • 保留原型链的优点:?类的?法定义在?类的原型对象上,可以实现?法复?
  • 不共享?类的引?属性

缺点:

由于调?了2次?类的构造?法,会存在?份多余的?类实例属性

function Parent(name) {
    this.name = name; // 实例基本属性 (该属性,强调私有,不共享)
    this.arr = [1]; // (该属性,强调私有)
}
Parent.prototype.say = function () { // --- 将需要复?、共享的?法定义在?类原型上
    console.log('hello')
}

function Child(name, like) {
    Parent.call(this, name, like) // 核? 第?次
    this.like = like;
}
Child.prototype = new Parent() // 核? 第?次
Child.prototype.constructor = Child // 修正constructor指向
let boy1 = new Child('?红', 'apple')
let boy2 = new Child('?明', 'orange')
// 优点1:可以向?类构造函数传参数
console.log(boy1.name, boy1.like); // ?红,apple
// 优点2:可复??类原型上的?法
console.log(boy1.say === boy2.say) // true详解js继承 5
// 优点3:不共享?类的引?属性,如arr属性
boy1.arr.push(2)
console.log(boy1.arr, boy2.arr); // [1,2] [1] 可以看出没有共享arr属性。
// 缺点1:由于调?了2次?类的构造?法,会存在?份多余的?类实例属性

寄?组合继承

function Parent(name) {
    this.name = name; // 实例基本属性 (该属性,强调私有,不共享)
    this.arr = [1]; // (该属性,强调私有)
}
Parent.prototype.say = function () { // --- 将需要复?、共享的?法定义在?类原型上
    console.log('hello')
}

function Child(name, like) {
    Parent.call(this, name, like) // 核?
    this.like = like;
}
// 核? 通过创建中间对象,?类原型和?类原型,就会隔离开。不是同?个啦,有效避免了?式4的缺点。
Child.prototype = Object.create(Parent.prototype)
// 这?是修复构造函数指向的代码
Child.prototype.constructor = Child
let boy1 = new Child('?红', 'apple')
let boy2 = new Child('?明', 'orange')
let p1 = new Parent('?爸爸')
注意: 这种? 法也要修复构造函数的
修复代码: Child.prototype.constructor = Child详解js继承 7
修复之后: console.log(boy1.constructor); // Child
console.log(p1.constructor); // Parent 完美😊

闭包 内存泄漏 垃圾回收机制

闭包

一个函数访问另一个函数内部的变量就形成闭包

作用:

  • 实现封装,防止变量流入其他环境发生命名冲突
  • 变量不会被销毁,保护函数内的变量安全

缺点:导致变量不会被垃圾回收机制回收,造成内存消耗

解决方法:在使用完变量后手动为它赋值为null

内存泄漏

不再用的内存没有被及时释放出来,导致该段内存无法被使用就是内存泄漏

JS垃圾回收机制

  • 标记清除法

垃圾回收程序运行的时候,会标记内存中的所有变量,然后它会将所有在上下文中的变量,以及被在上下文中的变量引用的变量的标记去掉,在此之后还有标记的变量就是待删除的

  • 引用计数法

其思路是每个值都记录它被引用的次数,当一个值的引用数为0时,就可以安全的收回其内存了,垃圾回收程序下次运行的时候就会释放引用数为0的值的内存

对象的深浅拷贝

浅拷贝

遍历赋值

//for in
function clone(obj){
    var cloneObj = {}
    for (var key in obj){
        if (obj.hasOwnProperty(key)){
            cloneObj[key] = obj[key]
        }
    }
    return cloneObj
}

深拷贝

//JSON.stringfy() 与 JSON.parse
function deepClone(obj){
    return JSON.parse(JSON.stringfy(obj))
}
//for in
function deepClone(obj){
    if(!obj || typeof obj != "object") return obj
    if(obj instanceof RegExp) return new RegExp(obj)
    var cloneObj = new obj.constructor
    for(var key in obj){
        if(obj.hasOwnProperty(obj)){
            cloneObj[key] = deepClone(obj[key])
        }
    }
    return cloneObj
}

防抖和节流

防抖

function debounce(fn, delay){
    var timer = null;
    return function(){
        if(timer){
            clearTimeout(timer);
		}
        timer = setTimeout(fn, delay);
    }
    
}

节流

function throttle(){
    let timer = null;
    return function() {
        if(timer) return false;
        timer = setTimeout(() => {
            fn()
            timer = null
        }, delay)
    }
}

AJAX 手写请求过程

var xhr = new XMLHttpRequest()
xhr.open("get", url, true)
xhr.send(null)
xhr.onreadystatechange = function(){
    if (xhr.readyState == 4){
        if ((xhr.status >= 200&& xhr.status < 300)||xhr.status == 304){
            console.log(xhr.responseText)
        }
        else{
            throw new Error("error")
        }
    }
}

跨域

JSONP

JSONP格式包含两个部分:回调和函数

例如 callback({ "name": "Nicholas" });

  • JSONP通过同源策略涉及不到的"漏洞",也就是像 img 中的 src,link 标签的 href, script 的 src 都没有被同源策略限制到
  • JSONP只能get请求
  • 不安全,容易造成xss攻击
  • 原因:去请求别的域,得到别的域返回的数据,万一是个脚本,那就注入到自己的代码里了
function addScriptTag(src){
    var script = document.createElement("script")
    script.setAttribute('type','text/javascript')
    script.src = src
    document.appendChild(script)
}
//回调
function callback(res){   //参数是一个json类型的数据
    console.log(res.message)
}

CORS(跨源资源共享)

(CORS,Cross-Origin Resource Sharing)定义了浏览器与服务器如何实现跨源通信。

CORS背后的基本思路就是使用自定义的HTTP头部允许浏览器和服务器相互了解,以确实请求或响应应该成功还是失败

有简单请求和非简单请求,满足一下条件就是简单请求

  • 请求方法是HEAD、POST、GET
  • 请求头只有Accept、AcceptLanguage、ContentType、ContentLanguage、Last-Event-Id

简单请求在发送是会添加一个额外的头部叫Origin,Origin头部包含发送请求的页面的源(协议、域名和端口),以便服务器确定是否为其提供响应

非简单请求

通过一种叫预检请求的服务器验证机制,在进行非简单请求时,会先向服务器发送一个“预检”请求,这个请求使用OPTIONS方法,在这个请求发送之后,服务器可以确定是否允许这种类型的请求。

通过预检后,浏览器接下来的每次请求就类似于简单请求了

nginx代理跨域

  • nginx模拟一个虚拟服务器,因为服务器与服务器之间是不存在跨域的
  • 发送数据时,客户端 ->nginx->服务端
  • 返回数据时,服务端->nginx->客户端

箭头函数和普通函数区别

箭头函数是普通函数的简写,但是它不具备很多普通函数的特性

  • this指向,箭头函数的this指向他定义时所在的对象,而不是调用时的对象
  • 不会进行函数提升
  • 没有arguments对象,不能使用arguments,如果要获取参数的话可以使用rest运算符
  • 没有yield属性,不能作为生成器Generator使用
  • 不能new
    • 没有自己的this,不能调用call和apply
    • 没有prototype,new关键字内部需要把新对象的_proto_指向函数的prototype

call、apply、bind

当我们使用一个函数需要改变this指向的时候才会用到call apply bind

区别

  • 如果你要传递的参数不多,则可以使用fn.call(thisObj, arg1, arg2 ...)
  • 如果你要传递的参数很多,则可以用数组将参数整理好调用fn.apply(thisObj, [arg1, arg2 ...])
  • 如果你想生成一个新的函数长期绑定某个函数给某个对象使用,则可以使用const newFn = fn.bind(thisObj); newFn(arg1, arg2...)
  • call,apply,bind 不传参数自动绑定在 window

手写

Promise

promise是为了解决JS的什么问题?

解决了回调地狱的问题,增强了代码的可读性,所谓回调地狱就是指把函数作为参数层层嵌套请求,这样层层嵌套,人们称之为回调地狱,代码阅读性非常差。

Promise

Promise是异步编程的一种解决方案,是一个异步函数,主要是为了解决异步处理回调地狱(也就是循环嵌套的问题)而产生的,有三种状态,待定(pending)、兑现(fulfilled,有时候也称为“解决”,resolved)、拒绝(rejected)

let promise = new Promise((resolve, reject) => {
      //在这里执行异步操作
    if ( /*异步操作成功*/ ) {
        resolve(success)
    } else {
        reject(error)
    }
})

promise.then((success) => {
      //异步操作成功在这里执行
      //对应于上面的resolve (success)方法
}).catch((error) => {
      //异步操作先败在这里执行
      //对应于上面的reject(error)方法
})

promise.all promise.race

Promise类提供两个将多个期约实例组合成一个期约的静态方法:Promise.all()和Promise.race()

Promise.all()静态方法创建的期约会在每个包含的期约全部解决之后再解决。这个静态方法接受一个可迭代对象,返回一个新期约

  • 如果至少有一个包含的期约待定,则合成的期约也会待定。如果有一个包含的期约拒绝,则合成的期约也会拒绝
  • 如果所有期约都成功解决,则合成期约的解决值就是所有包含期约解决值的数组
  • 如果有期约拒绝,则第一个拒绝的期约会将自己的理由作为合成

Promise.race()返回一组集合中最先解决或拒绝的期约的镜像

Promise.all()和Promise.race区别

  • Promise.all() 是数组里面所有的promise对象执行结束之后返回一个存储所有promise对象的结果
  • Promise.race() 只会返回执行速度最快的那个promise对象返回的结果

async await

async用来修饰函数的声明,使用async修饰的函数会变成一个异步函数

await用来修饰函数的调用,被await修饰的函数必须返回一个promise异步对象,使用await修饰后,就会将promise异步对象转换成一个同步操作

Proxy

宏任务,微任务 事件循环EventLoop

宏任务一般是:包括整体代码script,setTimeout,setInterval。

微任务:Promise then catch finally,process.nextTick

setTimeout(() => console.log(4))
 
new Promise(resolve => {
  resolve()
  console.log(1)
}).then(() => {
  console.log(3)
})
 
console.log(2)
//1 2 3 4 

JS中把所有任务分成了两类,一类是同步任务,一类是异步任务。同步任务指的是在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程,而是进入“任务队列”的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

具体来说,异步执行的机制如下:

  1. 所有同步任务都在主线程上执行,形成一个执行栈
  2. 主线程之外还有一个任务队列,只要异步任务有个结果,就在任务队列之中放置一个事件
  3. 一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,看看里面有哪些事件。那些对应的异步任务于是结束等待状态,进入执行栈,开始执行
  4. 主线程不断重复上面的第三步。

模块化、common.js

为什么要使用模块化

  • 防止命名冲突
  • 更好的分离,按需加载
  • 更好的复用性
  • 更高的维护性

ES6和common.js的区别

  • commonjs模块输出的是值的拷贝,而ES6输出的值是值的引用
  • commonjs是在运行时加载,是一个对象,ES6是在编译时加载,是一个代码块
  • commonjs的this指向当前模块,ES6的this指向undefined

JS性能优化方式

  • 资源压缩与合并:减少文件体积,提高加载速度

  • 减少http请求

  • 利用浏览器缓存:提升二次访问的速度

  • 使用CDN

  • 将CSS文件放在头部,JS文件放在尾部

  • 图片懒加载

  • 减少重绘和重排

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-11-16 18:44:30  更:2021-11-16 18:46:17 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 4:09:01-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码