1.手写Object.create
思路:将传入的对象作为原型
function create(obj) {
function F() {}
F.prototype = obj
return new F()
}
2.手写instanceof方法
思路:instanceof运算符用于判断构造函数的prototype属性是否出现在对象的原型链中任何位置。
实现步骤:
- 1.首先获取类型的原型
- 2.然后获取对象的原型
- 3.然后一直循环判断对象的原型是否等于类型的原型,直到对象原型为null,因为原型链最终为null
// 手写 instanceof 方法
function instanceOf(source, target) {
let proto = Object.getPrototypeOf(source);
while (proto) {
if (proto === target.prototype) {
return true;
}
proto = Object.getPrototypeOf(proto);
}
return false;
}
class Parent {
constructor(name) {
this.name = name;
}
}
class Child extends Parent{
}
const temp = new Child();
console.log(instanceOf(temp, Parent));
3.手写new操作符
思路:
在调用 new
的过程中会发生以上四件事情:
(1)首先创建了一个新的空对象
(2)设置原型,将对象的原型设置为函数的 prototype 对象。
(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)
(4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
// 手写new操作符
function myNew(constructor) {
if (typeof constructor !== "function") {
throw TypeError("type error");
}
// 创建空对象
const obj = {};
// 空对象__proto__ 指向 构造函数的prototype
obj.__proto__ = constructor.prototype;
// 执行构造函数,并将构造函数的this指向新创建的空对象
const returnObj = constructor.apply(obj, Array.from(arguments).slice(1))
// 若构造函数自身有返回对象或函数,则返回构造函数返回的,否则返回新创建的对象。
return typeof (returnObj === 'object' || typeof returnObj === "function") ? returnObj : obj;
}
4.手写Promise函数
// MyPromise.js
// 先定义三个常量表示状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
// 新建 MyPromise 类
class MyPromise {
constructor(executor){
// executor 是一个执行器,进入会立即执行
// 并传入resolve和reject方法
executor(this.resolve, this.reject)
}
// 储存状态的变量,初始值是 pending
status = PENDING;
// 用来存放then后面的回调函数。
onFulfilledCallback = [];
//用来存放catch后面的回调函数
onRejectedCallback = [];
// resolve和reject为什么要用箭头函数?
// 如果直接调用的话,普通函数this指向的是window或者undefined
// 用箭头函数就可以让this指向当前实例对象
// 成功之后的值
value = null;
// 失败之后的原因
reason = null;
// 更改成功后的状态
resolve = (value) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态修改为成功
this.status = FULFILLED;
// 保存成功之后的值
this.value = value;
// 如果是异步函数,resolve可能在then之后执行,故如果是异步的函数,就在resolve里面调用。
if (this.onFulfilledCallback.length !== 0) {
this.onFulfilledCallback.shift()(value);
}
}
}
// 更改失败后的状态
reject = (reason) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态成功为失败
this.status = REJECTED;
// 保存失败后的原因
this.reason = reason;
this.onRejectedCallback && this.onRejectedCallback(reason);
if (this.onRejectedCallback.length !== 0) {
this.onRejectedCallback.shift()(reason);
}
}
}
then(onFulfilled, onRejected) {
// 判断状态
if (this.status === FULFILLED) {
// 调用成功回调,并且把值返回
onFulfilled(this.value);
} else if (this.status === REJECTED) {
// 调用失败回调,并且把原因返回
onRejected(this.reason);
} else if (this.status === PENDING) {
this.onFulfilledCallback.push(onFulfilled);
this.onRejectedCallback.push(onRejected);
}
}
}
const promise = new MyPromise((resolve, reject)=>{
setTimeout(()=> {
resolve("3秒后执行成功")
}, 3000)
})
promise.then((data) => {
console.log(data);
})
5.手写Promise.all函数
核心思路
- 接收一个 Promise 实例的数组或具有 Iterator 接口的对象作为参数
- 这个方法返回一个新的 promise 对象,
- 遍历传入的参数,用Promise.resolve()将参数"包一层",使其变成一个promise对象
- 参数所有回调成功才是成功,返回值数组与参数顺序一致
- 参数数组其中一个失败,则触发失败状态,第一个触发失败的 Promise 错误信息作为 Promise.all 的错误信息。
// Promise.all实现
function PromiseAll(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) {
throw TypeError("type error")
}
const result = [];
let count = 0;
promises.forEach(promise => {
promise.then(val => {
count++;
result.push(val);
if (count === promises.length) {
resolve(result);
}
}).catch(error => {
reject(error);
})
});
})
}
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("延迟3秒")
}, 3000);
})
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject("延迟5秒")
}, 5000);
})
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("延迟4秒")
}, 4000);
})
const result = PromiseAll([promise1, promise2, promise3]);
result.then(val => {
console.log(val)
}).catch(error => {
console.log(error)
})
6.手写Promise.race()函数
思路:谁先执行完,就返回谁。
// Promise.race实现
function PromiseRace(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) {
throw TypeError("type error")
}
promises.forEach(promise => {
promise.then(val => {
resolve(val);
}).catch(error => {
reject(error);
})
});
})
}
7.手写一个Promise.finally?
Promise.prototype.myFinally = function (cb) {
return this.then((value) => {
return Promise.resolve(cb());
}, (error) => {
return Promise.resolve(cb());
})
}
promise2.myFinally(() => {
console.log("finally正在执行")
})
8.手写一个promise.allSettled
function PromiseAllSettled(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) {
throw TypeError("type error");
}
const result = [];
let count = 0;
promises.forEach(promise => {
promise.then(val => {
result.push(val);
}).catch(error => {
result.push(error);
}).finally(() => {
count++;
if (count === promises.length) {
resolve(result);
}
})
})
})
}
9.手写一下Promise.any?
原理:Promise.any是一个JavaScript的全局函数,它接收一个Promise对象的可迭代参数(如数组),并返回一个新的Promise对象。这个新的Promise对象将在可迭代参数中的任意一个Promise对象变为fulfilled状态时被解析,如果可迭代参数中的所有Promise对象都变为rejected状态,则返回一个rejected状态的Promise对象。
Promise.prototype.myAny = function (promises) {
return new Promise((resolve, reject) => {
if (Object.prototype.toString.call(promises) !== '[object Array]') {
throw new TypeError("promises必须为数组");
}
let count = 0;
promises.forEach((promise, index) => {
promise.then((data) => {
resolve(data);
}).catch((error) => {
count++;
if (count === promises.length) {
reject(new AggregateError('All promises were rejected'))
}
})
})
})
}
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('promise1');
}, 1000)
})
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('promise2');
}, 3000)
})
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('promise3');
}, 2000)
})
Promise.prototype.myAny([promise1, promise2, promise3]).then((data) => {
console.log(data)
});
10.手写一个Promise.resolve()?
Promise.myResolve = function (value) {
if (value instanceof Promise) {
return value;
}
return new Promise((resolve) => resolve(value))
}
Promise.myResolve("执行成功").then(data => {
console.log(data)
})
11.手写一个Promise.reject()?
Promise.myReject = function (value) {
if (value instanceof Promise) {
return value;
}
return new Promise((resolve, reject) => reject(value))
}
Promise.myReject("执行失败").catch(data => {
console.log(data)
})
12.来说一下如何串行执行多个Promise?
const delay = (time) => {
return new Promise((resolve, reject) => {
console.log(`wait ${time}s`)
setTimeout(() => {
console.log("executor");
resolve();
}, time * 1000);
});
}
const arr = [2, 3, 5];
// 方法一:使用reduce实现
arr.reduce((s, v) => {
console.log(s, v)
return s.then(() => delay(v))
}, Promise.resolve())
// 方法二:使用 async和await和循环
(async function() {
for (let time of arr) {
await delay(time);
}
})()
var p = Promise.resolve();
// 方法三:用一个for循环以及闭包保存
for (const time of arr) {
p = p.then(() => delay(time))
}
7.手写防抖函数?
原理: 函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。
<body>
<button onclick="fn()">Click</button>
</body>
// 手写防抖函数
function debounce(func, wait) {
let timeout;
return function(...args) {
const context = this; // 保留调用的上下文
// 清除上一个定时器
clearTimeout(timeout);
// 设置新的定时器
timeout = setTimeout(() => {
func.apply(context, args); // 调用原函数
}, wait);
};
}
// 使用示例
const fn = debounce(function() {
console.log('Function executed!');
}, 1000);
// 当你快速多次调用fn时,它只会在最后一次调用后的1秒后执行。
8.手写节流函数?
原理:函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。
function throttle(callback, wait) {
let timeout = null;
return function () {
const thisArg = this;
if (!timeout) {
timeout = setTimeout(() => {
callback.apply(thisArg, arguments);
clearTimeout(timeout);
timeout = null;
}, wait)
}
}
}
9.手写函数判断某个类型?
function getType(value) {
if (value === null) {
return value + '';
}
if (typeof value === "object") {
let valueType = Object.prototype.toString.call(value);
const type = valueType.split(" ")[1].split("");
type.pop(); // 将最后的“]”弹出来
return type.join("").toLowerCase();
} else {
return typeof value;
}
}
10.手写call函数
call 函数的实现步骤:
- 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
- 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
- 处理传入的参数,截取第一个参数后的所有参数。
- 将函数作为上下文对象的一个属性。
- 使用上下文对象来调用这个方法,并保存返回结果。
- 删除刚才新增的属性。
- 返回结果。
// call函数实现
Function.prototype.myCall = function(context) {
// 判断调用对象
if (typeof this !== "function") {
console.error("type error");
}
// 获取参数
let args = [...arguments].slice(1),
result = null;
// 判断 context 是否传入,如果未传入则设置为 window
context = context || window;
// 将调用函数设为对象的方法
context.fn = this;
// 调用函数
result = context.fn(...args);
// 将属性删除
delete context.fn;
return result;
};
11.手写Apply函数
apply 函数的实现步骤:
- 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
- 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
- 将函数作为上下文对象的一个属性。
- 判断参数值是否传入
- 使用上下文对象来调用这个方法,并保存返回结果。
- 删除刚才新增的属性
- 返回结果
// apply 函数实现
Function.prototype.myApply = function(context) {
// 判断调用对象是否为函数
if (typeof this !== "function") {
throw new TypeError("Error");
}
let result = null;
// 判断 context 是否存在,如果未传入则为 window
context = context || window;
// 将函数设为对象的方法
context.fn = this;
// 调用方法
if (arguments[1]) {
result = context.fn(...arguments[1]);
} else {
result = context.fn();
}
// 将属性删除
delete context.fn;
return result;
};
12.手写bind函数
bind 函数的实现步骤:
- 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
- 保存当前函数的引用,获取其余传入参数值。
- 创建一个函数返回
- 函数内部使用 apply 来绑定函数调用,需要判断函数作为构造函数的情况,这个时候需要传入当前函数的 this 给 apply 调用,其余情况都传入指定的上下文对象。
this instanceof Fn ? this : context
含义:
返回一个新的函数 Fn
,该函数内部通过 apply
方法来调用原函数 fn
。在 apply
方法中,根据调用方式的不同决定 this
的取值:如果是通过 new
操作符调用 Fn
构造函数,则 this
会指向新创建的实例;否则,this
会指向传入的 context
上下文。
// bind 函数实现
Function.prototype.myBind = function(context, ...args) {
if (typeof this !== 'function') {
throw new TypeError('Bind must be called on a function');
}
const self = this;
const boundFunction = function(...innerArgs) {
// 判断是否使用new调用,如果是new调用,绑定的this值失效,而是指向新创建的对象
if (this instanceof boundFunction) {
return new self(...args, ...innerArgs);
} else {
return self.apply(context, args.concat(innerArgs));
}
};
// 继承原始函数的原型
function Temp() {}
Temp.prototype = this.prototype;
boundFunction.prototype = new Temp();
return boundFunction;
};
// 示例
function example(name, age) {
this.name = name;
console.log(this.color);
console.log(name, age);
}
const boundExample = example.myBind({ color: 'red' }, 'John');
boundExample(30); // 输出:red, John, 30
const newBoundExample = new boundExample(30); // 输出:undefined, John, 30
console.log(newBoundExample.name); // 输出:John
13.手写实现原型链继承?
1.原型链继承
function Parent () {
this.name = 'kevin';
}
Parent.prototype.getName = function () {
console.log(this.name);
}
function Child () {
}
Child.prototype = new Parent();
var child1 = new Child();
console.log(child1.getName()) // kevin
2.借用构造函数(经典继承)
function Parent () {
this.names = ['kevin', 'daisy'];
}
function Child () {
Parent.call(this);
}
var child1 = new Child();
child1.names.push('yayu');
console.log(child1.names); // ["kevin", "daisy", "yayu"]
var child2 = new Child();
console.log(child2.names); // ["kevin", "daisy"]
优点:
1.避免了引用类型的属性被所有实例共享
2.可以在 Child 中向 Parent 传参
缺点:
方法都在构造函数中定义,每次创建实例都会创建一遍方法。
3.组合继承
原型链继承和经典继承双剑合璧。
function Parent (name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child (name, age) {
Parent.call(this, name);
this.age = age;
}
Child.prototype = new Parent();
var child1 = new Child('kevin', '18');
child1.colors.push('black');
console.log(child1.name); // kevin
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]
var child2 = new Child('daisy', '20');
console.log(child2.name); // daisy
console.log(child2.age); // 20
console.log(child2.colors); // ["red", "blue", "green"]
优点:融合原型链继承和构造函数的优点,是 JavaScript 中最常用的继承模式。
4.原型式继承
function createObj(o) {
function F(){}
F.prototype = o;
return new F();
}
就是 ES5 Object.create 的模拟实现,将传入的对象作为创建的对象的原型。
缺点:
包含引用类型的属性值始终都会共享相应的值,这点跟原型链继承一样。
5. 寄生式继承
创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。
function createObj (o) {
var clone = object.create(o);
clone.sayName = function () {
console.log('hi');
}
return clone;
}
缺点:跟借用构造函数模式一样,每次创建对象都会创建一遍方法。
6. 寄生组合式继承
function Parent (name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child (name, age) {
Parent.call(this, name);
this.age = age;
}
// 关键的三步
var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
var child1 = new Child('kevin', '18');
console.log(child1);
数据处理
1.实现日期格式化函数
dateFormat(new Date('2020-12-01'), 'yyyy/MM/dd') // 2020/12/01
dateFormat(new Date('2020-04-01'), 'yyyy/MM/dd') // 2020/04/01
dateFormat(new Date('2020-04-01'), 'yyyy年MM月dd日') // 2020年04月01日
const dateFormat = (dateInput, format)=>{
var day = dateInput.getDate()
var month = dateInput.getMonth() + 1
var year = dateInput.getFullYear()
format = format.replace(/yyyy/, year)
format = format.replace(/MM/,month)
format = format.replace(/dd/,day)
return format
}
2.交换a,b的值,不能用临时变量
a = a + b;
b = a - b;
a = a - b;
3. 实现数组的乱序输出?
function func(arr) {
const copyArr = arr.slice();
for (let i = 0; i < copyArr.length; i++) {
const randomIndex = Math.floor(Math.random() * copyArr.length); //生成一个从0到数组长度的随机数
[copyArr[i], copyArr[randomIndex]] = [copyArr[randomIndex], copyArr[i]]; // 交换当前遍历到的结点与随机数的位置。
console.log(randomIndex);
}
console.log(copyArr)
}
const arr = [1,2,3,4,5];
func(arr);
4.实现数组元素求和?
- arr=[1,2,3,4,5,6,7,8,9,10],求和
function func(arr) {
return arr.reduce((pre, cur) => pre + cur, 0);
}
const arr = [1,2,3,4,4];
console.log(func(arr));
- arr=[1,2,3,[[4,5],6],7,8,9],求和 递归实现:
function func(arr) {
let result = 0;
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
result += func(arr[i]);
} else {
result += arr[i];
}
}
return result;
}
const arr = [1,2,3,[[4,5],6]];
console.log(func(arr));
另一种方法:
function func(arr) {
return arr.toString().split(',').reduce((pre, cur) => Number(pre) + Number(cur))
}
const arr = [1,2,3,[[4,5],6]];
console.log(func(arr));
5.实现数组的扁平化?
递归实现:
//实现数组的扁平化
function func(arr) {
let result = [];
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
result = result.concat(func(arr[i]));
} else {
result.push(arr[i]);
}
}
return result;
}
const arr = [1,2,3,[[4,5],6]];
console.log(func(arr));
使用reduce的方式:
//实现数组的扁平化
function func(arr) {
return arr.reduce((pre, cur) => pre.concat(Array.isArray(cur) ? func(cur) : cur), []);
}
const arr = [1,2,3,[[4,5],6]];
console.log(func(arr));
使用toString和split的方式:
//实现数组的扁平化
function func(arr) {
return arr.toString().split(",").map(val => Number(val));
}
const arr = [1,2,3,[[4,5],6]];
console.log(func(arr));
使用ES6中flat:
//实现数组的扁平化
function func(arr) {
return arr.flat(Infinity);
}
const arr = [1,2,3,[[4,5],6]];
console.log(func(arr));
6. 实现数组去重?
// 使用ES6中Set和拓展运算符实现数组去重
function func(arr) {
return [...new Set(arr)];
}
//使用indexOf 和filter方法去重
function func1(arr) {
return arr.filter((val, index) => arr.indexOf(val) === index)
}
7. 实现数组的flat方法?(实现数组扁平化)
方式一:
function _flat(arr, depth) {
if(!Array.isArray(arr) || depth <= 0) {
return arr;
}
return arr.reduce((prev, cur) => {
if (Array.isArray(cur)) {
return prev.concat(_flat(cur, depth - 1))
} else {
return prev.concat(cur);
}
}, []);
}
方式二:
const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, "string", { name: "弹铁蛋同学" }]
function myFlat(arr) {
let result = [];
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
result = result.concat(...myFlat(arr[i])); //展开数组
} else {
result.push(arr[i]);
}
}
return result;
}
let flat = myFlat(arr);
console.log(flat)
8.手写一下常见数组的高阶函数的模拟实现?(forEach,map,filter,every,some,find/findIndex,reduce)
答:
1.forEach模拟实现:
forEach
主要用于数组的简单遍历
Array.prototype.myForEach = function (callbackFn) {
// 判断this是否合法
if (this === null || this === undefined) {
throw new TypeError("Cannot read property 'myForEach' of null");
}
// 判断callbackFn是否合法
if (Object.prototype.toString.call(callbackFn) !== "[object Function]") {
throw new TypeError(callbackFn + ' is not a function')
}
// 取到执行方法的数组对象和传入的this对象
var _arr = this, thisArg = arguments[1] || window;
for (var i = 0; i < _arr.length; i++) {
// 执行回调函数
callbackFn.call(thisArg, _arr[i], i, _arr);
}
}
2.map方法模拟实现:
map
函数对数组的每个元素执行回调函数,并返回含有运算结果的新数组
Array.prototype.myMap = function (callbackFn) {
if (this === null || this === undefined) {
throw new TypeError("不能为空");
}
if (Object.prototype.toString.call(callbackFn) === '[Object Function]') {
throw new TypeError("必须是方法");
}
const arr = this;
const thisArgs = arguments[1] || window;
const result = [];
for (let i = 0; i < arr.length; i++) {
result.push(callbackFn.call(thisArgs, arr[i], i, arr));
}
return result;
}
3.filter方法的模拟实现:
filter
是过滤的意思,它对数组每个元素执行回调函数,返回回调函数执行结果为true
的元素。
Array.prototype.myFilter = function (callbackFn) {
if (this === null || this === undefined) {
throw new TypeError("不能为空");
}
if (Object.prototype.toString.call(callbackFn) === '[Object Function]') {
throw new TypeError("必须是方法");
}
const arr = this;
const thisArgs = arguments[1] || window;
const result = [];
for (let i = 0; i < arr.length; i++) {
if (callbackFn.call(thisArgs, arr[i], i, arr)) {
result.push(arr[i]);
}
}
return result;
}
4.every方法模拟实现:
every
并不返回数组,返回布尔值 true/false
,数组的每个元素执行回调函数,如果执行结果全为 true
,every
返回 true
,否则返回 false
。
Array.prototype.myEvery = function (callbackFn) {
if (this === null || this === undefined) {
throw new TypeError("不能为空");
}
if (Object.prototype.toString.call(callbackFn) === '[Object Function]') {
throw new TypeError("必须是方法");
}
const arr = this;
const thisArgs = arguments[1] || window;
for (let i = 0; i < arr.length; i++) {
if (!callbackFn.call(thisArgs, arr[i], i, arr)) {
return false;
}
}
return true;
}
5.some方法模拟实现:
some
与 every
功能类似,都是返回布尔值。只要回调函数结果有一个 true
,some
便返回 true
,否则返回 false
。
Array.prototype.mySome = function (callbackFn) {
if (this === null || this === undefined) {
throw new TypeError("不能为空");
}
if (Object.prototype.toString.call(callbackFn) === '[Object Function]') {
throw new TypeError("必须是方法");
}
const arr = this;
const thisArgs = arguments[1] || window;
for (let i = 0; i < arr.length; i++) {
if (callbackFn.call(thisArgs, arr[i], i, arr)) {
return true;
}
}
return false;
}
6.find/findIndex方法模拟实现:
find
与 findIndex
是 ES6
新添加的数组方法,返回满足回调函数的第一个数组元素/数组元素索引。当数组中没有能满足回调函数的元素时,会分别返回 undefined和-1
。
Array.prototype.myFind = function (callbackFn) {
if (this === null || this === undefined) {
throw new TypeError("不能为空");
}
if (Object.prototype.toString.call(callbackFn) === '[Object Function]') {
throw new TypeError("必须是方法");
}
const arr = this;
const thisArgs = arguments[1] || window;
for (let i = 0; i < arr.length; i++) {
if (callbackFn.call(thisArgs, arr[i], i, arr)) {
return arr[i];
}
}
return undefined;
}
reduce方法模拟实现:
Array.prototype.myReduce = function (callbackFn) {
if (this === null || this === undefined) {
throw new TypeError("不能为空");
}
if (Object.prototype.toString.call(callbackFn) === '[Object Function]') {
throw new TypeError("必须是方法");
}
var i = 0, arr = this, accumulator = arguments[1];
if (accumulator === undefined) {
if (arr.length === 0) {
throw new Error("数组不能为空");
}
accumulator = arr[0];
i++;
}
for (; i < arr.length; i++) {
accumulator = callbackFn(accumulator, arr[i], i, arr);
}
return accumulator;
}
7.实现数组的push方法?
// 实现数组的push方法
Array.prototype.myPush = function () {
let arr = this;
for (let i = 0; i < arguments.length; i++) {
arr[arr.length] = arguments[i];
}
return this.length;
}
8.实现数组的filter方法?
// 实现数组的filter方法
Array.prototype.myFilter = function (callback) {
let arr = this;
let result = [];
for (let i = 0; i < arr.length; i++) {
if (callback(arr[i], i, arr)) {
result.push(arr[i]);
}
}
return result;
}
9.js实现快速排序?
function sortArray(nums) {
quickSort(0, nums.length - 1, nums);
return nums;
}
function quickSort(start, end, arr) {
if (start < end) {
const mid = sort(start, end, arr);
quickSort(start, mid - 1, arr);
quickSort(mid + 1, end, arr);
}
}
function sort(start, end, arr) {
const base = arr[start];
let left = start;
let right = end;
while (left !== right) {
// 从右边开始查找小于基数的值
while (left < right && arr[right] >= base) {
right--;
}
arr[left] = arr[right]; // 将找到的值赋给arr[left]
// 从左边开始查找大于基数的值
while (left < right && arr[left] <= base) {
left++;
}
arr[right] = arr[left]; // 将找到的值赋给arr[right]
}
arr[left] = base; // 将基数放回其正确的位置
return left;
}
const arr = [2,1,3,4,5,3,3,4];
const result = sortArray(arr);
console.log(result);
10.手写实现一个深递归?
实现深拷贝有三种方法:
1. 使用递归拷贝来实现:
function deepClone(source) {
let target;
if (typeof source !== 'object' || source === null) {
return target;
}
// 区分source是数组类型还是对象类型。
if (Object.prototype.toString.call(source) === '[object Array]') {
target = [];
} else {
target = {};
}
for (let key in source) {
// 如果不是对象的话,直接赋值
if (typeof source[key] !== 'object') {
target[key] = source[key];
} else {
// 如果是对象,要递归遍历
target[key] = deepClone(source[key]);
}
}
return target;
}
2. 使用JSON序列化和反序列化来实现:
const obj1 = {
a: 1,
b: {
c: 2,
d: [3, 4],
},
};
const obj2 = JSON.parse(JSON.stringify(obj1));
obj2.b.c = 5;
console.log(obj1.b.c); // 2
console.log(obj2.b.c); // 5
3. 使用lodash 的 cloneDeep() 方法
11.实现字符串的repeat()方法?
方法一:使用双层数组
//实现字符串的repeat方法
//输入字符串s,以及其重复的次数,输出重复的结果,例如输入abc,2,输出abcabc。
function repeat(str, num) {
let result = [];
for (let i = 0; i < num; i++) {
for (let j = 0; j < str.length; j++) {
result.push(str.charAt(j));
}
}
return result.join("");
}
方法二:
这个 repeat
函数的工作原理如下:
- 使用
new Array(num + 1)
创建一个新数组,数组的长度是num + 1
,并且数组的内容是undefined
。 - 使用
join(str)
将数组连接成一个字符串,str
作为连接的分隔符。
由于数组的内容是 undefined
,所以在连接时,str
将重复 num
次。
下面是一个例子,说明了如何使用 repeat('abc', 3)
:
- 创建一个新数组,长度为
3 + 1 = 4
,内容为[undefined, undefined, undefined, undefined]
。 - 使用
join('abc')
将数组连接成字符串。由于数组中的每个元素都是undefined
,因此在连接时不会在元素之间插入任何内容,只会插入分隔符'abc'
。所以结果是'abcabcabc'
。
总结一下,str
将重复 num
次,因为 join
方法使用 str
将 num + 1
个 undefined
元素连接在一起,因此 str
本身会重复 num
次。
//实现字符串的repeat方法
//输入字符串s,以及其重复的次数,输出重复的结果,例如输入abc,2,输出abcabc。
function repeat(str, num) {
return (new Array(num + 1).join(str));
}
12.实现字符串的reverse()方法?
方法一:
// 在字符串的原型链上添加一个方法,实现字符串的翻转:
String.prototype.myReverse = function () {
return this.split('').reverse().join("");
}
方法二:
function reverseString(str) {
let reversed = '';
for (let char of str) {
reversed = char + reversed;
}
return reversed;
}
方法三:
function reverseString(str) {
return str.split('').reduce((acc, char) => char + acc, '');
}
13. 将数字每千分位用逗号隔开
方法一:使用toLocalString()方法
function format(num) {
return num.toLocaleString(); //toLocalString() 可以将东西转换成本地习惯的方法,从而来实现千分位
}
format(12323.33) // '12,323.33'
方法二:使用split+ reduce
// 实现将带有小数点的数,千分位分割
function format(num) {
let str = num.toString(); // 转换为字符串
let newStr;
let isDecimalPoint = false; // 判断是否是带有小数点的
let secondPart = '';
if (str.indexOf('.') !== -1) {
let strs = str.split('.'); // 通过小数点去分割成两个字符串
newStr = strs[0]; // 小数点前面的字符串
secondPart = strs[1]; // 小数点后面的字符串
isDecimalPoint = true;
} else {
newStr = str;
}
console.log(newStr)
const firstPart = newStr
.split('')
.reverse()
.reduce((pre, cur, index) => (index % 3 ? cur : cur + ',') + pre);
if (isDecimalPoint) {
return firstPart + "." + secondPart;
} else {
return firstPart;
}
}
const temp = format(12323.332423) // '12,323.33'
console.log(temp)
14.实现非负大数相加?
其主要的思路如下:
- 首先用字符串的方式来保存大数,这样数字在数学表示上就不会发生变化
- 初始化res,temp来保存中间的计算结果,并将两个字符串转化为数组,以便进行每一位的加法运算
- 将两个数组的对应的位进行相加,两个数相加的结果可能大于10,所以可能要仅为,对10进行取余操作,将结果保存在当前位
- 判断当前位是否大于9,也就是是否会进位,若是则将temp赋值为true,因为在加法运算中,true会自动隐式转化为1,以便于下一次相加
- 重复上述操作,直至计算结束
//实现非负大整数相加
function sumBigNumber(a, b) {
let strA = a.split('');
let strB = b.split('');
let result = '';
let temp = 0;
console.log(strA);
while (strA.length || strB.length) {
temp = temp + Number(strA.pop() ?? 0) + Number(strB.pop() ?? 0);
result = (temp % 10) + result;
temp = temp > 9;
}
return result;
}
const temp =sumBigNumber("2134783276482364823", '21424354365476586779796847363')
console.log(temp)
15.实现 add(1)(2)(3)?
答: 方式一:暴力法:
function add(a) {
return function (b) {
return function (c) {
return a + b + c;
}
}
}
let newVar = add(1)(2)(3)
console.log(newVar)
方式二:函数柯里化
/**
* 将函数转换为其柯里化版本。
*
* @param {Function} fn - 需要柯里化的函数。
* @returns {Function} - 函数的柯里化版本。
*/
function curry(fn) {
/**
* 原始函数的柯里化版本。
*
* @param {...*} args - 传递给柯里化函数的参数。
* @returns {Function|*} - 如果提供了足够的参数,则返回原始函数的结果,否则返回期望其余参数的新函数。
*/
return function curried(...args) {
// 如果提供的参数数量大于或等于原始函数的参数数量,
// 则使用提供的参数应用原始函数。
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
// 否则,返回一个期望其余参数的新函数。
return function (...args2) {
// 使用组合的参数列表调用柯里化函数。
return curried.apply(this, args.concat(args2));
}
}
}
}
16.将类对象转换为数组的方法?
let arr = {0: '51',1: 'max', 2: 5, 3: 2, length: 4};
//使用slice()
const temp = Array.prototype.slice.call(arr, 0);
// //使用splice()
const temp1 = Array.prototype.splice.call(arr, 0)
//使用concat()
const temp2 = Array.prototype.concat.apply([], arr)
//使用Array.from()
const temp3 = Array.from(arr);
console.log(temp3)
17.如何使用reduce求和?
- arr = [1,2,3,4,5,6,7,8,9,10],求和
const arr = [1,2,3,4,5,6,7,8,9,10]
const temp = arr.reduce((pre, cur) => pre + cur, 0);
console.log(temp)
- arr = [1,2,3,[[4,5],6],7,8,9],求和
const arr = [1,2,3,[[4,5],6],7,8,9]
function sum(arr) {
// 先拍平,然后再计算或者用toString也可以
return arr.flat(Infinity).reduce((pre, cur) => pre + cur);
}
const temp = sum(arr);
console.log(temp)
- arr = [{a:1, b:3}, {a:2, b:3, c:4}, {a:3}],求和
const arr = [{a:1, b:3}, {a:2, b:3, c:4}, {a:3}]
function sum(arr) {
return arr.reduce((pre, cur) => pre + objSum(cur), 0);
}
function objSum(obj) {
let result = 0;
for (const key in obj) {
result += obj[key];
}
return result;
}
const temp = sum(arr);
console.log(temp)
18.将数组内对象转换为树形结构?
// 将js对象转化为树形结构
// 转换前:
source = [{
id: 1,
pid: 0,
name: 'body'
}, {
id: 2,
pid: 1,
name: 'title'
}, {
id: 3,
pid: 2,
name: 'div'
}]
// 转换为:
tree = [{
id: 1,
pid: 0,
name: 'body',
children: [{
id: 2,
pid: 1,
name: 'title',
children: [{
id: 3,
pid: 1,
name: 'div'
}]
}]
}]
function objToTree(data) {
let result = [];
// 先判断是否是一个数组,如果不是数组,直接返回
if (!Array.isArray(data)) {
return data;
}
let map = {};
// 将所有对象都放入到map中
data.forEach(val => map[val.id] = val);
data.forEach(val => {
// 找出当前遍历元素的父元素
let parent = map[val.pid];
if (parent) {
// 如果父元素存在,则为父元素添加children
(parent.children || (parent.children = [])).push(val);
} else {
// 不存在的话,说明是根节点
result.push(val);
}
});
return result;
}
const temp = objToTree(source);
console.log(temp)
19.使用ES5和ES6求函数参数的和
ES5中:
function sum() {
let sum = 0
Array.prototype.forEach.call(arguments, function(item) {
sum += item * 1
})
return sum
}
ES6中:
// ES6求函数的参数和
function sum(...args) {
return args.reduce((pre, cur) => pre + cur, 0);
}
20.解析 URL Params 为对象?
let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&id=378&id=556&city=%E5%8C%97%E4%BA%AC&enabled';
let url1 = 'hhttp://www.domain.com/'
const temp = parseParam(url)
console.log(temp)
/* 结果
{ user: 'anonymous',
id: [ 123, 456 ], // 重复出现的 key 要组装成数组,能被转成数字的就转成数字类型
city: '北京', // 中文需解码
enabled: true, // 未指定值得 key 约定为 true
}
*/
function parseParam(url) {
let paramStr;
// 通过字符串'?'将两个数字分割开,后面部分为参数部份
if (url.indexOf('?') !== -1) {
paramStr = url.split('?')[1];
} else {
paramStr = '';
}
if (!paramStr) {
return {};
}
// 将参数部份通过"&"分离
const paramArr = paramStr.split("&");
let result = {};
paramArr.forEach((value, index, array) => {
const keyOrValue = value.split("=");
const oldKey = keyOrValue[0];
const oldValue = keyOrValue[1];
// 对value值部分进行解码
let newValue = decodeURIComponent(oldValue ?? true);
newValue = !isNaN(newValue) ? Number(newValue) : newValue;
// 如果已经有该属性,则添加数组。
if (result.hasOwnProperty(oldKey)) {
result[oldKey] = [].concat(result[oldKey], newValue);
} else {
result[oldKey] = newValue;
}
})
return result;
}
场景应用
1. 循环打印红黄绿
下面来看一道比较典型的问题,通过这个问题来对比几种异步编程方法:红灯 3s 亮一次,绿灯 1s 亮一次,黄灯 2s 亮一次;如何让三个灯不断交替重复亮灯?
function delay(color, wait) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(color);
resolve();
}, wait);
})
}
const color = ['红灯', '绿灯', '黄灯'];
(async function func() {
while(true) {
for (let i = 0; i < color.length; i++) {
if (color[i] === '红灯') {
await delay(color[i], 3000);
}
if (color[i] === '绿灯') {
await delay(color[i], 1000);
}
if (color[i] === '黄灯') {
await delay(color[i], 2000);
}
}
}
})()
2. 实现每隔一秒打印 1,2,3,4?
答:
function delay(content) {
return new Promise((resolve, reject) => {
setTimeout(() =>{
console.log(content);
resolve();
}, 1000);
});
}
const arr = [1, 2, 3, 4];
(async function func() {
for (let i = 0; i < arr.length; i++) {
await delay(arr[i], arr[i]); //等1秒以后会继续
}
})()
3.小孩报数问题
有30个小孩儿,编号从1-30,围成一圈依此报数,1、2、3 数到 3 的小孩儿退出这个圈, 然后下一个小孩 重新报数 1、2、3,问最后剩下的那个小孩儿的编号是多少?
function childNum(num, count) {
let exitCount = 0; //离开人数
let counter = 0; // 记录报数
let curIndex = 0; // 当前下标
let allPlayer = [];
// 整一个数组,全部编号,代表学生数量,如果存的值为1,表示没有出局,如果为0,则表示出局
for (let i = 0; i < num; i++) {
allPlayer[i] = i + 1;
}
while(exitCount < num - 1) {
if (allPlayer[curIndex] !== 0) counter++;
if (counter === count) {
allPlayer[curIndex] = 0;
counter = 0;
exitCount++;
}
curIndex++;
if (curIndex === num) {
curIndex = 0;
}
}
for (let i = 0; i < num; i++) {
if (allPlayer[i] !== 0) {
return allPlayer[i];
}
}
}
const temp = childNum(30, 3)
console.log(temp)
4.用Promise实现图片的异步加载
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
<script>
let imageAsync = (url) => {
return new Promise((resolve, reject) => {
let img = new Image();
img.src = url;
img.onload = () => {
console.log("图片请求成功,此处进行通用操作")
resolve(img);
}
img.onerror = () => {
console.log("图片加载失败");
reject("加载失败")
}
})
}
imageAsync("imgSrc/test.png").then((img) => {
let element = img;
console.log(img)
element.width = 200;
element.height = 200;
document.body.appendChild(element)
console.log("加载成功");
}).catch((error) => {
console.log(error);
})
</script>
</html>
5.分片思想解决大数据量渲染问题
题目描述:渲染百万条结构简单的大数据时,怎么使用分片思想优化渲染?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<ul id="container"></ul>
</body>
<script>
let ul = document.getElementById("container");
// 插入10万条数据
let total = 100000;
// 一次插入20条
let once = 20;
// 总页数
let page = total / once;
// 每条记录的索引
let index = 0;
// 循环加载数据
function loop(curTotal, curIndex) {
if (curTotal <= 0) {
return false;
}
// 每页多少条
let pageCount = Math.min(curTotal, once);
window.requestAnimationFrame(function () {
for (let i = 0; i < pageCount; i++) {
let li = document.createElement('li');
li.innerText = curIndex + i + ':' + Math.floor(Math.random() * total);
ul.appendChild(li)
}
loop(curTotal - pageCount, curIndex + pageCount);
});
}
loop(total, index)
</script>
</html>
6.如何实现双向绑定的功能?
<body>
<div>
hello, world
<input type="text" id = "model">
<p id="word"></p>
</div>
</body>
<script type="text/javascript" src="myModule.js"></script>
<script>
const model = document.getElementById("model");
const word = document.getElementById("word");
var obj = {};
const newObj = new Proxy(obj, {
get: function (target, key, receiver) {
console.log("key===", key);
return Reflect.get(target, key, receiver);
},
set: function (target, key, value, receiver) {
console.log("set", target, key, value, receiver);
if (key === "text") {
model.value = value;
word.innerHTML = value;
}
return Reflect.set(target, key, value, receiver);
}
});
model.addEventListener("keyup", function (e) {
newObj.text = e.target.value;
})
</script>
效果:
7.输出以下代码运行结果?
// example 1
var a={}, b='123', c=123;
a[b]='b';
// c 的键名会被转换成字符串'123',这里会把 b 覆盖掉。
a[c]='c';
// 输出 c
console.log(a[b]);
---------------------------------------------------
// example 2
var a={}, b=Symbol('123'), c=Symbol('123');
// b 是 Symbol 类型,不需要转换。
a[b]='b';
// c 是 Symbol 类型,不需要转换。任何一个 Symbol 类型的值都是不相等的,所以不会覆盖掉 b。
a[c]='c';
// 输出 b
console.log(a[b]);
----------------------------------------------------
// example 3
var a={}, b={key:'123'}, c={key:'456'};
// b 不是字符串也不是 Symbol 类型,需要转换成字符串。
// 对象类型会调用 toString 方法转换成字符串 [object Object]。
a[b]='b';
// c 不是字符串也不是 Symbol 类型,需要转换成字符串。
// 对象类型会调用 toString 方法转换成字符串 [object Object]。这里会把 b 覆盖掉。
a[c]='c';
// 输出 c
console.log(a[b]);
8.如何将所有0移动到数组最后面?
说明:
- 必须在原数组上操作,不能拷贝额外的数组。
- 尽量减少操作次数。
const arr = [0, 1, 0, 3, 0, 12];
console.log(moveZero(arr));
function moveZero(arr) {
let n = 0;
arr.forEach((item, index) => {
if (item === 0) {
arr.splice(index, 1);
n++;
}
})
arr.push(...(new Array(n).fill(0)));
return arr;
}
9.如何在input搜索中实现防抖的效果?
<div>
<input type="text" id = "model">
</div>
</body>
<script type="text/javascript" src="myModule.js"></script>
<script>
let inputComp = document.getElementById("model");
inputComp.addEventListener("keyup", debounce(search, 3000)) // 这里要注意,防抖返回的就是一个函数
function search(val) {
console.log("执行搜索", val)
}
function debounce(fn, delay) {
let timer = null;
return function() {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, arguments);
timer = null;
}, delay);
}
}
10. (京东、快手)算法题之「两数之和」?
给定一个整数数组和一个目标值,找出数组中和为目标值的两个数。
你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用。
// 给定 nums = [2, 7, 11, 15], target = 9
//
// 因为 nums[0] + nums[1] = 2 + 7 = 9
// 所以返回 [0, 1]
function twoSum(arr, target) {
const map = new Map();
let result = [];
for (let i = 0; i < arr.length; i++) {
map.set(arr[i], true);
}
for (let i = 0; i < arr.length; i++) {
if (map.has(target - arr[i]) && map.get(target - arr[i])) {
result.push([arr[i], target - arr[i]]);
map.set(arr[i], false);
map.set(target - arr[i], false);
}
}
return result;
}
const temp = twoSum([2, 7, 11, 15, 4, 5], 9);
console.log(temp)
11.实现发布-订阅模式?
class EventCenter{
// 1. 定义事件容器,用来装事件数组
handlers = {};
// 2. 添加事件方法,参数:事件名 事件方法
addEventListener(type, handler) {
// 创建新数组容器
if (!this.handlers[type]) {
this.handlers[type] = []
}
// 存入事件
this.handlers[type].push(handler)
}
// 3. 触发事件,参数:事件名 事件参数
dispatchEvent(type, params) {
// 若没有注册该事件则抛出错误
if (!this.handlers[type]) {
return new Error('该事件未注册')
}
// 触发事件
this.handlers[type].forEach(handler => {
handler(...params)
})
}
// 4. 事件移除,参数:事件名 要删除事件,若无第二个参数则删除该事件的订阅和发布
removeEventListener(type, handler) {
if (!this.handlers[type]) {
return new Error('事件无效')
}
if (!handler) {
// 移除事件
delete this.handlers[type]
} else {
const index = this.handlers[type].findIndex(el => el === handler)
if (index === -1) {
return new Error('无该绑定事件')
}
// 移除事件
this.handlers[type].splice(index, 1)
if (this.handlers[type].length === 0) {
delete this.handlers[type]
}
}
}
}
const event = new EventCenter();
const sum = (a, b ,c) => {
console.log(a + b + c);
}
const sum_1 = (a, b) => {
console.log(a + b);
}
const sub = (a, b) => {
console.log(a - b);
}
event.addEventListener('sum', sum);
event.addEventListener('sum', sum_1);
event.addEventListener('sub', sub);
console.log(event.handlers);
event.dispatchEvent('sum', [1, 2, 3]);
event.removeEventListener('sum', sum)
console.log(event.handlers);
12.查找文章中出现频率最高的单词
function findMostFrequentWord(text) {
// 使用正则表达式将文本分解为单词数组
// \b:这是一个单词边界。它确保匹配的内容在单词的开始或结束位置。
//
// \w:这匹配一个单词字符。在正则表达式中,\w 是一个简写字符类,它匹配任何单词字符,等同于 [a-zA-Z0-9_]。这意味着它会匹配任何字母、数字或下划线。
//
// +:这表示前面的元素(在这里是 \w)可以出现一次或多次。因此,\w+ 会匹配一个或多个单词字符,即一个完整的单词。
//
// g:这是一个"全局"标志,它表示正则表达式应该找到所有的匹配,而不是仅仅是第一个匹配。
const words = text.toLowerCase().match(/\b\w+\b/g);
// 使用对象来存储每个单词及其出现的次数
const wordCounts = {};
// 遍历单词数组,统计每个单词的出现次数
for (let word of words) {
wordCounts[word] = (wordCounts[word] || 0) + 1;
}
// 查找出现次数最多的单词
let maxCount = 0;
let mostFrequentWord = '';
for (let word in wordCounts) {
if (wordCounts[word] > maxCount) {
maxCount = wordCounts[word];
mostFrequentWord = word;
}
}
return mostFrequentWord;
}
// 示例
const text = "This is a sample text. This text contains words. Some words appear more than once in this text.";
const result = findMostFrequentWord(text);
console.log(result); // 输出: "this"
13.封装异步的fetch,使用async await方式来使用
(async () => {
// 定义一个HttpRequestUtil类,用于处理HTTP请求
class HttpRequestUtil {
/**
* 发送GET请求
* @param {string} url - 请求的URL
* @returns {Promise<Object>} 返回的JSON数据
*/
async get(url) {
const res = await fetch(url);
const data = await res.json();
return data;
}
/**
* 发送POST请求
* @param {string} url - 请求的URL
* @param {Object} data - 要发送的数据
* @returns {Promise<Object>} 返回的JSON数据
*/
async post(url, data) {
const res = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
const result = await res.json();
return result;
}
/**
* 发送PUT请求
* @param {string} url - 请求的URL
* @param {Object} data - 要发送的数据
* @returns {Promise<Object>} 返回的JSON数据
*/
async put(url, data) {
const res = await fetch(url, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data) // 注意:这里应该使用body而不是data
});
const result = await res.json();
return result;
}
/**
* 发送DELETE请求
* @param {string} url - 请求的URL
* @param {Object} data - 要发送的数据
* @returns {Promise<Object>} 返回的JSON数据
*/
async delete(url, data) {
const res = await fetch(url, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data) // 注意:这里应该使用body而不是data
});
const result = await res.json();
return result;
}
}
// 创建HttpRequestUtil的实例
const httpRequestUtil = new HttpRequestUtil();
// 使用get方法发送请求,并打印返回的结果
const res = await httpRequestUtil.get('http://golderbrother.cn/');
console.log(res);
})();
14.实现双向数据绑定
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
</style>
</head>
<body>
<input id="input" type="text"><br>
<span id="span"></span>
</body>
<script type="text/javascript" src="myModule.js"></script>
<script>
// 创建一个空对象用于数据存储和劫持
let obj = {}
// 获取页面中 id 为 'input' 和 'span' 的元素
let input = document.getElementById('input')
let span = document.getElementById('span')
// 使用 Object.defineProperty 进行数据劫持
Object.defineProperty(obj, 'text', {
configurable: true, // 可配置,允许重新定义属性
enumerable: true, // 可枚举,允许通过 for...in 循环遍历属性
get() {
console.log('获取数据了') // 当访问 obj.text 时,输出获取数据的日志
},
set(newVal) {
console.log('数据更新了') // 当设置 obj.text 时,输出数据更新的日志
input.value = newVal // 将输入框的值设置为新值
span.innerHTML = newVal // 将 span 元素的 innerHTML 设置为新值
}
})
// 监听输入框的键盘输入事件
input.addEventListener('keyup', function(e) {
obj.text = e.target.value // 将输入框的值设置为 obj.text,触发数据劫持的 set 方法
})
</script>
</html>
15.使用setTimeout()去实现setInterval()?
思路: setInterval 的作用是每隔一段指定时间执行一个函数,但是这个执行不是真的到了时间立即执行,它真正的作用是每隔一段时间将事件加入事件队列中去,只有当当前的执行栈为空的时候,才能去从事件队列中取出事件执行。所以可能会出现这样的情况,就是当前执行栈执行的时间很长,导致事件队列里边积累多个定时器加入的事件,当执行栈结束的时候,这些事件会依次执行,因此就不能到间隔一段时间执行的效果。
针对 setInterval 的这个缺点,我们可以使用 setTimeout 递归调用来模拟 setInterval,这样我们就确保了只有一个事件结束了,我们才会触发下一个定时器事件,这样解决了 setInterval 的问题。
实现思路是使用递归函数,不断地去执行 setTimeout 从而达到 setInterval 的效果
function mySetInterval(fn, timeout) {
// 控制器,控制定时器是否继续执行
var timer = {
flag: true
};
// 设置递归函数,模拟定时器执行。
function interval() {
if (timer.flag) {
fn();
setTimeout(interval, timeout);
}
}
// 启动定时器
setTimeout(interval, timeout);
// 返回控制器
return timer;
}
let timer = mySetInterval(()=> {
console.log("111")}, 3000)
setTimeout(() => {
timer.flag = false; // 可以通过这种方式将定时器终止
}, 10000)
16. 实现 jsonp
// 动态的加载js文件
function addScript(src) {
const script = document.createElement('script');
script.src = src;
script.type = "text/javascript";
document.body.appendChild(script);
}
addScript("http://xxx.xxx.com/xxx.js?callback=handleRes");
// 设置一个全局的callback函数来接收回调结果
function handleRes(res) {
console.log(res);
}
// 接口返回的数据格式
handleRes({a: 1, b: 2});
17.判断对象是否存在循环引用
循环引用对象本来没有什么问题,但是序列化的时候就会发生问题,比如调用JSON.stringify()
对该类对象进行序列化,就会报错: Converting circular structure to JSON.
下面方法可以用来判断一个对象中是否已存在循环引用:
/**
* isCycleObject - 检查对象是否具有循环引用。
*
* @param {Object} obj - 待检查的对象。
* @param {Array} parent - 用于追踪层级结构并检测循环的父对象数组。默认为初始对象。
*
* @returns {boolean} - 如果对象包含循环引用则返回 true,否则返回 false。
*/
const isCycleObject = (obj, parent) => {
// 以提供的对象为初始父对象开始。
const parentArr = parent || [obj];
// 遍历对象中的所有属性。
for (let i in obj) {
// 如果属性是对象,则需要进一步检查。
if (typeof obj[i] === 'object') {
let flag = false;
// 检查当前对象属性是否已经在层级结构中(即在 parentArr 中)。
// 如果是,则表示有循环引用。
parentArr.forEach((pObj) => {
if (pObj === obj[i]) {
flag = true;
}
});
// 如果在此级别检测到循环,则返回 true。
if (flag) return true;
// 如果没有,则递归检查对象的属性以查找循环。
flag = isCycleObject(obj[i], [...parentArr, obj[i]]);
// 如果在更深的层级检测到循环,则返回 true。
if (flag) return true;
}
}
// 如果没有检测到循环,则返回 false。
return false;
}
// 示例使用:
const a = 1;
const b = {a};
const c = {b};
const o = {d: {a: 3}, c};
o.c.b.aa = a; // 这会创建一个循环引用。
console.log(isCycleObject(o)); // 由于循环引用,这将输出 true。
18.LazyMan函数
// LazyMan(“Hank”)
// 输出
// Hi! This is Hank!
// ---------------------------------------------
// LazyMan(“Hank”).sleep(10).eat(“dinner”)
// 输出
// Hi! This is Hank!
// 等待10秒..
// Wake up after 10
// Eat dinner~
//---------------------------------------------
// LazyMan(“Hank”).eat(“dinner”).eat(“supper”)
// 输出
// Hi This is Hank!
// Eat dinner~
// Eat supper~
//---------------------------------------------
// LazyMan(“Hank”).eat(“supper”).sleepFirst(5)
// 输出
// 等待5秒
// Wake up after 5
// Hi This is Hank!
// Eat supper
class LazyMan {
constructor(name) {
this.name = name;
this.tasks = []; // 任务队列
// 向任务队列中添加任务,用于打印"Hi This is ${name}!"
this.tasks.push(() => {
setTimeout(() => {
console.log(`Hi, This is ${name}!`);
this.next();
}, 0);
});
setTimeout(() => {
this.next();
}, 0);
}
// 执行任务队列中的下一个任务
next() {
let task = this.tasks.shift();
task && task();
}
// 向任务队列中添加任务,用于打印"Eat ${food} ~"
eat(food) {
this.tasks.push(() => {
setTimeout(() => {
console.log(`Eat ${food} ~`);
this.next();
});
});
return this;
}
// 向任务队列中添加任务,用于打印"Wake up after ${time}"
sleep(time) {
this.tasks.push(() => {
setTimeout(() => {
console.log(`Wake up after ${time}`);
this.next();
}, time * 1000);
});
return this;
}
// 向任务队列中添加任务,用于打印"Wake up after ${time}",并将该任务放到队列开头
sleepFirst(time) {
this.tasks.unshift(() => {
setTimeout(() => {
console.log(`Wake up after ${time}`);
this.next();
}, time * 1000);
});
return this;
}
}
19.# 实现一个request/Fetch最大并发控制机
function request(urls, maxNumber, callback) 要求编写函数实现,根据urls数组内的url地址进行并发网络请求,最大并发数maxNumber,当所有请求完毕后调用callback函数(已知请求网络的方法可以使用fetch api)。和上面的promise scheduler类似。
function request(urls, maxNumber, callback) {
let queue = [];
let currentJobs = 0;
let results = [];
const run = function() {
if (queue.length === 0 || currentJobs >= maxNumber) return;
currentJobs++;
const url = queue.shift();
console.log('started', url);
fetch(url)
.then(response => response.json())
.then(data => {
console.log('finshed', url);
currentJobs--;
results.push({ res: data, time: new Date() });
if (results.length === urls.length) {
callback(results);
return;
}
run();
})
.catch(error => {
console.log('finshed', url);
currentJobs--;
results.push({ error, time: new Date() });
if (results.length === urls.length) {
callback(results);
return;
}
run();
});
};
urls.forEach(url => {
queue.push(url);
run(url);
});
}
const urls = [
"https://www.badiu.com",
"https://www.badiu.com",
"https://www.badiu.com",
"https://www.badiu.com",
"https://www.badiu.com",
];
console.log(request(urls, 3, res => {
console.log(res);
}));