前端面经-js篇

文章目录

  • 1、闭包,内存泄露
  • 2、作用域链,变量提升
  • 3、let,var,const
  • 4、常用数组(reduce),字符串方法
  • 5、map和foreach区别
  • 6、js事件循环机制,宏任务微任务,async/await,读代码运行顺序
  • 7、原型与原型链
  • 8、对象继承方法
  • 9、new做了什么操作
  • 10、bind,call,apply的区别,手撕源码
  • 11、Ajax原理,手撕源码(xmlhttprequest)
  • 12、this指向
  • 13、设计模式,应用场景,手撕源码
  • 14、promise,promise.all,promise.race源码手撕(让你写一个请求,5秒内执行完就返回执行结果,否则返回超时)
  • 15、JS垃圾回收机制
  • 16、基本数据类型
  • 17、== 和 === 的区别
  • 18、隐式转换(坑1)var a=?;console.log(a1&&a2&&a3);
  • 19、如何判断数组
  • 20、js动画和css动画区别
  • 21、dom0级时间dom2级事件
    • 区别
    • DOM的级别Level
    • onclick和addEventListener的区别
    • addEventListener有几个参数?响应的含义?
    • addEventListener有兼容性的问题吗?那IE响应的是什么方法 ?
  • 22、模块化
  • 23、防抖节流
  • 24、深浅拷贝
  • 25、事件流
  • 26、浏览器事件冒泡、事件捕获事件冒泡:
  • 27、typeof和instance of(判断数据类型的5种方法)
  • 28、变量提升
  • 29、ES6和common.js
  • 30、怎么判断两个对象是否相等
  • 31、利用Promise知识,用原生JS封装AJAX
  • 32、用setTimeout实现setInterval
  • 33、拦截器(Axios)
  • 34、箭头函数与普通函数的区别
  • 35、JQuery相关面试题
  • 36、一般的函数调用和链式调用的区别
  • 37、Ajax和Fetch的异同
  • 38、undefined和null的区别:
  • 39、js中0.1+0.2不等于0.3问题,以及解决方法
  • 40、JS中的for of和for in的区别?
  • 41、为什么JS是单线程
  • 42、使用过的DOM操作有什么
  • 43、前端如何捕获错误
  • 44、js实现一个轮播图
  • 45、JavaScript中显式原型和隐式原型的联系
  • 46、实现准确搜索的方法
  • 47、类型转换问题
  • 48、export和export default有什么区别?在导入上有什么区别
  • 49、generator和promise的区别
  • 50、事件委托
    • 如果有1000个按钮,给它们添加点击事件 你会怎么做?
    • 那你用事件委托在点击时是怎么获取到点击对象的呢?event.target 有兼容性问题吗? 那对应的是什么?

1、闭包,内存泄露

特性

  • 函数嵌套函数
  • 函数内部可以引用函数外部的参数和变量
  • 参数和变量不会被垃圾回收机制回收

优点

  • 保护函数内的变量安全,实现封装,防止变量流入其他环境发生命名冲突
  • 在内存中维持一个变量,可以做缓存(但使用多了同时也是一种缺点,消耗内存
  • 匿名执行函数可以减少内存消耗

缺点

  • 其中一点上面已经有体现了 ,就是被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄露,解决方法是可以在使用完变量后 手动为他赋值为null;
  • 其次由于闭包涉及跨域访问,所以会导致性能有所流失,我们可以把跨域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的

2、作用域链,变量提升

在js中,作用域分为全局作用域和函数作用域,ES6新增块级作用域

作用域链

一般情况下,变量取值到创建这个变量的函数的作用域中取值。

但是如果在当前作用域中取不到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。

作用域链这么形成

作用域和执行上下文:许多人误认为作用域和执行上下文的概念,误认为他们是相同的概念,事实并非如此。

我们知道js属于解释性语言,js的执行分为:解释和执行两个阶段 。这两个阶段所做的事情并不一样。

解释阶段

  • 词法分析
  • 语法分析
  • 作用域规则确定

执行阶段

  • 创建执行上下文
  • 执行函数代码
  • 垃圾回收

JavaScript 解释阶段便会确定作用域规则,因此作用域在函数定义时就已经确定了,而不是在函数调用时确定,但是执行上下文是函数执行之前创建的。执行上下文最明显的就是 this 的指向是执行时确定的。而作用域访问的变量是编写代码的结构确定的。

作用域和执行上下文之间最大的区别是: 执行上下文在运行时确定,随时可能改变;作用域在定义时就确定,并且不会改变。

一个作用域下可能包含若干个上下文环境。有可能从来没有过上下文环境(函数从来就没有被调用过);有可能有过,现在函数被调用完毕后,上下文环境被销毁了;有可能同时存在一个或多个(闭包)。同一个作用域下,不同的调用会产生不同的执行上下文环境,继而产生不同的变量的值。

EC:函数的执行环境AO:(Actived Object)活动对象: 保存函数调用时的局部变量 其实AO对象就是函数作用域对象

变量提升

var 声明的变量会提升到当前作用域的最顶端
function 声明的函数也会提升到当前作用域的最顶端

3、let,var,const

  • let没有变量提升
  • let变量不能重复声明
  • ES6可以用let定义块级作用域变量

不同

  • var定义的变量,没有块的概念,可以跨块访问,不能跨函数访问。
  • let定义的变量,有块的概念,不能跨块访问,也不能跨函数访问。
  • const用来定义常量,有块的概念,不能跨块访问,而且不能修改。

什么是暂时性死区
如果区块(花括号)中存在let命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域,凡是在声明之前就使用这些变量,就会报错,所以在代码块内,使用let命令声明变量之前,该变量都是不可用的
这被称为暂时性死区

4、常用数组(reduce),字符串方法

数组

会改变原有数组

会改变原有数组,不会返回新的数组或值:
pop()—删除数组的最后一个元素并返回删除的元素。

push()—向数组的末尾添加一个或更多元素,并返回新的长度。

shift()—删除并返回数组的第一个元素。

unshift()—向数组的开头添加一个或更多元素,并返回新的长度。

reverse()—反转数组的元素顺序。

sort()—对数组的元素进行排序。

splice()—用于插入、删除或替换数组的元素。

不会改变数组,并会返回新的数组

concat()—连接两个或更多的数组,并返回结果。

every()—检测数组元素的每个元素是否都符合条件。

some()—检测数组元素中是否有元素符合指定条件。

filter()—检测数组元素,并返回符合条件所有元素的数组。

indexOf()—搜索数组中的元素,并返回它所在的位置。

join()—把数组的所有元素放入一个字符串。

toString()—把数组转换为字符串,并返回结果。
lastIndexOf()—返回一个指定的字符串值最后出现的位置,在一个字符串中的指定位置从后向前搜索。

map()—通过指定函数处理数组的每个元素,并返回处理后的数组。

slice()—选取数组的的一部分,并返回一个新数组。

valueOf()—返回数组对象的原始值。

其他

reduce 对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。

reducer 函数接收4个参数:
Accumulator (acc) (累计器)
Current Value (cur) (当前值)
Current Index (idx) (当前索引)
Source Array (src) (源数组)
您的 reducer 函数的返回值分配给累计器,该返回值在数组的每个迭代中被记住,并最后成为最终的单个结果值。

字符串

split、slice、substr、indexOf、charAt、trim、toLocalUpperCase、toLocaleLowerCase

5、map和foreach区别

foreach没有返回值,map会返回数组

6、js事件循环机制,宏任务微任务,async/await,读代码运行顺序

js事件循环机制推文

js异步任务安装准确的划分,应该将任务分为:

宏任务:setTimeout、setInterval( 按照指定的周期(以毫秒计)来调用函数或计算表达式。)
微任务:列如promise.then方法。注意new Promise()的时候是同步,立即执行。

所以针对这种机制,js的事件循环机制应该是这样的

注意:现在有三个队列:同步队列(也称执行栈)、宏任务队列、微任务队列

  • 遇到同步代码,依次推入同步队列并执行
  • 当遇到setTimeout、setInterval,会被推到宏任务队列
  • 如果遇到.then,会被当做微任务,被推入微任务队列
  • 同步队列执行完毕,然后去微任务队列取任务,知道微任务队列清空。然后去检查宏任务队列,去宏队列取任务,并且没一个宏任务执行王弼都会去微任务队列跑一遍,看看有没有新的微任务,有的话先把微任务清空,这样依次循环。
setTimeout(function(){
	console.log(1)
},0)

new Promise(function(resolve,reject){
	console.log(2)
	resolve()
}).then((res)=>{
	console.log(3)
})
console.log(4)
//执行结果是 2 4 3 1

async

当我们在函数前使用async的时候,使得该函数返回的是一个Promise对象

async function test(){
	return 1 //async的函数会帮我们隐式的使用Promise。resolve(1)
}
//等价于下面的代码
function test(){
	return new Promise(function(resolve,reject){
		resolve(1)
	})
}

可见async只是帮助我们返回一个Promise而已

await

await表示等待,是右侧【表达式】的结果,这个表达式的结果结算可以是Promise对象的值或者一个函数的值。并且只能在带有async的内部使用
使用await时,会从右往左执行,当遇到await时候,会阻塞函数内部处于他后面的代码。去执行该函数外部的同步代码,当外部同步代码执行完毕,再回到该函数内部执行剩余代码,并且当await执行完毕之后,会先处理微任务队列的代码。

	async function async1() {
            console.log( 'async1 start' )
            await async2()
            console.log( 'async1 end' )
        }
        async function async2() {
            console.log( 'async2' )
        }
        console.log( 'script start' )
        setTimeout( function () {
            console.log( 'setTimeout' )
        }, 0 )
        async1();
        new Promise( function ( resolve ) {
            console.log( 'promise1' )
            resolve();
        } ).then( function () {
            console.log( 'promise2' )
        } )
        console.log( 'script end' )

前端面经-js篇_第1张图片

7、原型与原型链

原型:每一个对象都与另一个对象相关联,那个关联的对象就称为原型
原型链:每一个对象,都有一个原型对象与之关联,这个原型对象它也是一个普通对象,这个普通对象也有自己的原型对象,这样层层递进,就形成了一个链条,这个链条就是原型链。通过原型链可以实现JS的继承,把父类的原型对象赋值给子类的原型,这样子类实例就可以访问父类原型上的方法了

8、对象继承方法

36个手写问题

  • 原型链继承
function Animal() {
    this.colors = ['black', 'white']
}
Animal.prototype.getColor = function() {
    return this.colors
}
function Dog() {}
Dog.prototype =  new Animal()

let dog1 = new Dog()
dog1.colors.push('brown')
let dog2 = new Dog()
console.log(dog2.colors)  // ['black', 'white', 'brown']

原型链继承存在的问题:

问题1:原型中包含的引用类型属性将被所有实例共享;
问题2:子类在实例化的时候不能给父类构造函数传参;

  • 构造继承
function Animal(name) {
    this.name = name
    this.getName = function() {
        return this.name
    }
}
function Dog(name) {
    Animal.call(this, name)
}
Dog.prototype =  new Animal()

借用构造函数实现继承解决了原型链继承的 2 个问题:引用类型共享问题以及传参问题。但是由于方法必须定义在构造函数中,所以会导致每次创建子类实例都会创建一遍方法。

  • class实现继承
class Animal {
    constructor(name) {
        this.name = name
    } 
    getName() {
        return this.name
    }
}
class Dog extends Animal {
    constructor(name, age) {
        super(name)
        this.age = age
    }
}
  • 组合继承

组合继承结合了原型链和盗用构造函数,将两者的优点集中了起来。基本的思路是使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。

function Animal(name) {
    this.name = name
    this.colors = ['black', 'white']
}
Animal.prototype.getName = function() {
    return this.name
}
function Dog(name, age) {
    Animal.call(this, name)
    this.age = age
}
Dog.prototype =  new Animal()
Dog.prototype.constructor = Dog

let dog1 = new Dog('奶昔', 2)
dog1.colors.push('brown')
let dog2 = new Dog('哈赤', 1)
console.log(dog2) 
// { name: "哈赤", colors: ["black", "white"], age: 1 }

缺点:两次调用父类构造函数,数据冗余

寄生式组合继承

function Animal(name) {
    this.name = name
    this.colors = ['black', 'white']
}
Animal.prototype.getName = function() {
    return this.name
}
function Dog(name, age) {
    Animal.call(this, name)
    this.age = age
}
Dog.prototype =  Object.create(Animal.prototype)
Dog.prototype.constructor = Dog

let dog1 = new Dog('奶昔', 2)
dog1.colors.push('brown')
let dog2 = new Dog('哈赤', 1)
console.log(dog2) 
// { name: "哈赤", colors: ["black", "white"], age: 1 }

9、new做了什么操作

  • 创建了一个空的js对象 即{}
  • 将空对象的原型prototype指向构造函数的原型
  • 将空对象作为构造函数的上下文(改变this指向)
  • 对构造函数有返回值的判断
function create(Con,...args){
        //1.创建一个空的对象
        let obj = {}; //let obj = Object.create({});
        //2.将空对象的原型prototype指向构造函数的原型
        Object.setPrototypeOf(obj,Con.prototype); //obj._proto_ = Con.prototype
        //3.改变构造函数的上下文(this),并将剩余的参数传入
        let result = Con.apply(obj,args);
        //4.在构造函数有返回值的情况进行判断
        return result instanceof Object?result:obj;
    }

10、bind,call,apply的区别,手撕源码

  • bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
  • call()方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
  • apply() 方法调用一个具有给定this值的函数,以及以一个数组(或类数组对象)的形式提供的参数;

11、Ajax原理,手撕源码(xmlhttprequest)

XMLHttpRequest—必知必会

readyState 属性表示Ajax请求的当前状态。它的值用数字代表。
0 代表未初始化。 还没有调用 open 方法
1 代表正在加载。 open 方法已被调用,但 send 方法还没有被调用
2 代表已加载完毕。send 已被调用。请求已经开始
3 代表交互中。服务器正在发送响应
4 代表完成。响应发送完毕

步骤
创建XMLHttpRequest对象,即创建一个异步调用对象.
创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息.
设置响应HTTP请求状态变化的函数.
发送HTTP请求.
获取异步调用返回的数据.
使用JavaScript和DOM实现局部刷新.

12、this指向

this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象。

  • 在全局环境下,this指向window
  • 在函数里,this始终指向该函数的调用者
  • 当通过new关键字生成构造函数的实例时候,构造函数里的this指向这个新生的实例
  • 箭头函数里没有属于它自己this,this等于包裹箭头函数的外层环境的this,故这个外层环境被什么调用,箭头函数里的this就指向什么
  • this的指向可以通过call,apply,bind改变

13、设计模式,应用场景,手撕源码

14、promise,promise.all,promise.race源码手撕(让你写一个请求,5秒内执行完就返回执行结果,否则返回超时)

同步的概念
同步,我的理解是一种线性执行的方式,执行的流程不能跨越。一般用于流程性比较强的程序,我们做的用户登录功能也是同步处理的,必须用户通过用户名和密码验证后才能进入系统的操作。

异步的概念
异步,是一种并行处理的方式,不必等待一个程序执行完,可以执行其它的任务。在程序中异步处理的结果通常使用回调函数来处理结果。在JavaScript中实现异步的方式主要有Ajax和H5新增的Web Worker。

promiseA+规范

promise代表了一个异步操作的最终结果,主要是通过then方法来注册成功以及失败的情况

1.术语

“promise”是一个对象或函数,其then方法的行为符合此规范。
“thenable”是定义then方法的对象或函数。
“value”是任何合法的JavaScript值(包括undefined,一个卑鄙的或一个承诺)。
“exception”是使用该throw语句抛出的值。
“原因”是一个值,表明承诺被拒绝的原因。

2.要求
Promise 对象存在以下三种状态:

Pending(进行中)

Fulfilled(已成功)

Rejected(已失败)

状态只能由 Pending 变为 Fulfilled 或由 Pending 变为 Rejected ,且状态改变之后不会在发生变化,会一直保持这个状态。

3.如何实现一个promise
如何实现一个promise

  • promise 有三个状态:pending,fulfilled,or rejected
  • new promise时, 需要传递一个executor()执行器,执行器立即执行;
  • executor接受两个参数,分别是resolve和reject;
  • promise 的默认状态是 pending;
  • promise 有一个value保存成功状态的值,可以是undefined/thenable/promise;
  • promise 有一个reason保存失败状态的值;
  • promise 只能从pending到rejected, 或者从pending到fulfilled,状态一旦确认,就不会再改变;
  • promise 必须有一个then方法,then 接收两个参数,分别是 promise 成功的回调 onFulfilled, 和 promise 失败的回调 onRejected;
  • 如果调用 then 时,promise 已经成功,则执行onFulfilled,参数是promise的value;
  • 如果调用 then 时,promise 已经失败,那么执行onRejected, 参数是promise的reason;
  • 如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个 then 的失败的回调onRejected;
    以上只实现了同步操作,如何实现异步操作?

因为 promise 调用 then 方法时,当前的 promise 并没有成功,一直处于 pending 状态。所以如果当调用 then 方法时,当前状态是 pending,我们需要先将成功和失败的回调分别存放起来,在executor()的异步任务被执行时,触发 resolve 或 reject,依次调用成功或失败的回调。

手撕promise.all

 Promise.myAll = function(promises) {
            return new Promise((resolve, reject) => {
                if(!(promises instanceof Array)) {
                    reject('TypeError: paras is not a array')
                }
                let count = 0
                let arr = []
                for(let i=0; i < promises.length; i++) {
                    promises[i].then(res => {
                        count++
                        arr[i] = res
                        if(count === promises.length) {
                            resolve(arr)
                        }
                    }, err => {
                        reject(err)
                    })
                }
            })
        }

手撕promise.race

Promise.myRace = function(promises) {
            return new Promise((resolve, reject) => {
                if(!(promises instanceof Array)) {
                    reject('TypeError: arguments is not a Array')
                }
                for(let i=0; i < promises.length; i++) {
                    promises[i].then(res => {
                        resolve(res)
                    }, err => {
                        reject(err)
                    })
                }
            })
        }

手撕promise.myResolve

  Promise.myResolve = function(value) {
            return new Promise((resolve, reject) => {
                if(value instanceof Promise) {
                    value.then(res => {
                        resolve(res)
                    }, err => {
                        reject(err)
                    })
                } else {
                    resolve(value)
                }
            })
        }

手撕promise.myReject

Promise.reject = function(value) {
            return new Promise((resolve, reject) => {
                reject(value)
            })
        }

Promise的缺点
1、无法取消Promise,一旦新建它就会立即执行,无法中途取消。

2、如果不设置回调函数,promise内部抛出的错误,不会反应到外部。

3、当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

new Promise((resolve,reject) => {
  reject()
  resolve()       // 这个 promise 最终状态? 失败态/rejected
  console.log(3)    // 这个会输出吗? 会
})
.catch(() => {})
.then(() => {})    // 这个会执行吗? 会

Promise值穿透

Promise.resolve('foo')
    .then(Promise.resolve('bar'))
    .then(function(result){
      console.log(result)
    })

当然,输出的结果为foo。问其原因,答案如题——Promise值穿透

Promise.resolve(1)
  .then(function(){return 2})
  .then(Promise.resolve(3))
  .then(console.log)

输出结果: 2

解释:.then 或者 .catch 的参数期望是函数,传入非函数则会发生值穿透。

15、JS垃圾回收机制

回收规则

  • 全局变量不会被回收
  • 局部变量会被回收,也就是函数一旦运行完成之后,函数内部的东西都会被销毁
  • 只要被另外一个作用域所引起就不会被回收

16、基本数据类型

值类型(基本类型):

字符串(String)、数字(Number)、布尔(Boolean)、对空(Null)、未定义(Undefined)、Symbol(每个从Symbol()返回的symbol值都是唯一的。一个symbol值能作为对象属性的标识符)。

引用数据类型:

对象(Object)、数组(Array)、函数(Function)。

类型装换
任何对象转为布尔值,都会都到true。在JS中,只有0,-0,NaN,“”,null,undefined这留个值转为布尔值时候,结果为false。

区别
普通数据类型:内存中存储的是数据本身,作为形参引入到函数中,则给函数分配的内存空间将会有一个形参空间存储该数据(可以理解为其替身)。函数执行过程中对该数据进行操作使之发生的改变只发生在“替身”上面,故函数执行完,给函数分配的空间被销毁,“替身”也被销毁,源数据不会发生改变。
引用数据类型:以数组为例,内存中存储的是数组的地址,实际内容存储在 堆段 中。【堆段:堆段中存储的特点是想要多少内存就可以分配多少存储空间。】 故在函数中以参数引入数组,给函数分配的内存空间中,形参空间存储的也是数组的地址。在函数执行过程中,对数组进行操作使之发生改变,是真实地发生在原数组身上的,故执行完函数,函数内存空间释放后,数组仍有改变。

17、== 和 === 的区别

== 没有类型比较,===会比较类型

18、隐式转换(坑1)var a=?;console.log(a1&&a2&&a3);

19、如何判断数组

instanceof操作符判断

用arr instancecof Array

instanceof主要用于某个实例是否属于某个对象

let arr = [];
console.log(arr instanceof Array);

Object.prototype.toString

用法:Object.prototype.toString.call(arr) === ‘[Object Array]’

虽然Array也继承自Object,但是Array.prototype上重写了toString,而我们通过toString.call(arr)实际上是通过原型链调用了

let arr = []
console.log(Object.prototype.toString.call(arr) === '[object Array]');

Array.isArray

用法:Array.isArray(arr)ES5中新增了Array.isArray方法,IE8以下不支持

Array.isArray(arg)

isArray函数需要一个参数arg,如果参数是个对象并且class内部属性是“Array”,返回布尔值true,否则它返回false

let arr = [];
console.log(Array.isArray(arr));

20、js动画和css动画区别

21、dom0级时间dom2级事件

区别

dom0的事件只会触发一次,后面定义的会把前面定义的覆盖掉
dom2级的事件会依次执行,IE6-IE8的执行顺序不一定

DOM的级别Level

DOM0:不是W3C规范。
DOM1:开始是W3C规范。专注于html文档和xml文档
DOM2:对DOM1增加了样式表对象模型
DOM3:对DOM2增加了内容模型(DTD\SCHEMAS)和文档验证

onclick和addEventListener的区别

on事件会被后面的on的事件覆盖,addEventListener则不会覆盖。
1.onclick事件在同一时间只能指向唯一对象
2.addEventListener对任何DOM都是有效的,而onclick仅限于HTML
3.addEventListener给一个事件注册多个listener
4.addEventListener可以控制listner的触发阶段,(捕获、冒泡)。对于多个相同的事件处理器,不会重复触发,不需要手动使用removeEventListener清除

addEventListener有几个参数?响应的含义?

三个 :事件名 函数 捕获还是冒泡阶段

addEventListener有兼容性的问题吗?那IE响应的是什么方法 ?

attachEvent

22、模块化

23、防抖节流

防抖概述

在事件被触发n秒后再执行回调函数,如果在这n秒内又被触发,则重新计时。

基于上述场景,首先提出第一种思路:在第一次触发事件时,不立即执行函数,而是给出一个期限值比如200ms,然后:

如果在200ms内没有再次触发滚动事件,那么就执行函数 如果在200ms内再次触发滚动事件,那么当前的计时取消,重新开始计时 效果:如果短时间内大量触发同一事件,只会执行一次函数。

应用场景

(1) 用户在输入框中连续输入一串字符后,只会在输入完后去执行最后一次的查询ajax请求,这样可以有效减少请求次数,节约请求资源;

(2) window的resize、scroll事件,不断地调整浏览器的窗口大小、或者滚动时会触发对应事件,防抖让其只触发一次;

简单版:函数内部支持使用 this 和 event 对象;

function debounce(func, wait) {
    var timeout;
    return function () {
        var context = this;
        var args = arguments;
        clearTimeout(timeout)
        timeout = setTimeout(function(){
            func.apply(context, args)
        }, wait);
    }
}

使用:

var node = document.getElementById('layout')
function getUserAction(e) {
    console.log(this, e)  // 分别打印:node 这个节点 和 MouseEvent
    node.innerHTML = count++;
};
node.onmousemove = debounce(getUserAction, 1000)

节流

概述

规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。

应用场景

(1)鼠标连续不断地触发某事件(如点击),只在单位时间内只触发一次;

(2)在页面的无限加载场景下,需要用户在滚动页面时,每隔一段时间发一次 ajax 请求,而不是在用户停下滚动页面操作时才去请求数据;

(3)监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断;

简单版:使用时间戳来实现,立即执行一次,然后每 N 秒执行一次。

function throttle(func, wait) {
    var context, args;
    var previous = 0;

    return function() {
        var now = +new Date();
        context = this;
        args = arguments;
        if (now - previous > wait) {
            func.apply(context, args);
            previous = now;
        }
    }
}

24、深浅拷贝

概述
js大致分成两种数据类型:基本数据类型和引用数据类型。

基本数据类型保存在栈内存,而引用类型则保存在堆内存中。对于基本数据类型的拷贝,并没有深浅拷贝的区别,我们所说的深浅拷贝都是对于引用数据类型而言的。

浅拷贝:浅拷贝是复制引用,复制后的引用都是指向同一个对象的实例,彼此之间的操作会互相影响。

深拷贝:深拷贝不是简单的复制引用,而是在堆中重新分配内存,并且把源对象实例的所有属性都进行新建复制,以保证深拷贝的对象的引用图不包含任何原有对象或对象图上的任何对象,复制后的对象与原来的对象是完全隔离的。

25、事件流

事件流描述的就是从页面中接收事件的顺序。

DOM2级事件规定的事件流包括三个阶段: (1)事件捕获阶段 (2)处于目标阶段 (3)事件冒泡阶段

前端面经-js篇_第2张图片

26、浏览器事件冒泡、事件捕获事件冒泡:

事件冒泡:

从事件源朝父级一直到根元素(HTML)。当某个元素的某类型事件被触发时,那么它的父元素同类型的事件也会被触发,一直触发到根源上;
从具体的元素到不确定的元素。

事件捕获:

从根元素(HTML)到事件源,当某个元素的某类型事件被触发时,先触发根元素的同类型事件,朝子一级触发,一直触发到事件源。
从不确定的元素到具体的元素;

在一些标准的浏览器中如Chrome、Firefox是支持这两种冒泡的
在IE以及老版本opra不支持
IE、Opera的事件绑定函数是attachEvent,而Chrome等浏览器则是addEventListener

阻止冒泡

div.addEventListener("click',function(e){
	e.stopPropagation();
},true)
// 这样div的父元素就接收不到事件了

stopPropagation()方法既可以阻止事件冒泡,也可以阻止事件捕获,也可以阻止处于目标阶段。
阻止默认事件:e.preventDefault();

27、typeof和instance of(判断数据类型的5种方法)

typeof

typeof 是判断参数是什么类型的实例,返回值为说明运算数类型的字符串。

返回值结果:“number”、“string”、“boolean”、“object”、“function”、“undefined”

若参数为引用类型,始终返回“object”,对于Array、null始终返回“object”,所以用typeof来判断参数类型有很大的局限性。

instaceof
instanceof是用来判断一个对象在其原型链中是否存在一个构造函数的prototype属性,返回一个布尔值

a instanceof b:判断a是否为b的实例,可以用于继承关系中

b是c的父对象,a是c的实例,a instanceof b 与 a instanceof c 结果均为true

对于所有的引用类型,均为Object的实例

undefined和null比较特殊,虽然null的类型是object,但是null不具有任何对象的特性

通过Object下的toString.call()方法来判断

let arr = []
console.log(Object.prototype.toString.call(arr) === '[object Array]');

根据对象的contructor判断

console.log('数据类型判断' -  constructor);
console.log(arr.constructor === Array); //true
console.log(date.constructor === Date); //true
console.log(fn.constructor === Function); //true

jq中判断数据类型的方法

jQuery.isArray();是否为数组
jQuery.isEmptyObject();是否为空对象 (不含可枚举属性)。
jQuery.isFunction():是否为函数
jQuery.isNumberic():是否为数字
jQuery.isPlainObject():是否为使用“{}”或“new Object”生成对象,而不是浏览器原生提供的对象。
jQuery.isWindow(): 是否为window对象;
jQuery.isXMLDoc(): 判断一个DOM节点是否处于XML文档中。

28、变量提升

js引擎的工作方式是,先预解析代码, 获取所有被声明的变量和函数声明,然后再一行一行地运行,这就使所有变量声明的语句,都会被提升到代码的头部,这就是变量提升

29、ES6和common.js

ESM (es6 模块)
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。
因为ESM是静态化的,所以在运行前的编译阶段就可以确定模块关系,从而进行一些模块之间的操作,比如treeshaking等等
ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。
export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。js引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值

CommonJS
CommonJS 只能在运行时确定这些东西。CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成
CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值
比如const a = ‘func’; const func = require(’./test/’+a+’.js’), 这个括号里面是可以放表达式的!!表达式 只能在运行时才能确定值啊,所以cmd只能在运行时确定模块关系 ,这就注定了在编译阶段不能做一些事。

30、怎么判断两个对象是否相等

getOwnPropertyNames

只要两个对象的名和键值都相同。那么两个对象的内容就相同了
1.用Object.getOwnPropertyNames拿到对象的所以键名数组
2.比对键名数组的长度是否相等。否=>false。真=>3
3.比对键名对应的键值是否相等

怎么判断两个对象是否相等

 function isObjectValueEqual(a, b) {
        var aProps = Object.getOwnPropertyNames(a);
        var bProps = Object.getOwnPropertyNames(b);

        if (aProps.length != bProps.length) {
            return false;
        }

        for (var i = 0; i < aProps.length; i++) {
            var propName = aProps[i];
            var propA = a[propName];
            var propB = b[propName];
            if ( propA !== propB) {
                    return false;
            }
        }
        return true;
    }

31、利用Promise知识,用原生JS封装AJAX

var url = '/请求的路径';
var params = {
    id: 'id=123',
    limit: 'limit=10'
};

// 封装一个get请求的方法
function getJSON(url) {
    return new Promise(function(resolve, reject) {
        var XHR = XMLHttpRequest ? new XMLHttpRequest() : new window.ActiveXObject('Microsoft.XMLHTTP');

        XHR.onreadystatechange = function() {
            //readyState属性表示请求/响应过程的当前活动阶段。
            if (XHR.readyState == 4) {
                if ((XHR.status >= 200 && XHR.status < 300) || XHR.status == 304) {
                    try {
                        //获取数据
                        var response = JSON.parse(XHR.responseText);
                        resolve(response);
                    } catch (e) {
                        reject(e);
                    }
                } else {
                    reject(new Error("Request was unsuccessful: " + XHR.statusText));
                }
            }
        }
        XHR.open('GET', url + '?' + params.join('&'), true);
        XHR.send(null);
    })
}

getJSON(url).then(resp => console.log(resp));

32、用setTimeout实现setInterval

思路是使用递归函数,不断地去执行setTimeout从而达到setInterval的效果,看代码

function mySetInterval(fn, millisec){
  function interval(){
    setTimeout(interval, millisec);
    fn();
  }
  setTimeout(interval, millisec)
}

这个mySetInterval函数有一个叫做interval的内部函数,它通过setTimeout来自动被调用,在interval中有一个闭包,调用了回调函数并通过setTimeout再次调用了interval。

33、拦截器(Axios)

分为请求拦截器和响应拦截器两种。我一般把拦截器写在main.js里。

  1. 请求拦截器
    请求拦截器的作用是在请求发送前进行一些操作,例如在每个请求体里加上token,统一做了处理如果以后要改也非常容易。
 
axios.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么,例如加入token
    .......
    return config;
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  });
  1. 响应拦截器
    响应拦截器的作用是在接收到响应后进行一些操作,例如在服务器返回登录状态失效,需要重新登录的时候,跳转到登录页。
 
axios.interceptors.response.use(function (response) {
    // 在接收响应做些什么,例如跳转到登录页
    ......
    return response;
  }, function (error) {
    // 对响应错误做点什么
    return Promise.reject(error);
  });
  1. 移除拦截器
var myInterceptor = axios.interceptors.request.use(function () {/*...*/});
axios.interceptors.request.eject(myInterceptor);

4.为axios实例添加拦截器

var instance = axios.create();
instance.interceptors.request.use(function () {/*...*/});

34、箭头函数与普通函数的区别

  • 语法更加简洁、清晰
  • 箭头函数没有 prototype (原型),所以箭头函数本身没有this​​​​​​​
  • 箭头函数不会创建自己的this
  • call | apply | bind 无法改变箭头函数中this的指向
  • 箭头函数不能作为构造函数使用
  • 箭头函数不绑定arguments,取而代之用rest参数…代替arguments对象,来访问箭头函数的参数列表
  • 箭头函数不能用作Generator函数,不能使用yield关键字

35、JQuery相关面试题

jQuery面试题总结

36、一般的函数调用和链式调用的区别

链式调用完方法后,return this返回当前调用方法的对象。

37、Ajax和Fetch的异同

Ajax的本质是使用XMLHttpRequest对象来请求数据

Fetch 是全局量 window 的一个方法,它的主要特点有:
1、第一个参数是URL:
2、第二个是可选参数,可以控制不同配置的 init 对象
3、使用了 JavaScript Promises 来处理结果/回调:

fetch规范与jQuery.ajax()主要有两种方式的不同
1、从 fetch()返回的 Promise 将不会拒绝HTTP错误状态, 即使响应是一个 HTTP 404 或 500。相反,它会正常解决 (其中ok状态设置为false), 并且仅在网络故障时或任何阻止请求完成时,它才会拒绝
2、默认情况下, fetch在服务端不会发送或接收任何 cookies, 如果站点依赖于维护一个用户会话,则导致未经认证的请求(要发送 cookies,必须发送凭据头).
这一点也可以做一些处理:
如果想要在同域中自动发送cookie,加上 credentials 的 same-origin 选项

fetch(url, {
  credentials: ’same-origin'
})

same-origin值使得fetch处理Cookie与XMLHttpRequest类似。 否则,Cookie将不会被发送,导致这些请求不保留认证会话。对于CORS请求,使用include值允许将凭据发送到其他域:

fetch(url, {
  credentials: 'include'
})

最后fetch采用了Promise的异步处理机制,使用比ajax更加简单,有可能会逐渐代替ajax

38、undefined和null的区别:

  • undefined表示一个变量没有被声明,或者被声明了但没有被赋值(未初始化),一个没有传入实参的形参变量的值为undefined,如果一个函数什么都不返回,则该函数默认返回undefined。null则表示“什么都没有”,即“空值”
  • null 和 undefined 的值相等,但类型不等:undefined的类型(typeof)是undefined;null的类型(typeof)是object
  • js将未赋值的变量默认值设为undefined;
  • null和undefined之间的主要区别在于它们被转换为原始类型的方式

39、js中0.1+0.2不等于0.3问题,以及解决方法

原因
因为js使用的双精度浮点,所以在计算机内部存储数据的编码会出现误差,导致0.1+0.2=0.30000000000000004。和0.3相比较结果为false。
解决方案

  • 将其先转换成整数,再相加之后转回小数。具体做法为先乘10相加后除以10
 let x=(0.1*10+0.2*10)/10;
 console.log(x===0.3)
  • 使用number对象的toFixed方法,toFixed方法可以指定运算结果的小数点后的指定位数的数字,使保留一位小数就是toFixed(1)。
//let x=(0.1+0.2).toFixed(1)
//因为使用toFixed方法将number类型转换成了字符串类型
//,所以使用parseFloat将字符串转回number类型
let x=parseFloat((0.1+0.2).toFixed(1));
console.log(x===0.3);

40、JS中的for of和for in的区别?

推荐在循环对象属性的时候,使用for…in,在遍历数组的时候的时候使用for…of。

  • for…in循环出的是key,for…of循环出的是value
  • 注意,for…of是ES6新引入的特性。修复了ES5引入的for…in的不足
  • 作用于数组的for-in循环除了遍历数组元素以外,还会遍历自定义属性。for…of不能循环普通的对象,需要通过和Object.keys()搭配使用

41、为什么JS是单线程

好处:单线程的特点能更好的提高运行效率
JS用法层面:JS的主要用途是与用户互动和dom操作。这决定了它只能是单线程,否则会带来很复杂的同步问题。如:若JS有多个线程,一个操作一个节点,另一个删除那个节点,要先走哪一步?

42、使用过的DOM操作有什么

总结js常用的dom操作(js的dom操作API)

43、前端如何捕获错误

前端如何捕获错误

  1. window.onerror与window.addEventListener(‘error’)捕获js运行时错误

44、js实现一个轮播图

js实现一个轮播图
1、首先要有个盛放图片的容器,设置为单幅图片的宽高,且overflow:hidden,这样保证每次可以只显示一个图片
2、container内有个放图片的list进行position的定位 ,其中的图片采用float的方式,同时当图片进行轮播时,改变list的Left值使得其显示的图片发生变化。
3、图片的轮播使用定时器,通过定时器改变list的Left值是的图片循环展示
4、当鼠标滑动到图片上时,清除定时器,图片停止轮播,当鼠标移出时继续进行轮播
5、图片上有一组小圆点用于与当前显示的图片相对应,同时可以通过点击的方式查看对应的图片
6、图片可以通过点击pre,next进行左右滑动显示

45、JavaScript中显式原型和隐式原型的联系

显式原型:prototype

隐式原型:proto

二者的关系

隐式原型指向创建这个对象的函数的prototype

46、实现准确搜索的方法

实现准确搜索的方法
思路:

  1. 目前监听input输入框有三种方法
    一种是onchange事件,触发条件:内容改变且失去焦点。
    一种是onpropertychange事件,触发条件:元素内容改变即触发,另外js改变内容,该元素的属性改变也会触发。但只在IE11以下支持。
    一种是html5的oninput事件,触发条件:value值的改变,但是js方式改变value不会触发,且在IE8以上及其他标准浏览器支持。
  2. 采用最简单的html5的oninput事件,onchange事件触发条件无法达到理想的输入即检索,onpropertychange对浏览器的支持力度太低。
  3. 由于oninput事件是输入即触发,这会导致出现输入汉字时出发次数过多,即使在拼写的时候也会触发,影响用户体验和后台交互,所以利用compositionstart和compositionend来处理。

原理:
当浏览器有非直接的文字输入时,compositionstart事件就会同步触发,记住,是同步
当浏览器是直接的文字输入时,compositionend事件就会触发

47、类型转换问题

Boolean类型转换:

1.对于String:只有非空字符串为真
2.对于Number:除了0和NaN之外都为真
3.对于Boolean:true为真
4.对于Object:除了null之外都为真
5.undefined为false;

下面几个都会转化为0:

Number()
Number(0)
Number(’’)
Number(‘0’)
Number(false)
Number(null)
Number([])

48、export和export default有什么区别?在导入上有什么区别

1.export与export default均可用于导出常量、函数、文件、模块等
2.你可以在其它文件或模块中通过import+(常量 | 函数 | 文件 | 模块)名的方式,将其导入,以便能够对其进行使用
3.在一个文件或模块中,export、import可以有多个,export default仅有一个
4.通过export方式导出,在导入时要加{ },export default则不需要

49、generator和promise的区别

Generator 函数是将函数分步骤阻塞 ,只有主动调用 next() 才能进行下一步

50、事件委托

事件委托就是利用事件冒泡机制指定一个事件处理程序,来管理某一类型的所有事件。即:利用冒泡的原理,把事件加到父级上,触发执行效果。
好处:

  1. 只在内存中开辟了一块空间,节省资源同时减少了dom操作,提高性能
  2. 对于新添加的元素也会有之前的事件
    例子:ul中触发每个li来改变他们的背景颜色

如果有1000个按钮,给它们添加点击事件 你会怎么做?

事件委托

那你用事件委托在点击时是怎么获取到点击对象的呢?event.target 有兼容性问题吗? 那对应的是什么?

ev.target || ev.srcElement

你可能感兴趣的:(前端面经,javascript,html5,node.js)