JavaScript高频手写面试题
instanceof在查找的过程中会遍历左边的原型链,直到找到右边变量的prototype,如果查找失败就返回false
// instanceof的作用是判断当前目标对象是否是指定的类型 // 解决思路是:instanceof在查找的过程中会遍历左边的原型链,直到找到右边变量的prototype,如果查找失败就返回false let myInstanceof = (target, origin) => { while (target) { if (target.__proto__ === origin.prototype) { return true } target = target.__proto__ } return false } let arr = [2, 3, 5] console.log(myInstanceof(arr, Array)) // true console.log(myInstanceof(arr, Object)) // true
<script> // map() 方法返回一个新数组,新数组中的元素为原始数组中的每个元素调用函数处理后得到的值。 Array.prototype.myMap = function (fn, context) { let res = [] // 用来保存返回的新数组 context = context || [] let arr = this //由于方法是添加到数组原型上的,所以谁调用这个方法this就指向哪个数组 for (let i = 0; i < arr.length; i++) { res.push(fn(arr[i])) } return res } let arr = [1, 2, 3] let res = arr.myMap((item) => item * 2) console.log(res) </script>
Array.prototype.myMap = function (fn, thisValue) { var res = [] thisValue = thisValue || [] // pre:累加的值,cur:当前数组的值,index:当前的值的索引,arr:当前的数组 // this指的是当前调用这个方法是数组 this.reduce(function (pre, cur, index, arr) { return res.push(fn.call(thisValue, cur, index, arr)) }, []) return res } var arr = [2, 3, 1, 4] var res = arr.myMap(function (item, index, arr) { return item + 1 }) console.log(res) // 3,4,2,5
<body> <!-- reduce方法的使用, 参数1传递一个函数作为参数,函数中有四个参数: total:累加项的和 currValue:当前循环的项 currIndex:当前循环项的索引 arr:当前的数组 参数2是一个初始值:如果传递了值,它就会作为total的初始值,arr的第一项就会作为currvalue的值 如果没有传递值:arr的第一项就是作为total的值,第二项就会作为currvalue的值 --> <!-- <script> var arr = [1, 2, 3, 4] var i = 0 var y = 0 const res1 = arr.reduce(function (total, currValue, currIndex, arr) { console.log(i) i++ return total + currValue }) const res2 = arr.reduce(function (total, currValue, currIndex, arr) { console.log(y) y++ return total + currValue }, 10) console.log(res1) // 10 console.log(res2) // 20 </script> --> <script> //实现reduce方法 //如果有initValue,那么total的值就是initValue,currValue的值就是arr的第一项 //如果没有initValue,那么total的值就是arr的第一项 var arr = [1, 2, 3, 4] Array.prototype.myReduce = function (fn, initValue) { var i = 0 // 1.先判断initValue的值是否存在 if ( Object.prototype.toString.call(initValue) === '[object Undefined]' ) { // 如果不存在,initvalue的值就是初始值 initValue = this[0] i = 1 console.log('false') } for (i; i < this.length; i++) { initValue = fn(initValue, this[i], i, this) } return initValue } const res = arr.myReduce(function (total, currValue, currIndex, arr) { return total + currValue }, 10) console.log(res) </script> </body>
<body> 数组扁平化就是将多维数组转为一维数组 <!-- <script> // 方法一:使用flat,这是es6新增方法方法,flat方法的参数是需要转换的维度 var arr = [1, 3, 4, [3, [3, 5, [6, 1, 8]]]] //这是四维数组 console.log(arr.flat()) console.log(arr.flat(Infinity)) // 如果不知到数组的维度可以使用Infinity </script> --> <!-- 利用concat实现数组扁平化,不能和flat一样指定维度,concat可以链接两个数组或者多个数组--> <script> var arr = [1, 3, 4, [3, [3, 5, [6, 1, 8]]]] //这是四维数组 function myFlat(arr) { let res = [] for (let i = 0; i < arr.length; i++) { if (Array.isArray(arr[i])) { res = res.concat(myFlat(arr[i])) } else { res.push(arr[i]) } } return res } console.log(myFlat(arr)) </script> </body>
(1)使用递归实现深拷贝
<script> //创建一个引用类型的数据 var oldObj = { name: 'zs', happy: ['吃饭', '睡', '打豆豆'], parent: { father: 'li', mather: 'wang' } } //创建一个新对象 var newObject = {} //创建一个函数用于实现深拷贝,主要使用了递归 function cloneDeep(target, source) { //先循环获取每一项 for (let i in source) { let item = source[i] //判断每一项中的数据是否是数组 if (item instanceof Array) { target[i] = [] cloneDeep(target[i], item) } else if (item instanceof Object) { //如果当前项是对象,就返回 target[i] = {} cloneDeep(target[i], item) } else { target[i] = item } } return target } cloneDeep(newObject, oldObj) oldObj.name = '李四' console.log(oldObj) console.log(newObject) </script>
(2)使用Object.create()方法实现深拷贝
<script> //创建一个引用类型的数据 // var oldObj = { // name: "zs", // happy: ['吃饭', '睡', '打豆豆'], // parent: { // father: 'li', // mather: 'wang' // } // }; // //使用方法获取一个拷贝来的方法 // var newObj = Object.create(oldObj); // console.log(newObj); // console.log(newObj.__proto__); //注意使用该方法实现的深拷贝它的属性是放在它的原型上的,这些属性不是newObj自己拥有的 // object.create()方法的实现原理:利用了原型是继承 function create2(o) { function NewObj() {} NewObj.prototype = o return new NewObj() } //创建一个引用类型的数据 var oldObj = { name: 'zs', happy: ['吃饭', '睡', '打豆豆'], parent: { father: 'li', mather: 'wang' } } //使用方法获取一个拷贝来的方法 var newObj = create2(oldObj) console.log(newObj) console.log(newObj.__proto__) </script>
(3)使用jquery的extend方法实现深拷贝
<script src="./query.js"></script> <script> //创建一个引用类型的数据 var oldObj = { name: "zs", happy: ['吃饭', '睡', '打豆豆'], parent: { father: 'li', mather: 'wang' } }; var newObj = $.extend({}, oldObj); console.log(newObj); extend方法的使用方法 // extend(obj1,obj2,obj3):她将obj2,obj3的值复制给obj1,如果有重复的属性就覆盖她 </script>
(4)使用JSON.parse和JSON.stringify方法实现深拷贝
<script> //创建一个引用类型的数据 var oldObj = { name: "zs", happy: ['吃饭', '睡', '打豆豆'], parent: { father: 'li', mather: 'wang' } }; var newObj = JSON.parse(JSON.stringify(oldObj)); newObj.name = "李四"; console.log(oldObj); console.log(newObj) </script>
(1)使用扩展运算符
<!-- 什么是扩展运算符? 扩展运算符就是三个点,扩展运算符的作用就是将数据或者类数组对象转为用逗号隔开的值 --> <script> var arr = [1, [7, [9]], { a: "1" }, function() {}, null, undefined, NaN]; var result = [...arr]; console.log(result); //修改源对象中的某个值 arr[2].a = "222"; console.log(arr); console.log(result); </script>
(2)使用assgin方法
<script> var arr = [1, [7, [9]], { a: "1" }, function() {}, null, undefined, NaN]; var result = Object.assign(arr); console.log(result); //修改源对象中的某个值 arr[2].a = "222"; console.log(arr); console.log(result); </script>
<script> // 手动实现apply(obj, [arg1, arg2, arg3]) Function.prototype.myApply = function (context) { let obj = context || window obj.fn = this const arg = arguments[1] || [] // 如果传递的有参数,那么第二个就是传毒的参数数组 let res = obj.fn(...arg) delete obj.fn return res } function f(a, b) { console.log(a, b) console.log(this.name) } let obj = { name: '这是' } f.myApply(obj, [1, 2]) </script> <script> // 手动实现call(obj,arg1,arg2) Function.prototype.myCall = function (context) { let obj = context || window obj.fn = this // 获取参数,由于参数不确定传递过来的个数,就先去掉第一个参数,他就是要指向的this let arr = [...arguments].slice(1) res = obj.fn(...arr) delete obj.fn return res } function f(a, b) { console.log(a, b) console.log(this.name) } let obj = { name: '栗色' } f.myCall(obj, 1, 2) </script> <script> // 手动实现bind(obj,[arg1,arg2])由于bind函数支持柯里化 Function.prototype.myBind = function (context) { let obj = context || window let fn = this // 获取第一次传递过来的参数 let arg = Array.prototype.slice.call(arguments, 1) // 由于第一位是obj,所以需要将裁剪 return function () { let arr = Array.prototype.slice.call(arguments) fn.apply(obj, arg.concat(arr)) } } let obj = { name: '展示' } function fn(a, b) { console.log(a, b) } let call = fn.bind(obj, [2, 3]) call(20) </script>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<body> new的操作: (1)创建一个空对象, (2)将对象的__proto__指向构造函数的原型对象 (3)利用apply改变this的指向, (4)返回结果 <script> function Person(name, age) { this.age = age this.name = name } Person.prototype.sayHi = function () { console.log('hi,' + this.name) } var p1 = new Person('张三', 20) console.log(p1) // 手动实现new function myCreate() { // 创建一个空对象 let obj = {} // 获取传递过来的构造函数 let fn = [].shift.call(arguments) // shift方法将数组的第一项弹出来并且返回弹出的第一项,这里的[]填不填元素都没有关系 // 将空对象的__proto__指向构造函数的prototype(获取构造函数原型上的方法) obj.__proto__ = fn.prototype // 使用apply修改this的指向,将传递过来的参数赋值给obj(获取属性) console.log(arguments) let res = fn.apply(obj, arguments) // 确保返回的是一个对象(以防传递进的fn不是构造函数) return typeof res === 'object' ? res : obj } var p2 = myCreate(Person, '李四', 30) console.log(p2) </script> </body>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<body> <script> // promise 是一个容器,存放的是 未来的某个值 // 语法上来说,就是一个对象 // console.log(1); // const promise = new Promise((resolve, reject) => { // console.log(2); // let num = Math.random(); // if (num > 0.5) { // resolve('成功'); // } else { // reject('失败'); // } // }); // promise.then( // (res) => { // console.log(4, res); // }, // (err) => { // console.log(5, err); // } // ); // console.log(3); // promise 有几个状态,是常量,先定义起来 const PENDDING = 'pendding' // 等待状态 const FULFILLED = 'fulfilled' // 执行状态 const REJECTED = 'rejected' // 拒绝状态 class MyPromise { // cb为创建的promise的时候传递进来的函数 constructor(cb) { // 一个promise要关注几个点 this.status = PENDDING // 当前的promise的状态 this.value = undefined // 成功以后的返回值 this.reason = undefined // 失败以后的返回值 // 使用箭头函数可以固定this的指向,不然在cb中调用resolve,this就找不到指向 const resolve = (val) => { if (this.status === PENDDING) { // 改变状态 this.status = FULFILLED this.value = val } } const reject = (err) => { if (this.status === PENDDING) { this.status = REJECTED this.reason = err } } try { // 传递cb进来的时候,它是需要两个参数的,这两个参数分别是函数,所以在上面定义了两个函数 cb(resolve, reject) } catch (err) { reject(err) } } then(onFulfilled, onRejected) { if (this.status === FULFILLED) { onFulfilled(this.value) } if (this.status === REJECTED) { onRejected(this.reason) } } } const p = new MyPromise((resolve, reject) => { let num = Math.random() if (num > 0.5) { resolve('成功') } else { reject('失败') } }) p.then( (res) => { console.log('请求成功了:', res) }, (err) => { console.log('请求失败了:', err) } ) </script> </body>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<body> ajax请求的五个步骤: (1)创建xmlHttpRequest对象 (2)设置请求方法和请求的地址 (3)使用send发送情求 (4)监听状态变化 (5)最后接受返回的数据 <script> function myAjax() { let xhr = new XMLHttpRequest() xhr.open('get', 'url') xhr.onreadystatechange = () => { // xhr的readyState的状态有0-4:0(请求未就绪)1(已经建立服务器链接)2(请求发送)3(请求处理中) 4(已经接受了响应) if (xhr.readyState === 4) { if (xhr.status >= 200 && xhr.status < 300) { let string = xhr.responseText let object = JSON.parse(string) } } } // 发送请求 xhr.send() } </script> <!-- 使用promise实现ajax --> <script> function myAjax(url) { const p = new Promise(function (resolve, reject) { // 创建xhr对象 let xhr = new XMLHttpRequest() // 创建链接 xhr.open('get', url) // 监听状态改变 xhr.onreadystatechange = () => { if (xhr.readyState === 4) { if (xhr.status >= 200 && xhr.status < 300) { resolve(JSON.parse(xhr.responseText)) } else { reject('请求失败了') } } } // 发送请求 xhr.send() }) return p } </script> </body>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<body> <input id="input1" /> <script> // 防抖就是在在触发了事件的一段时候后执行函数,如果在这段时间里又触发了,就重新计数 // fn是指向的函数,而delay是执行的时间 function debounce(fn, delay) { if (typeof fn !== 'function') { throw new TypeError('fn不是函数') } let timer // 返回一个函数是因为它不是已返回就调用,而是当触发了时间的时候次啊会调用它 return function () { var _this = this // 这里将this赋值给_this是因为定时器中的this是指向window的 var args = arguments // 这里是获取调用函数时候传递进来的参数 // 如果定时器存在,就先清除定时器 if (timer) { clearTimeout(timer) } // 重置定时器 timer = setTimeout(function () { fn.apply(_this, args) }, delay) } } var input1 = document.getElementById('input1') input1.addEventListener( 'keyup', debounce(() => { console.log(input1.value) }, 600) ) </script> </body>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!-- * @Description: 节流函数就是连续触发事件,只在n秒中执行一次,常用场景;如拖动dom,如果使用防抖的话据会出现卡顿的情况,因为它只在结束的 时候触发一次 使用节流就流顺的多 * @Version: 2.0 * @Autor: Seven * @Date: 2021-02-16 12:25:47 * @LastEditors: Gan * @LastEditTime: 2021-02-16 12:42:27 --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>手写节流函数</title> </head> <body> <script> function throttle(fn, delay) { // 设置一个定时器 let timer // 返回一个函数,当触发事件的时候,就回触发这个事件 return function () { // 将调用该函数的元素给_this,因为定时器中的this是执行window的 var _this = this // 获取调用函数的时候,传递进来的参数 var args = arguments // 判断定时器是否存在,如果存在就表示当前的时间还没有过 if (timer) { return } timer = setTimeout(function () { fn.apply(_this, args) timer = null }, delay) } } </script> </body> </html>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
function getData(url) { return new Promise((resolve, reject) => { $.ajax({ url, success(data) { resolve(data) }, error(err) { reject(err) } }) }) } const url1 = 'image1Ur1' const url2 = 'image1Ur2' const url3 = 'image1Ur3' getData(url1) .then((data1) => { console.log(data1) getData(url2) }) .then((data2) => { console.log(data2) getData(url3) }) .then((data3) => { console.log(data3) }) .catch((err) => { console.log(err) })
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>实现懒加载</title> </head> <body> <ul> <li><img src="./imgs/default.png" data="./imgs/1.png" alt="" /></li> <li><img src="./imgs/default.png" data="./imgs/2.png" alt="" /></li> <li><img src="./imgs/default.png" data="./imgs/3.png" alt="" /></li> <li><img src="./imgs/default.png" data="./imgs/4.png" alt="" /></li> <li><img src="./imgs/default.png" data="./imgs/5.png" alt="" /></li> <li><img src="./imgs/default.png" data="./imgs/6.png" alt="" /></li> <li><img src="./imgs/default.png" data="./imgs/7.png" alt="" /></li> <li><img src="./imgs/default.png" data="./imgs/8.png" alt="" /></li> <li><img src="./imgs/default.png" data="./imgs/9.png" alt="" /></li> <li><img src="./imgs/default.png" data="./imgs/10.png" alt="" /></li> </ul> <script> let imgs = document.querySelectorAll('img') // 可视区高度 let clientHeight =window.innerHeight ||document.documentElement.clientHeight ||document.body.clientHeight function lazyLoad() { // 卷去的高度 let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop for (let i = 0; i < imgs.length; i++) { // 图片在可视区冒出的高度 let x = clientHeight + scrollTop - imgs[i].offsetTop // 图片在可视区内 if (x > 0 && x < clientHeight + imgs[i].height) { imgs[i].src = imgs[i].getAttribute('data') } } } // addEventListener('scroll', lazyLoad) or setInterval(lazyLoad, 1000) </script> </body> </html>