irpas技术客

2022前端大厂面试题之JavaScript篇(1)_zag666

大大的周 5602

系列文章:2022前端大厂面试题之JavaScript篇(2)

文章目录 js中的同步和异步异步编程的实现方式闭包closure回调地狱原型原型链原型链面试题: 原型继承防抖和节流宏任务与微任务宏任务与微任务面试题:

js中的同步和异步

同步任务是指在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务,当我们打开网站时,网站的渲染过程,比如元素的渲染,其实就是一个同步任务。 异步任务是指不进入主线程,而进入任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程。异步模式:每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。当我们打开网站时,像图片的加载,音乐的加载,其实就是一个异步任务。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。 JavaScript的异步机制: (1)所有同步任务都在主线程上执行,行成一个执行栈 (2)主线程之外,还存在一个任务队列,只要异步任务有了结果,就会在任务队列中放置一个事件 (3)一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,看看里面还有哪些事件,那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行 (4)主线程不断的重复上面的第三步

异步编程的实现方式

1.回调函数:这是异步编程最基本的方法。 回调函数:当一个函数作为参数传入另一个参数中,并且它不会立即执行,只有当满足一定条件后该函数才可以执行,这种函数就称为回调函数。它的优点是简单、容易理解和部署,缺点是多个回调函数嵌套的时候会造成回调地狱,上下两层的回调函数间的代码耦合度太高,不利于代码的可维护。 2.事件监听:事件驱动模式。任务的执行不取决于代码的顺序,而取决于某个事件是否发生。 3.发布/订阅:假定存在一个"信号中心",某个任务执行完成,就向信号中心"发布"一个信号,其他任务可以向信号中心"订阅"这个信号,从而知道什么时候自己可以开始执行,又称"观察者模式"。 4.Promise:可以将嵌套的回调函数作为链式调用。每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。f1().then(f2);//f1的回调函数f2但有时会造成多个 then 的链式调用,造成代码语义不够明确。 5.Generator:ES6 提供的一种异步编程解决方案,当遇到异步函数执行的时候,将函数执行权转移出去,当异步函数执行完毕时再将执行权给转移回来。 6.async/await:generator 和 promise 实现的一个自动执行的语法糖。相对于Promise,优势:处理 then 的调用链,能够更清晰准确的写出代码;并且也能优雅地解决回调地狱问题。缺点:因为 await 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 await 会导致性能上的降低,代码没有依赖性的话,完全可以使用 Promise.all 的方式。

闭包closure

JavaScript函数内部可以直接读取全局变量,但是在函数外部无法读取函数内部的局部变量。 注意点:在函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明的是一个全局变量!

闭包就是能够读取其他函数内部变量的函数。 由于在javascript中,只有函数内部的子函数才能读取局部变量,所以说,闭包可以简单理解成“定义在一个函数内部的函数“。闭包让你可以在一个内层函数中访问到其外层函数的作用域,从而使这个外层函数的变量可以被外部访问到。闭包的最大作用就是延续函数内部某些变量的生命周期。

所以,在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

function init() { var name = "zagiee"; // name 是一个被 init 创建的局部变量 function displayName() { // displayName() 是内部函数,一个闭包 console.log(name); // 使用了父函数中声明的变量 } displayName(); } init();//zagiee function funA(){ var a = 10; // funA的活动对象之中; return function(){ //匿名函数的活动对象; alert(a); } } var b = funA(); b(); //10 funA()();//10

参考题目 闭包的特点: 1.让外部访问函数内部变量成为可能; 2.让这些变量的值始终保持在内存中,不会在外部函数调用后被自动清除; 3.可以避免使用全局变量,防止全局变量污染; 4.由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

回调地狱

回调函数中嵌套回调函数的情况就叫做回调地狱。回调地狱就是为是实现代码顺序执行而出现的一种操作,如何解决回调地狱: 1.通过Promise对象进行链式编程来解决:

Promise构造函数接收一个函数作为参数,我们需要处理的异步任务就卸载该函数体内,该函数的两个参数是resolve,reject。异步任务执行成功时调用resolve函数返回结果,反之调用reject。Promise对象的then方法用来接收处理成功时响应的数据,catch方法用来接收处理失败时相应的数据。Promise的链式编程可以保证代码的执行顺序,前提是每一次在than做完处理后,一定要return一个Promise对象,这样才能在下一次then时接收到数据。

2.async/await语法糖:

async关键字,他作为一个关键字放到声明函数前面,表示该函数为一个异步任务,不会阻塞后面函数的执行。await关键字只能在使用async定义的函数中使用?,await后面可以直接跟一个 Promise实例对象,await可以直接拿到Promise中resolve中的数据。 原型

①所有引用类型,对象都有一个__proto__(隐式原型)属性,属性值是一个普通的对象 ②所有函数都有一个prototype(显式原型)属性,属性值是一个普通的对象 ③构造函数(可以用来new的函数就叫构造函数,箭头函数不能用来当做构造函数)的prototype和其实例的__proto__是指向同一个地方的,这个地方就叫做原型对象

var a = [1,2,3]; a.__proto__ === Array.prototype; // true function Person(name, age) { this.name = name this.age = age } Person.prototype.sayName = function() { console.log(this.name) } const person1 = new Person('小明', 20) console.log(Person.prototype === person1.__proto__) // true console.log(Function.prototype === Person.__proto__) // true console.log(Function.prototype === Object.__proto__) // true,function Object()其实也是个函数,所以他是Function构造函数的实例 console.log(Function.prototype === Function.__proto__) // true console.log(Person.prototype.constructor === Person) // true console.log(Person.prototype.__proto__ === Object.prototype) // true console.log(Function.prototype.__proto__ === Object.prototype) // true console.log(Object.prototype.__proto__ === null) // true console.log(Person.prototype.__proto__ === Function.prototype) // false

Function是一个构造函数,他有显式原型对象,这个显式原型对象是对象(而非函数)的一个实例,所以他有隐式原型,指向他的构造函数的显式原型,也就是Function.prototype.__proto__ === Object.prototype,一个具体函数Person的隐式原型也是Object.prototype:Person.prototype.__proto__ === Object.prototype 此图出处

原型链 当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它的__proto__隐式原型上查找,即它的构造函数的prototype,如果还没有找到就会再在构造函数的prototype的__proto__中查找,这样一层一层向上查找就会形成一个链式结构,我们称为原型链。Object.prototype其实也有__proto__,指向null,那才是原型链的终点。 function Parent(month){ this.month = month; } var child = new Parent('Ann'); console.log(child.month); // Ann console.log(child.father); // undefined

在child中查找某个属性时,会执行下面步骤: constructor和prototype是成对的,你指向我,我指向你。

原型链面试题: 原型继承

原型继承:实例可以使用构造函数上的prototype中的方法。

function Person(name) { // 构造函数 this.name = name } Person.prototype.sayName = function() { // 往原型对象添加方法 console.log(this.name) } const person = new Person('zagiee') // 实例 // 使用构造函数的prototype中的方法 person.sayName() // zagiee console.log(Person instanceof Function) // true console.log(Person instanceof Object) // true console.log(person instanceof Person) // true console.log(person instanceof Object) // true 防抖和节流

防抖 (debounce):防止抖动,单位时间内事件触发会被重置,避免事件被误伤触发多次。有个需要频繁触发的函数在规定时间内,只让最后一次生效,前面不生效。代码实现重在清零 clearTimeout。

<button id="btn">按钮</button> function debounce(fn, delay) { //记录上一次的延时器 let timer = null; return function () { // 清楚上一次延时器 clearTimeout(timer); //重新设置新的延时器 timer = setTimeout(function () { fn.apply(this); },delay); } } document.getElementById('btn').onclick = debounce(function () {console.log('点击事件被触发了' + Date.now())},1000);

节流(throttle):控制流量,单位时间内事件只能触发一次。一个函数执行一次后,只有大于设定的执行周期后才会执行第二次。有个需要频繁触发的函数,处于优化性能角度,在规定时间内,只让函数触发的第一次生效,后面不生效。所以节流会稀释函数的执行频率。代码实现重在开锁关锁。

function throttle(fn, wait) { //记录上一次函数触发时间 let lastTime = 0; return function () { let nowTime = Date.now(); if (nowTime - lastTime > wait) { //修正this指向问题 fn.call(this); //同步时间 lastTime = nowTime; } }; } document.onscroll = throttle(function () {console.log('scroll事件被触发了' + Date.now())},200); 宏任务与微任务

宏任务:当前调用栈中执行的代码成为宏任务。(主代码快,定时器等等)。 e.g.:I/O、setTimeout、setInterval、setImmediate、requestAnimationFrame

微任务: 当前(此次事件循环中)宏任务执行完,在下一个宏任务开始之前需要执行的任务,可以理解为回调事件。 e.g.:process.nextTick、MutationObserver、Promise.then catch finally

宏任务中的事件放在callback queue(有多个)中,由事件触发线程维护;微任务的事件放在微任务队列(只有一个)中,由js引擎线程维护。

运行机制:

在执行栈中执行第一个宏任务(只有一个任务:执行主线程的js代码)。执行过程中遇到微任务,将微任务添加到微任务队列中(微任务队列只有一个)。当前宏任务执行完毕,立即执行微任务队列中的所有任务。当前微任务队列中的任务执行完毕,检查渲染,GUI线程接管渲染。渲染完毕后,js线程接管,开启下一次事件循环,执行下一次宏任务(事件队列中取)。 宏任务与微任务面试题: console.log('start') setTimeout(() => { console.log('setTimeout') }, 0) new Promise((resolve) => { console.log('promise') resolve() }) .then(() => { console.log('then1') }) .then(() => { console.log('then2') }) console.log('end') //输出: start promise end then1 then2 setTimeout async function async1 () {//函数创建未执行,所以不输出 console.log('async1 start'); await async2();//此处输出async2后在微队列等待执行栈完成任务后执行。 console.log('async1 end') } async function async2 () { console.log('async2') } console.log('script start'); setTimeout(function () {//定时器,将任务放在宏任务中 console.log('setTimeout')//微队列执行完成后执行宏队列,输出setTimeout。 }, 0); async1();//调用时才会执行 new Promise(function (resolve) { console.log('promise1');//输出promise1完成后,Promise.then推送至微队列排队 resolve() }).then(function () { console.log('promise2') }); console.log('script end')//输出script end,此时执行栈清空,开始执行微队列输出async1 end和promise2 //输出: script start async1 start async2 promise1 script end async1 end promise2 undefined setTimeout

总结: 1.先执行同步和立即执行任务,比如说console.log()、new Promise() 2.再依次执行微任务,比如说thenable函数和catchable函数 3.当微任务执行完成后开始执行宏任务,比如说定时器、事件回调等


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #This