💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# Js常见面试题 ## 数据类型 ### JS的基本数据类型 `Undefined、Null、Boolean、Number、String`新增:`Symbol` ### JS有哪些内置对象? `Object` 是 `JavaScript` 中所有对象的父对象 数据封装类对象:`Object、Array、Boolean、Number` 和 `String` 其他对象:`Function、Arguments、Math、Date、RegExp、Error` ### JS中使用typeof能得到的哪些类型?(考点:JS变量类型) ~~~javascript typeof undefinded //undefined typeof null // object typeof'abc' // string typeof 123 // number typeof [] // object typeof {} // object typeof true //boolean typeof b // b 没有声明,但是还会显示 undefined ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ### 何时使用`===`何时使用`==`?(考点:强制类型转换) `==` 比较两个值相等返回`ture` `===` 比较两者的值和类型都相等才返回`true` 严格相等 `0,NAN,null,undefinded,'',false` 在`if`语句中会强制转化为 `false` ~~~javascript if (obj.a == null) { // 这里相当于 obj.a === null || obj.a ===undefinded, 简写形式 // 这里jquery 源码中推荐的写法 } ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ### `null,undefined` 的区别? 1、`null`: `Null`类型,代表“空值”,代表一个空对象指针,使用`typeof`运算得到 `“object”`,所以你可以认为它是一个特殊的对象值。 2、`undefined`: `Undefined`类型,当一个声明了一个变量未初始化时,得到的就是`undefined`。 ps:一句话简单来说`null`和`undefine`值比较是相等,但类型不同。 ### Javascript创建对象的几种方式? 1、对象字面量的方式 ~~~javascript var = {} ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") 2、通过构造函数方式创建。 ~~~javascript var obj = new Object(); ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") 3、通过`Object.create()`方式创建。 ~~~javascript var obj = Object.create(Object.prototype); ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ### 翻转一个字符串 可以先将字符串转成一个数组,然后用数组的`reverse()+join()`方法。 ~~~javascript let str="hello word"; let b=[...str].reverse().join("");//drow olleh ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ### 描述`new`一个对象的过程 1、创建一个新对象 2、`this`指向这个对象 3、执行代码,即对`this`赋值 4、隐式返回`this` ### JS按存储方式区分变量类型 ~~~javascript // 值类型 var a = 10 var b = a a = 11 console.log(b) // 10 // 引用类型 var obj1 = {x : 100} var obj2 = obj1 obj1.x = 200 console.log(obj2) // 200 ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ### 如何判断一个变量是对象还是数组? 1、`instanceof`方法 `instanceof`运算符是用来测试一个对象是否在其原型链原型构造函数的属性 ~~~javascript var arr = []; arr instanceof Array; // true ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") 2、`constructor`方法 `constructor`属性返回对创建此对象的数组函数的引用,就是返回对象相对应的构造函数 ~~~javascript var arr = []; arr.constructor == Array; //true ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") 3、最简单的方法,这种写法兼容性最好使用`Object.prototype.toString.call()` ~~~javascript function isObjArr(value){ if (Object.prototype.toString.call(value) === "[object Array]") { console.log('value是数组'); }else if(Object.prototype.toString.call(value)==='[object Object]'){//这个方法兼容性好一点 console.log('value是对象'); }else{ console.log('value不是数组也不是对象') } } ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") 4、`ES5`新增方法`isArray()` ~~~javascript var a = new Array(123); var b = new Date(); console.log(Array.isArray(a)); //true console.log(Array.isArray(b)); //false ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ps:千万不能使用`typeof`来判断对象和数组,因为这两种类型都会返回`"object"`。 ### 如何对一个数组去重? 1、`Set`结构去重(`ES6`用法)。 `ES6`提供了新的数据结构`Set`。它类似于数组,但是成员的值都是唯一的,没有重复的值。 ~~~javascript [...new Set(array)]; ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") 2、遍历,将值添加到新数组,用`indexOf()`判断值是否存在,已存在就不添加,达到去重效果。 ~~~javascript let a = ['1','2','3',1,NaN,NaN,undefined,undefined,null,null, 'a','b','b']; let unique= arr =>{ let newA=[]; arr.forEach(key => { if( newA.indexOf(key)<0 ){ //遍历newA是否存在key,如果存在key会大于0就跳过push的那一步 newA.push(key); } }); return newA; } console.log(unique(a)) ;//["1", "2", "3", 1, NaN, NaN, undefined, null, "a", "b"] // 这个方法不能分辨NaN,会出现两个NaN。是有问题的,下面那个方法好一点。 ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") 3、利用`for`嵌套`for`,然后`splice`去重(`ES5`中最常用)。 ~~~javascript function unique(arr){ for(var i=0; i<arr.length; i++){ for(var j=i+1; j<arr.length; j++){ if(arr[i]==arr[j]){ //第一个等同于第二个,splice方法删除第二个 arr.splice(j,1); j--; } } } return arr; } var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}]; console.log(unique(arr)) //[1, "true", 15, false, undefined, NaN, NaN, "NaN", "a", {…}, {…}] //NaN和{}没有去重,两个null直接消失了 ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") 4、`forEach`遍历,然后利用`Object.keys(对象)`返回这个对象可枚举属性组成的数组,这个数组就是去重后的数组。 ~~~javascript let a = ['1', '2', '3', 1,NaN,NaN,undefined,undefined,null,null, 'a', 'b', 'b']; const unique = arr => { var obj = {} arr.forEach(value => { obj[value] = 0;//这步新添加一个属性,并赋值,如果不赋值的话,属性会添加不上去 }) return Object.keys(obj);//`Object.keys(对象)`返回这个对象可枚举属性组成的数组,这个数组就是去重后的数组 } console.log(unique(a));//["1", "2", "3", "NaN", "undefined", "null", "a", "b"] ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ## 作用域和闭包 ### `var、let、const`之间的区别 `var`声明变量可以重复声明,而`let`不可以重复声明 `var`是不受限于块级的,而`let`是受限于块级 `var`会与`window`相映射(会挂一个属性),而`let`不与`window`相映射 `var`可以在声明的上面访问变量,而`let`有暂存死区,在声明的上面访问变量会报错 `const`声明之后必须赋值,否则会报错 `const`定义不可变的量,改变了就会报错 `const`和`let`一样不会与`window`相映射、支持块级作用域、在声明的上面访问变量会报错 ### 说明`This`几种不同的使用场景 1、作为构造函数执行 2、作为对象属性执行 3、作为普通函数执行 4、`call apply bind` ### 谈谈`This`对象的理解 1、`this`总是指向函数的直接调用者(而非间接调用者) 2、如果有`new`关键字,`this`指向`new`出来的那个对象 3、在事件中,`this`指向触发这个事件的对象,特殊的是,`IE`中的`attachEvent`中的`this`总是指向全局对象`Window` ### 作用域 `ES5`作用域分为 全局作用域 和 函数作用域。 `ES6`新增块级作用域,块作用域由 `{ }`包括,`if`语句和 `for`语句里面的`{ }`也属于块作用域。 ~~~javascript // 块级作用域 if (true) { var name = 'zhangsan' } console.log(name) // 函数和全局作用域 var a = 100 function fn () { var a = 200 console.log('fn', a) } ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ### 用`JS`创建10个`<a>`标签,点击的时候弹出来对应的序号?(考点:作用域) ~~~javascript var i for (i = 0; i < 10; i++) { (function (i) { var a = document.createElement('a') a.innerHTML = i + '<br>' a.addEventListener('click', function (e) { e.preventDefault() alert(i) }) document.body.appendChild(a) })(i) } ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ### 说说你对作用域链的理解 1、作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到`window`对象即被终止,作用域链向下访问变量是不被允许的 2、简单的说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期 3、通俗来说,一般情况下,变量取值到 创建 这个变量 的函数的作用域中取值。 但是如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链 ~~~javascript var a = 100 function fn () { var b = 200 // 当前作用域没有定于的变量,即‘自由变量’ console.log(a) console.log(b) } fn() console.log(a) 去父级作用域找a自由变量 作用域链 ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ### 闭包 闭包就是能够读取其他函数内部变量的函数 闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域 ~~~javascript function F1 () { var a = 100 // 返回一个函数,函数作为返回值 return function () { console.log(a) } } // f1 得到一个函数 var f1 = F1() var a = 200 f1() // 100 定义的时候父级作用域 ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ### 闭包的特性 函数内再嵌套函数 内部函数可以引用外层的参数和变量 参数和变量不会被垃圾回收机制回收 ### 说说你对闭包的理解 使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。在js中,函数即闭包,只有函数才会产生作用域的概念 闭包的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中 闭包的另一个用处,是封装对象的私有属性和私有方法 好处:能够实现封装和缓存等; 坏处:就是消耗内存、不正当使用会造成内存溢出的问题 ### 使用闭包的注意点 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露 解决方法是,在退出函数之前,将不使用的局部变量全部删除 ### 闭包的使用场景 闭包的两个场景:函数作为返回值 和 函数作为参数传递 ~~~javascript function F1 () { var a = 100 // 返回一个函数,函数作为返回值 return function () { console.log(a) } } // f1 得到一个函数 var f1 = F1() var a = 200 f1() // 100 定义的时候父级作用域 function F2 (fn) { var a = 200 fn() } F2(f1) ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ### 实际开发中闭包的应用 ~~~javascript // 闭包实际应用中主要用于封装变量 收敛权限 function isFirstLoad() { var _list = [] return function (id) { if (_list.indexOf(id) >= 0) { return false } else { _list.push(id) return true } } } // 使用 var firstLoad = new isFirstLoad() firstLoad(10) //true firstLoad(10) //false firstLoad(30) //true ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ## 原型和原型链 ### JavaScript原型,原型链 ? 有什么特点? 每个对象都会在其内部初始化一个属性,就是`prototype`(原型), 当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去`prototype`里找这个属性,这个`prototype`又会有自己的`prototype`,于是就这样一直找下去,也就是我们平时所说的原型链的概念 原型和原型链关系 **关系:**`instance.constructor.prototype = instance.__proto__` 原型和原型链特点 `JavaScript`对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变 当我们需要一个属性的时,`Javascript`引擎会先看当前对象中是否有这个属性, 如果没有的 就会查找他的Prototype对象是否有这个属性,如此递推下去,一直检索到 `Object` 内建对象 PS: 1.所有的引用类型(数组,对象,函数)都具有对象的特性,即可自由扩展属性(除了‘`null`’)除外 2.所有的引用类型(数组,对象,函数)都有一个`_proto_`(隐式原型)属性,属性值是一个普通对象 3.所有的函数,都有一个`prototype`(显示原型)的属性,也是一个普通对象 4.所有的引用类型(数组,对象,函数)`_proto_`属性值指向他的构造的‘`prototype`’ 属性值 ### 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的`_proto_`(即它的构造函数的`prototype`) 中寻找 ~~~javascript // 构造函数 function Foo(name, age){ this.name = name } Foo.prototype.alertName = function () { alert(this.name) } //创建实例 var f = new Foo('zhangsan') f.printName = function () { console.log(this.name) } //测试 f.printName() f.alertName() ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ### 循环对象自身的属性 ~~~javascript var item for (item in f) { // 高级浏览器已经在 for in 中屏蔽了来自原型的属性 // 但是这里建议大家还是加上这个判断,保证程序的健壮性 if (f.hasOwnProperty(item)) { console.log(item) } } ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ### 原型链 ~~~javascript f.toString() //到f._proto_._proto_ 中去找 ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ## 异步和单线程 ### 同步和异步的区别是什么?分别举一个同步和异步的例子 `同步交互`:指发送一个请求,需要等待返回,然后才能够发送下一个请求,有个等待过程。 `异步交互`:指发送一个请求,不需要等待返回,随时可以再发送下一个请求,即不需要等待。 `相同的地方`:都属于交互方式,都是发送请求。 `不同的地方`:一个需要等待,一个不需要等待。 **简单而言**,同步就是必须一件一件的做事,等前一件事做完后才能做下一件事。而异步这是把事情指派给别人后,接着继续做下一件事,不必等别人返回的结果。 **举例:** 1、广播,就是一个异步例子。发起者不关心接收者的状态。不需要等待接收者的返回信息; 在部分情况下,我们的项目开发中都会优先选择不需要等待的异步交互方式。 2、电话,就是一个同步例子。发起者需要等待接收者,接通电话后,通信才开始。需要等待接收者的返回信息 比如银行的转账系统,对数据库的保存操作等等,都会使用同步交互操作。 ps: `alert`是同步,`setTimeout`和`setInterval`是异步,同步会阻塞代码执行,而异步不会 ### 异步编程的实现方式? 1、`回调函数` 优点:简单、容易理解 缺点:不利于维护,代码耦合高 2、`事件监听(采用时间驱动模式,取决于某个事件是否发生):` 优点:容易理解,可以绑定多个事件,每个事件可以指定多个回调函数 缺点:事件驱动型,流程不够清晰 3、`发布/订阅(观察者模式)` 类似于事件监听,但是可以通过‘消息中心’,了解现在有多少发布者,多少订阅者 4、`Promise对象` 优点:可以利用`then`方法,进行链式写法;可以书写错误时的回调函数; 缺点:编写和理解,相对比较难 5、`Generator函数` 优点:函数体内外的数据交换、错误处理机制 缺点:流程管理不方便 6、`async函数` 优点:内置执行器、更好的语义、更广的适用性、返回的是`Promise`、结构清晰。 缺点:错误处理机制 ### 定时器的执行顺序或机制。 简单来说:**因为`js`是单线程的,浏览器遇到`setTimeout`或者`setInterval`会先执行完当前的代码块,在此之前会把定时器推入浏览器的待执行事件队列里面,等到浏览器执行完当前代码之后会看一下事件队列里面有没有任务,有的话才执行定时器的代码。** 所以即使把定时器的时间设置为`0`还是会先执行当前的一些代码。 ### 一个关于`setTimeout`的笔试题 ~~~javascript console.log(1) setTimeout(function () { console.log(2) },0) console.log(3) setTimeout(function () { console.log(4) },1000) console.log(5) //结果为 1 3 5 2 4 ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ### 前段使用异步的场景 1、定时任务:`setTimeout,setInverval` 2、网络请求:`ajax`请求,动态`<img>`加载 3、事件绑定 ## 数组和对象`API` ### map与forEach的区别? 1、`forEach`方法,是最基本的方法,就是遍历与循环,默认有3个传参:分别是遍历的数组内容`item`、数组索引`index`、和当前遍历数组`Array` 2、`map`方法,基本用法与`forEach`一致,但是不同的,它会返回一个新的数组,所以在`callback`需要有`return`值,如果没有,会返回`undefined` ### JS 数组和对象的遍历方式,以及几种方式的比较 通常我们会用循环的方式来遍历数组。但是循环是 导致js 性能问题的原因之一。一般我们会采用下几种方式来进行数组的遍历 `for in循环` `for循环` `forEach` 1、这里的 `forEach`回调中两个参数分别为 `value,index` 2、`forEach` 无法遍历对象 3、`IE`不支持该方法;`Firefox` 和 `chrome` 支持 4、`forEach` 无法使用 `break,continue` 跳出循环,且使用 `return` 是跳过本次循环 5、可以添加第二个参数,为一个数组,回调中的this会指向这个数组,若没有添加,则是指向`window`; 在方式一中,`for-in`需要分析出`array`的每个属性,这个操作性能开销很大。用在 `key` 已知的数组上是非常不划算的。所以尽量不要用`for-in`,除非你不清楚要处理哪些属性,例如`JSON`对象这样的情况 在方式2中,循环每进行一次,就要检查一下数组长度。读取属性(数组长度)要比读局部变量慢,尤其是当`array`里存放的都是 `DOM`元素,因为每次读取都会扫描一遍页面上的选择器相关元素,速度会大大降低 ### 写一个能遍历对象和数组的通用`forEach`函数 ~~~javascript function forEach(obj, fn) { var key // 判断类型 if (obj instanceof Array) { obj.forEach(function (item, index) { fn(index, item) }) } else { for (key in obj) { fn ( key, obj[key]) } } } ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ### 数组`API` ~~~javascript map: 遍历数组,返回回调返回值组成的新数组 forEach: 无法break,可以用try/catch中throw new Error来停止 filter: 过滤 some: 有一项返回true,则整体为true every: 有一项返回false,则整体为false join: 通过指定连接符生成字符串 push / pop: 末尾推入和弹出,改变原数组, 返回推入/弹出项【有误】 unshift / shift: 头部推入和弹出,改变原数组,返回操作项【有误】 sort(fn) / reverse: 排序与反转,改变原数组 concat: 连接数组,不影响原数组, 浅拷贝 slice(start, end): 返回截断后的新数组,不改变原数组 splice(start, number, value...): 返回删除元素组成的数组,value 为插入项,改变原数组 indexOf / lastIndexOf(value, fromIndex): 查找数组项,返回对应的下标 reduce / reduceRight(fn(prev, cur), defaultPrev): 两两执行,prev 为上次化简函数的return值,cur 为当前值(从第二项开始) ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ### 对象`API` ~~~javascript var obj = { x: 100, y: 200, z: 300 } var key for (key in obj) { // 注意这里的 hasOwnProperty,再讲原型链的时候讲过 if (obj.hasOwnProperty(key)) { console.log(key, obj[key]) } } ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ## 日期和随机数 ### 获取`2020-06-10`格式的日期 ~~~javascript Date.now() // 获取当前时间毫秒数 var dt = new Date() dt.getTime() //获取毫秒数 dt.getFullYear() // 年 dt.getMonth() // 月 (0-11) dt.getDate() // 日 (0-31) dt.getHours() // 小时 (0-23) dt.getMinutes() //分钟 (0-59) dt.getSeconds() //秒 (0-59) ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ps: `ES6` 新增`padStart(),padEnd()`方法可用来返回`06`格式日期 ### 获取随机数,要求是长度一致的字符串格式 ~~~javascript var random = Math.random() var random = random + '0000000000' console.log(random) var random = random.slice(0, 10) console.log(random) ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ## `DOM`和`BOM`操作 ### `DOM`是哪种基本的数据结构 `DOM`是一种树形结构的数据结构 ### `DOM`操作的常用`API`有哪些 1、获取`DOM`节点,以及节点的`property`和`Attribute` 2、获取父节点,获取子节点 3、新增节点,删除节点 ### `DOM`节点的`Attribute`和`property`有何区别 `property`只是一个`JS`对象的属性修改和获取 `Attribute`是对`html`标签属性的修改和获取 ### `DOM`的节点操作 **创建新节点** ~~~javascript createDocumentFragment() //创建一个DOM片段 createElement() //创建一个具体的元素 createTextNode() //创建一个文本节点 ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") **添加、移除、替换、插入** ~~~ appendChild() //添加 removeChild() //移除 replaceChild() //替换 insertBefore() //插入 ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") **查找** ~~~javascript getElementsByTagName() //通过标签名称 getElementsByName() //通过元素的Name属性的值 getElementById() //通过元素Id,唯一性 ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") **如何检测浏览器的类型** 可以通过检测`navigator.userAgent` 在通过不通浏览器的不通来检测 ~~~javascript var ua = navigator.userAgent var isChrome = ua.indexOf('Chrome') console.log(isChrome) ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") **拆解`url`的各部分** 使用`location`里面的`location.href location.protocol location.pathname location.search location.hash`来获取各种参数 ~~~javascript console.log(location.href) console.log(location.host) //域名 console.log(location.protocol) console.log(location.pathname) //路径 console.log(location.search) // 参数 console.log(location.hash) // history history.back() history.forward() ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ## 事件机制 ### 请解释什么是事件代理 事件代理(`Event Delegation`),又称之为事件委托。是 `JavaScript` 中常用绑定事件的常用技巧。顾名思义,“事件代理”即是把原本需要绑定的事件委托给父元素,让父元素担当事件监听的职务。事件代理的原理是`DOM`元素的事件冒泡。 **使用事件代理的好处是:** 1、可以提高性能 2、可以大量节省内存占用,减少事件注册,比如在`table`上代理所有`td`的`click`事件就非常棒 3、可以实现当新增子对象时无需再次对其绑定 ### 事件模型 `W3C`中定义事件的发生经历三个阶段:`捕获阶段(capturing)、目标阶段(targetin)、冒泡阶段(bubbling)` `冒泡型事件:`当你使用事件冒泡时,子级元素先触发,父级元素后触发 `捕获型事件:`当你使用事件捕获时,父级元素先触发,子级元素后触发 `DOM事件流:`<时支持两种事件模型:捕获型事件和冒泡型事件 `阻止冒泡:`在`W3c`中,使用`stopPropagation()`方法;在`IE`下设置`cancelBubble = true` `阻止捕获:`阻止事件的默认行为,例如`click - <a>后`的跳转。在`W3c`中,使用`preventDefault()`方法,在`IE`下设置`window.event.returnValue = false` ### 编写一个通用的事件监听函数 ~~~javascript function bindEvent(elem,type,selector,fn){ if(fn==null){ fn = selector; selector = null } elem.addEventListener(type,function(e){ var target; if(selector){ target = e.target; if(target.matches(selector)){ fn.call(target,e) } }else{ fn(e) } }) } ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ### 描述事件冒泡流程 当给某元素绑定一个事件的时候,首先会触发自己绑定的,然后会逐层向上级查找事件,这就是事件冒泡 ~~~javascript var p1 = document.getElementById('p1') var body = document.body function bindEvent(elem, type, fn) { elem.addEventListener(type, fn) } bindEvent(p1, 'click', function (e) { e.stopPropagation() var target = e.target alert("激活") }) bindEvent(body, 'click', function (e) { var target = e.target alert("取消") }) ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ### 对于一个无限下拉加载图片的页面,如何给每个图片绑定事件 可以使用代理,通过对父级元素绑定一个事件,通过判断事件的`target`属性来进行判断,添加行为 ~~~javascript var div1 = document.getElementById('div1') var div2 = document.getElementById('div2') function bindEvent(elem, type, fn) { elem.addEventListener(type, fn) } bindEvent(div1, 'click', function (e) { var target = e.target alert(target.innerHTML) }) bindEvent(div2, 'click', function (e) { var target = e.target alert(target.innerHTML) }) ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ## AJAX ### `Ajax`原理 `Ajax`的原理简单来说是在用户和服务器之间加了—个中间层(`AJAX`引擎),通过`XmlHttpRequest`对象来向服务器发异步请求,从服务器获得数据,然后用`javascript`来操作`DOM`而更新页面。使用户操作与服务器响应异步化。这其中最关键的一步就是从服务器获得请求数据 `Ajax`的过程只涉及`JavaScript、XMLHttpRequest和DOM`。`XMLHttpRequest`是ajax的核心机制 ### 手动编写一个`ajax`,不依赖第三方库 ~~~javascript // 1. 创建连接 var xhr = null; xhr = new XMLHttpRequest() // 2. 连接服务器 xhr.open('get', url, true) // 3. 发送请求 xhr.send(null); // 4. 接受请求 xhr.onreadystatechange = function(){ if(xhr.readyState == 4){ if(xhr.status == 200){ success(xhr.responseText); } else { // fail fail && fail(xhr.status); } } } ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") `ajax`的优点和缺点 `ajax的优点` 1、无刷新更新数据(在不刷新整个页面的情况下维持与服务器通信) 2、异步与服务器通信(使用异步的方式与服务器通信,不打断用户的操作) 3、前端和后端负载均衡(将一些后端的工作交给前端,减少服务器与宽度的负担) 4、界面和应用相分离(`ajax`将界面和应用分离也就是数据与呈现相分离) `ajax的缺点` 1、ajax不支持浏览器back按钮 2、安全问题 `Aajax`暴露了与服务器交互的细节 3、对搜索引擎的支持比较弱 4、破坏了`Back`与`History`后退按钮的正常行为等浏览器机制。 ### 什么情况下会碰到跨域问题?有哪些解决方法? 跨域问题是这是浏览器为了安全实施的同源策略导致的,同源策略限制了来自不同源的`document、脚本`,同源的意思就是两个`URL`的域名、协议、端口要完全相同。 `script`标签`jsonp`跨域、`nginx`反向代理、`node.js`中间件代理跨域、后端在头部信息设置安全域名、后端在服务器上设置`cors`。 ps:有三个标签允许跨域加载资源: `<img src=xxx> <link href = xxx> <script src=xxx>` 三个标签场景: `<img>` 用于打点统计,统计网站可能是其他域 `<link> <script>` 可以使用`CDN`,`CDN`的也是其他域 `<script>`可以用于JSONP **⚠️跨域注意事项** 所有的跨域请求都必须经过信息提供方允许 如果未经允许即可获取,那么浏览器同源策略出现漏洞 ### `XML`和`JSON`的区别? **数据体积方面** `JSON`相对于`XML`来讲,数据的体积小,传递的速度更快些。 **数据交互方面** `JSON`与`JavaScript`的交互更加方便,更容易解析处理,更好的数据交互 **数据描述方面** `JSON`对数据的描述性比`XML`较差 **传输速度方面** `JSON`的速度要远远快于`XML` ### `get`和`post`的区别 1、`get`和`post`在`HTTP`中都代表着请求数据,其中get请求相对来说更简单、快速,效率高些 2、`get`相对`post`安全性低 3、`get`有缓存,`post`没有 4、`get`体积小,`post`可以无限大 5、`get`的`url`参数可见,`post`不可见 6、`get`只接受`ASCII`字符的参数数据类型,`post`没有限制 7、`get`请求参数会保留历史记录,`post`中参数不会保留 8、`get`会被浏览器主动`catch,post`不会,需要手动设置 9、`get`在浏览器回退时无害,`post`会再次提交请求 什么时候使用`post`? **`post`一般用于修改服务器上的资源,对所发送的信息没有限制。比如** 1、无法使用缓存文件(更新服务器上的文件或数据库) 2、向服务器发送大量数据(`POST` 没有数据量限制) 3、发送包含未知字符的用户输入时,`POST` 比 `GET` 更稳定也更可靠 ### `TCP`建立连接为什么需要三次? * 为了实现可靠数据传输, `TCP` 协议的通信双方, 都必须维护一个序列号, 以标识发送出去的数据包中, 哪些是已经被对方收到的。 三次握手的过程即是通信双方相互告知序列号起始值, 并确认对方已经收到了序列号起始值的必经步骤 * 如果只是两次握手, 至多只有连接发起方的起始序列号能被确认, 另一方选择的序列号则得不到确认 ## ES6 ### `ES5`的继承和`ES6`的继承有什么区别? `ES5`的继承时通过`prototype`或构造函数机制来实现。**`ES5`的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到`this`上**(`Parent.apply(this)`)。 `ES6`的继承机制完全不同,**实质上是先创建父类的实例对象`this`(所以必须先调用父类的`super()`方法),然后再用子类的构造函数修改`this`**。 具体的:`ES6`通过`class`关键字定义类,里面有构造方法,类之间通过`extends`关键字实现继承。子类必须在`constructor`方法中调用`super`方法,否则新建实例报错。因为子类没有自己的`this`对象,而是继承了父类的`this`对象,然后对其进行加工。如果不调用`super`方法,子类得不到`this`对象。 ps:`super`关键字指代父类的实例,即父类的`this`对象。在子类构造函数中,调用`super`后,才可使用`this`关键字,否则报错 ### `javascript`如何实现继承? 1、`构造继承` 2、`原型继承` 3、`实例继承` 4、`拷贝继承` `原型prototype`机制或`apply和call`方法去实现较简单,建议使用构造函数与原型混合方式 ~~~javascript function Parent(){ this.name = 'wang'; } function Child(){ this.age = 28; } Child.prototype = new Parent();//继承了Parent,通过原型 var demo = new Child(); alert(demo.age); alert(demo.name);//得到被继承的属性 } ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ### 谈谈你对`ES6`的理解 新增模板字符串(为`JavaScript`提供了简单的字符串插值功能) 箭头函数 `for-of`(用来遍历数据—例如数组中的值。) `arguments`对象可被不定参数和默认参数完美代替。 `ES6`将`promise`对象纳入规范,提供了原生的`Promise`对象。 增加了`let和const`命令,用来声明变量。 增加了块级作用域。 `let`命令实际上就增加了块级作用域。 还有就是引入`module`模块的概念 ### 谈一谈箭头函数与普通函数的区别? * 函数体内的`this`对象,就是定义时所在的对象,而不是使用时所在的对象 * 不可以当作构造函数,也就是说,不可以使用`new`命令,否则会抛出一个错误 * 不可以使用`arguments`对象,该对象在函数体内不存在。如果要用,可以用`Rest`参数代替 * 不可以使用`yield`命令,因此箭头函数不能用作`Generator`函数 ### `forEach、for in、for of`三者区别 `forEach`更多的用来遍历数 `for in`一般常用来遍历对象或`json` `for of`数组对象都可以遍历,遍历对象需要通过和`Object.keys()` `for in`循环出的是`key,for of`循环出的是`value` ### `Set、Map`的区别 应用场景`Set`用于数据重组,Map用于数据储存 **`Set:`** 1,成员不能重复 2,只有键值没有键名,类似数组 3,可以遍历,方法有`add, delete,has` **`Map:`** 1,本质上是健值对的集合,类似集合 2,可以遍历,可以跟各种数据格式转换 ### `promise`对象的用法,手写一个`promise` `promise`是一个构造函数,下面是一个简单实例 ~~~javascript var promise = new Promise((resolve,reject) => { if (操作成功) { resolve(value) } else { reject(error) } }) promise.then(function (value) { // success },function (value) { // failure }) ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ### 请描述一下`Promise`的使用场景,'`Promise`'它所解决的问题以及现在对于异步操作的解决方案。 `Promise`的使用场景:`ajax`请求,回调函数,复杂操作判断。 `Promise`是`ES6`为了解决异步编程所诞生的。 异步操作解决方案:`Promise、Generator`、定时器(不知道算不算)、还有`ES7`的`async` ### `ECMAScript6` 怎么写`class`,为什么会出现`class`这种东西? 这个语法糖可以让有`OOP`基础的人更快上手`js`,至少是一个官方的实现了 但对熟悉`js`的人来说,这个东西没啥大影响;一个`Object.creat()`搞定继承,比`class`简洁清晰的多 ## 算法和其他 ### 冒泡排序 每次比较相邻的两个数,如果后一个比前一个小,换位置 ~~~javascript var arr = [3, 1, 4, 6, 5, 7, 2]; function bubbleSort(arr) { for (var i = 0; i < arr.length - 1; i++) { for(var j = 0; j < arr.length - i - 1; j++) { if(arr[j + 1] < arr[j]) { var temp; temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } return arr; } console.log(bubbleSort(arr)); ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ### 快速排序 采用二分法,取出中间数,数组每次和中间数比较,小的放到左边,大的放到右边 ~~~javascript var arr = [3, 1, 4, 6, 5, 7, 2]; function quickSort(arr) { if(arr.length == 0) { return []; // 返回空数组 } var cIndex = Math.floor(arr.length / 2); var c = arr.splice(cIndex, 1); var l = []; var r = []; for (var i = 0; i < arr.length; i++) { if(arr[i] < c) { l.push(arr[i]); } else { r.push(arr[i]); } } return quickSort(l).concat(c, quickSort(r)); } console.log(quickSort(arr)); ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ### 懒加载 ~~~javascript <img id="img1" src="preview.png" data-realsrc = "abc.png"/> <script type = "text/javascript"> var img1 = document.getElementById("img1") img1.src = img1.getAttribute('data-realsrc') </script> ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ### 缓存`DOM`查询 ~~~javascript // 未缓存 DOM查询 var i for (i = 0; i < document.getElementByTagName('p').length; i++) { //todo } //缓存了DOM查询 var pList = document.getElementByTagName('p') var i for (i= 0; i < pList.length; i++) { // todo } ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ### 合并`DOM`插入 ~~~javascript var listNode = document.getElementById('list') //要插入10个li标签 var frag = document.createDocumentFragment(); var x,li for (x = 0; x < 10; x++) { li = document.createElement('li') li.innerHTML = "List item" + x frag.appendChild(li) } listNode.appendChild(frag) ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ### 事件节流 ~~~javascript var textarea = document.getElementById('text') var timeoutId textarea.addEventListener('keyup', function () { if (timeoutId) { clearTimeout(timeoutId) } timeoutId = setTimeout(function () { //触发事件 },100) }) ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ### 尽早操作 ~~~javascript window.addEventListener('load', function () { //页面的全部资源加载完才会去执行,包括图片,视频 }) document.addEventListener('DOMContentLoaded', function() { //DOM 渲染完即可执行,此时图片,视频还可能没有加载完成 }) ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ### 浅拷贝 首先可以通过 `Object.assign` 来解决这个问题 ~~~ let a = { age: 1 } let b = Object.assign({}, a) a.age = 2 console.log(b.age) // 1 ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ### 深拷贝 这个问题通常可以通过`JSON.parse(JSON.stringify(object))` 来解决 ~~~javascript let a = { age: 1, jobs: { first: 'FE' } } let b = JSON.parse(JSON.stringify(a)) a.jobs.first = 'native' console.log(b.jobs.first) // FE ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ### 数组降维 ~~~javascript [1, [2], 3].flatMap(v => v) // -> [1, 2, 3] ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") 如果想将一个多维数组彻底的降维,可以这样实现 ~~~javascript const flattenDeep = (arr) => Array.isArray(arr) ? arr.reduce( (a, b) => [...a, ...flattenDeep(b)] , []) : [arr] flattenDeep([1, [[2], [3, [4]], 5]]) ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ### 预加载 在开发中,可能会遇到这样的情况。有些资源不需要马上用到,但是希望尽早获取,这时候就可以使用预加载 预加载其实是声明式的 `fetch`,强制浏览器请求资源,并且不会阻塞 `onload` 事件,可以使用以下代码开启预加载 ~~~ <link rel="preload" href="http://example.com"> 复制代码 ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") 预加载可以一定程度上降低首屏的加载时间,因为可以将一些不影响首屏但重要的文件延后加载,唯一缺点就是兼容性不好 ### 预渲染 可以通过预渲染将下载的文件预先在后台渲染,可以使用以下代码开启预渲染 ~~~ <link rel="prerender" href="http://poetries.com"> ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ## 性能优化 ### JavaScript性能优化 ~~~ 1、尽可能把 <script> 标签放在 body 之后,避免 JS 的执行卡住 DOM 的渲染,最大程度保证页面尽快地展示出来 2、尽可能合并 JS 代码:提取公共方法,进行面向对象设计等…… 3、CSS 能做的事情,尽量不用 JS 来做,毕竟 JS 的解析执行比较粗暴,而 CSS 效率更高。 4、尽可能逐条操作 DOM,并预定好 CSs 样式,从而减少 reflow 或者 repaint 的次数。 5、尽可能少地创建 DOM,而是在 HTML 和 CSS 中使用 display: none 来隐藏,按需显示。 6、压缩文件大小,减少资源下载负担。 ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动") ### JavaScript几条基本规范 ~~~ 1、不要在同一行声明多个变量 2、请使用===/!==来比较true/false或者数值 3、使用对象字面量替代new Array这种形式 4、不要使用全局变量 5、Switch语句必须带有default分支 6、函数不应该有时候有返回值,有时候没有返回值 7、For循环必须使用大括号 8、IF语句必须使用大括号 9、for-in循环中的变量 应该使用var关键字明确限定作用域,从而避免作用域污染 ~~~ ![](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw== "点击并拖拽以移动")