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>
<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>
<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>
<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>
<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>
<!-- * @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>
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) })
<!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>
浙公网安备 33010602011771号