一、JavaScript 基础
1、手写 new 操作符
function mockNew(constructor, ...args) {
// 1.创建一个新对象 obj
const obj = {};
// 2.把构造函数当参数传入,新对象指向构造函数原型对象
obj.__proto__ = constructor.prototype;
// 3.通过 apply 将构建函数的 this 指向新对象
let result = constructor.apply(obj, args);
// 4.根据返回值判断
return result instanceof Object ? result : obj;
}
// 测试代码
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function() {
console.log(this.age);
};
let boy = mockNew(Person, "xiaomim", 10);
boy.say();
2、手写 Object.create
// Object.create 用于创建新对象,使用现有的对象来作为新创建对象的原型。
// 通过 object.create 创建的对象不会继承 Object.prototype 上的属性和方法
function mockCreate(obj) {
function F() {}; // 创建一个新对象
F.prototype = obj; // 将该函数的原型设置为指定的对象
return new F(); // 返回一个新对象
}
// 测试代码
let Person = {
name: 'xiaomin',
age: 10
};
let boy = mockCreate(Person);
console.log(boy); // {}
console.log(boy.name) // "xiaomin"
3、手写 instanceof
// instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
//如果检测的类型在当前实例的原型链上,则返回 true,说明这个实例属于这个类型,否则返回 false。
function mockInstanceof(left, right) {
let proto = Object.getPrototypeOf(left); // 获取对象的原型
let prototype = right.prototype; // 获取构造函数的 prototype 对象
// 判断构造函数的 prototype 对象是否在对象的原型链上
while(proto !== null) {
if (proto === prototype) return true;
proto= Object.getPrototypeOf(proto); // 继续查找原型链
}
return false;
}
// 测试代码
console.log(mockInstanceof(10, Array));
console.log(mockInstanceof([], Object));
4、手写类型判函数
function mockType(value) {
// 判断数据是 null 的情况
if (value === null) {
return value + "";
}
// 判断数据是引用类型的情况
if (typeof value === "object") {
let valueClass = Object.prototype.toString.call(value);
let type = valueClass.split(" ")[1].split("");
type.pop();
return type.join("").toLowerCase();
} else {
// 判断数据是基本数据类型的情况和函数的情况
return typeof value;
}
}
console.log(mockType({}))
5、手写节流函数
时间搓写法(立即执行)
function throttle(fn, millisecond) {
let start = 0; //先把开始时间定义为0 (第一次立刻执行)
return function () {
let end = Date.now(); //结束时间直接获取当前
if ((end - start) >= millisecond) { //结束时间-开始时间>=时间参数 (执行)
start = end; //结束时间赋值给开始时间,为一下次执行做准备
fn.apply(this, arguments); //执行函数,注意要用 apply 来改变 this 指向
}
}
}
// 测试代码
let fn = () => {
console.log("执行了");
}
setInterval(throttle(fn, 1000), 1000);
定时器写法
function throttle(fn, time) {
let isRun = false;
return function (...args) {
if (isRun) {
return;
}
isRun = true;
setTimeout(() => {
fn.apply(this, args);
isRun = false;
}, time)
}
}
// 测试代码
let fn = () => {
console.log("执行了");
}
setInterval(throttle(fn, 1000), 1000);
6、手写防抖函数
function debounce(fn, time) {
let timer = null;
return function (...args) {
if (timer) { // 如果存在定时器,则取消之前的定时器重新记时
clearTimeout(timer);
timer = null;
}
timer = setTimeout(() => { // 设置定时器
fn.apply(this, args);
}, time);
}
}
// 测试代码
let fn = () => {
console.log('fffffff');
}
setInterval(debounce(fn, 500), 1000);
7、实现深拷贝
方法一:递归实现
function deepCopy(obj) {
let res = Array.isArray(obj) ? [] : {};
for (let i in obj) {
if (obj.hasOwnProperty(i)) {
res[i] = typeof obj[i] === "object" ? deepCopy(obj[i]) : obj[i];
}
}
return res;
}
// 测试
let obj1 = { a: 1, b: { c: 1 } }
var obj2 = deepCopy(obj1);
obj1.a = 2;
obj1.b.c = 3;
console.log(obj1); // {a:2,b:{c:3}}
console.log(obj2); // {a:1,b:{c:3}}
方法二:JSON.parse(JSON.stringify()) 序列化
// 利用JSON.stringify将js对象序列化成JSON字符串,再使用JSON.parse来反序列化还原js对象。
let obj1 = {
a: 0,
b: {c: 0}
};
let obj2 = JSON.parse(JSON.stringify(obj1));
obj1.a = 1;
obj1.b.c = 1;
console.log(obj1); // {a: 1, b: {c: 1}}
console.log(obj2); // {a: 0, b: {c: 0}}
方法三:扩展运算符...
let a = {
name: '张三',
age: 18
}
let b = { ...a };
b.name = '李四';
console.log(a); // {name:'张三', age: 18}
console.log(b); // {name:'李四', age: 18},源数据不会发生改变
// 注意:扩展运算符只针对第一层,多层还是浅拷贝
8、手写浅拷贝
function shallowCopy(object) {
//只拷贝对象
if (!object || typeof object !== "object") return;
//根据 object 的类型判断是新建一个数组还是对象
let newObject = Array.isArray(object) ? [] : {};
//遍历 object,并且判断是 object 的属性才拷贝
for (let key in object) {
if (object.hasOwnProperty(key)) {
newObject[key] = object[key];
}
}
return newObject;
}
let obj1 = {a:1,b:{c:1}}
var obj2 = shallowCopy(obj1);
obj1.a = 2;
obj1.b.c = 3;
console.log(obj1); // {a:2,b:{c:3}}
console.log(obj2); // {a:1,b:{c:3}}
Object.assign 实现浅拷贝
// Object.assign 用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
let target = { a: 1 }
let obj1 = { b: 2 }
let obj2 = { c: 3 }
Object.assign(target, obj1, obj2);
console.log(target); // {a:1,b:2,c:3}
9、手写 call 函数
// 原生JS提供了call、apply、bind三种方式来修改this指向。
// call、apply会立即执行,bind返回一个新函数。
// call、apply临时改变this指向一次,bind永久改变this指向。
// apply第二个参数是数组,call和bing没有限制参数类型。
// 将myCall方法绑定到function原型链上。
Function.prototype.myCall = function(context, ...args) {
context = context || window; // context就是this要指向的对象,不设置默认window
args = args ? args : []; // args不传时默认是空数组,防止下面用spread操作符时报错
let fn = Symbol(); // 生成一个唯一值
context[fn] = this; // 把要执行的函数绑定到context的属性上
let result = context[fn](...args); // 调用的函数,this指向context
delete context[fn];
return result;
}
10、手写 apply 函数
Function.prototype.myApply = function(context, args) {
context = context || window;
args = args ? args : [];
const fn = Symbol();
context[fn] = this;
const result = context[fn](...args);
delete context[fn];
return result;
}
11、手写 bind 函数
Function.prototype.myBind = function (context) {
if (typeof this !== 'function') { //必须是函数才能调用bind
throw new Error('Function.prototype.bind - what is trying to be bound is not callable')
}
let fn = this; //先获取要执行的函数
let agrs = [...arguments].slice(1); //调用myBind时传入的参数
let fn2 = function(){} //创建空函数来中转
let bindFn = function () {
let agrs2 = [...arguments]; //调用bindFn时传入的参数
let Allagrs = agrs.concat(agrs2); //合并两组参数
//判断是new调用还是普通调用,且使用apply修改this的指向
fn.apply(this instanceof fn ? this : context, Allagrs);
}
fn2.prototype = fn.prototype;
bindFn.prototype = new fn2(); //原型链继承
return bindFn;
}
let obj = {
name: 'xiaomin'
};
function show(age, sex) {
this.name = this.name;
this.age = age;
this.sex = sex;
this.height = 176;
}
show.prototype.hobby = '打王者';
let bindFn = show.myBind(obj, 18);
let boy = new bindFn('男');
console.log(boy); //{name: undefined, age: 18, sex: "男", height: 176}
console.log(boy.hobby) //打王者
console.log(bindFn.prototype.constructor === show); //true
12、实现函数柯里化
// 将接受多个参数的函数变换成接收一个单一参数的函数,并返回一个接收剩余参数的新函数。
// 作用:参数复用、提前返回和延迟执行。
function myCurry(fn) {
// 获取函数需要的参数长度
var length = fn.length;
var args = args || [];
return function() {
var newArgs = args.concat(Array.prototype.slice.call(arguments))
// 判断参数的长度是否已经满足函数所需参数的长度
if(newArgs.length < length) {
// 如果不满足,递归返回科里化的函数,等待参数的传入
return myCurry.call(this, fn, newArgs);
} else {
// 如果满足,执行函数
return fn.apply(this, newArgs);
}
}
}
es6 实现函数柯里化
function myCurry(fn, ...args) {
return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);
}
13、实现 Promise
14、实现 Promise.all, Promise.race, Promise.any, Promise.resolve, Promise.reject
15、实现 AXAJ 请求
16、使用 Promise 封装 AJAX 函数
二、数据处理
1、删除数组的第一个元素,不改变原数组,返回一个新数组。
方法一:slice(start,end),选取数组的一部分,返回一个新数组。
var arr = [1, 2, 3, 4, 5];
var newArr = arr.slice(1);
console.log(newArr);
方法二:filter() 过滤下标,返回满足不等于0的元素。
var arr = [1, 2, 3, 4, 5];
var newArr = arr.filter((val,index,arr) => {
return index !== 0;
})
console.log(newArr);
方法三:push.apply() 合并数组 + shift() 方法
// push.apply: 合并数组,把后一个数组合并进前一个数组,使前一个数组发生改变。
// shift(): 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。
var arr = [1, 2, 3, 4, 5];
let newArr = [];
newArr.push.apply(newArr,arr);
newArr.shift();
console.log(newArr);
方法四:concat() 合并数组 + shift() 方法
// concat: 合并2个或多个数组,返回的是一个浅拷贝
var arr = [1, 2, 3, 4, 5];
let newArr = arr.concat();
newArr.shift();
console.log(newArr);
方法五:迭代拷贝
var arr = [1, 2, 3, 4, 5];
let newArr = [];
for(var i = 1; i < arr.length; i++){
newArr.push(arr[i]);
}
console.log(newArr);
2、数组去重
方法一:双重for循环
var arr = [1,3,2,4,5,5,6,6];
function noRepeat(arr){
for (var i = 0; i < arr.length; i++) {
for (var j = 0; j < arr.length; j++) {
if (arr[i] == arr[j] && i != j) {
arr.splice(j, 1);
}
}
}
return arr;
}
console.log(noRepeat(arr));
方法二:for 循环 + indexOf
var arr = [1,3,2,4,5,5,6,6];
function noRepeat(arr) {
let newArr = []
for(let i = 0;i<arr.length;i++){
newArr.indexOf(arr[i]) === -1 ? newArr.push(arr[i]) : newArr
};
return newArr
}
console.log(noRepeat(arr));
方法三:forEach + indexOf
var arr = [1,3,2,4,5,5,6,6];
function noRepeat(arr) {
let newArr = [];
arr.forEach(item=>{
newArr.indexOf(item) === -1 ? newArr.push(item) : newArr;
})
return newArr;
}
console.log(noRepeat(arr));
方法四:for 循环 + sort 排序
var arr = [1,3,2,4,5,5,6,6];
function noRepeat(arr) {
arr = arr.sort()
let newArr = []
for(let i = 0;i<arr.length;i++){
arr[i] === arr[i-1] ? newArr : newArr.push(arr[i])
};
return newArr
}
console.log(noRepeat(arr));
方法五:Set() + 扩展运算符
// ES6 提供了新的数据结构 Set,它类似于数组,成员的值都是唯一的,没有重复值。
// Set方法,返回是一个类数组,需要结合扩展运算符...转成真实数组
var arr = [1,3,2,4,5,5,6,6];
var newArr = [...new Set(arr)];
console.log(newArr);
方法六:Set() + Array.from()
var arr = [1,3,2,4,5,5,6,6];
var newArr = Array.from(new Set(arr));
console.log(newArr);
方法七: filter + indexOf
var arr = [1,3,2,4,5,5,6,6];
let newArr = arr.filter(function(item,index,arr){
return arr.indexOf(item) == index;
});
console.log(newArr);
3、数组冒泡排序
var arr = [12, 23, 44, 53, 67, 65, 78, 123, 2, 4, 7]
function fn(arr) {
for (var i = 0; i < arr.length; i++) {
for (var j = 0; j < arr.length; j++) {
if (arr[j] < arr[j + 1]) {
var test = arr[j]
arr[j] = arr[j + 1]
arr[j + 1] = test
}
}
}
}
fn(arr)
console.log(arr);
4、实现一个字符串转驼峰,列如border-bottom-color => borderBottomColor
var str = "border-bottom-color"
function toHump(str) {
var test = str.split("-")
for (var i = 1; i < test.length; i++) {
test[i] = test[i][0].toUpperCase() + test[i].slice(1)
}
return test.join("")
}
console.log(toHump(str));
5、实现一个函数计算求和,支持这两个情况:sum(2,3) 和sum(2)(3)
function sum() {
var num = arguments[0];
if (arguments.length == 1) {
return function (sec) {
console.log(num + sec);
}
} else {
for (var i = 1; i < arguments.length; i++) {
num += arguments[i]
}
console.log(num);
}
}
sum(2, 3);
sum(2)(3);
6、只用递归实现杨辉三角
1
1 2
1 2 3
.....
let num = 0;
let arr = [];
function fn(num){
if(num<=10){
num++;
arr.push(num);
console.log(...arr);
fn(num);
}
}
fn(num);
7、打印当前时间,时间格式为 yyyy-mm-dd
let dateFormat = (dateParam, format) => {
var day = dateParam.getDate();
var month = dateParam.getMonth() + 1;
var year = dateParam.getFullYear();
format = format.replace(/yyyy/, year);
format = format.replace(/MM/, month);
format = format.replace(/dd/, day);
return format;
}
// 测试代码
console.log(dateFormat(new Date('2023-10-12'), 'yyyy/MM/dd')); // 2023/10/12
console.log(dateFormat(new Date('2023-11-12'), 'yyyy-MM-dd')); // 2023-11-12
console.log(dateFormat(new Date('2023-12-12'), 'yyyy年MM月dd日')); // 2023年12月12日
9、实现日期格式化
let dateFormat = (dateParam, format)=>{
var day = dateParam.getDate()
var month = dateParam.getMonth() + 1
var year = dateParam.getFullYear()
format = format.replace(/yyyy/, year)
format = format.replace(/MM/,month)
format = format.replace(/dd/,day)
return format
}
// 测试代码
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日
8、判断字符串"xyzxxz"中出现次数最多的字符,并统计出现次数
方法一:
let str1 = "abcdefgadddaddefea"
let obj = {}
for (let i = 0; i < str1.length; i++) {
let chars = str1.charAt(i)
if (obj[chars]) {
obj[chars]++;
} else {
obj[chars] = 1
}
}
let max = 0;
let ch = ''
for (let key in obj) {
if (obj[key] > max) {
max = obj[key]
ch = key
}
}
console.log(`字符串中出现次数最多的字符是${ch},出现的次数是${max}次`)
方法二:
var str = "zasdfghiksdsdadjadjasdksadasd";
var arr = str.split(''); //字符串转成数组
var obj = {};
arr.forEach((item)=>{
if(obj[item]){
obj[item]++;
}else{
obj[item] = 1;
}
})
var a = Object.values(obj); //获取对象的key的value值
var m = Math.max(...a); //解构出最大的value值
for (key in obj){ //迭代obj对象
if (obj[key] === m ) {
console.log(`出现最多的字母是${key}最多的次数是${m}`);
}
}
9、实现数组的乱序输出
10、实现数组元素求和
(1) arr=[1,2,3,4,5,6,7,8,9,10],求和
(2) arr=[1,2,3,[[4,5],6],7,8,9],求和
(3) arr = [{a:1, b:3}, {a:2, b:3, c:4}, {a:3}],求和
11、实现数组的扁平化
12、实现对象扁平化
13、实现数组的 flat 方法
14、实现数组的 push 方法
15、实现数组的 filter 方法
16、实现数组的 map 方法
17、实现字符串的 repeat 方法
18、实现类数组转化为数组
19、 将js对象转化为树形结构
20、 解析 URL Params 为对象
三、场景
1、循环打印红黄绿
红灯 3s 亮一次,绿灯 1s 亮一次,黄灯 2s 亮一次
(1) 用 promise 实现
function red() {
console.log('red');
}
function green() {
console.log('green');
}
function yellow() {
console.log('yellow');
}
const task = (timer, light) =>
new Promise((resolve, reject) => {
setTimeout(() => {
if (light === 'red') {
red()
}
else if (light === 'green') {
green()
}
else if (light === 'yellow') {
yellow()
}
resolve()
}, timer)
})
const step = () => {
task(3000, 'red')
.then(() => task(1000, 'green'))
.then(() => task(2000, 'yellow'))
.then(step)
}
step()
(2) 用 async/await 实现
function red() {
console.log('red');
}
function green() {
console.log('green');
}
function yellow() {
console.log('yellow');
}
const task = (timer, light) =>
new Promise((resolve, reject) => {
setTimeout(() => {
if (light === 'red') {
red()
}
else if (light === 'green') {
green()
}
else if (light === 'yellow') {
yellow()
}
resolve()
}, timer)
})
const step = async () => {
await task(3000, 'red')
await task(1000, 'green')
await task(2000, 'yellow')
step()
}
step()
2、实现每隔一秒打印 1、2、3、4
(1) 使用闭包实现
for (var i = 0; i < 5; i++) {
(function(i) {
setTimeout(function() {
console.log(i);
}, i * 1000);
})(i);
}
(2) 使用 let 块级作用域实现
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, i * 1000);
}
3、实现斐波那契数列
4、使用 setTimeout 实现 setInterval
5、实现简单路由
6、实现双向数据绑定
7、实现 prototype 继承
8、实现发布-订阅模式
9、用 Promise 实现图片的异步加载
10、手写一个 jsonp
11、实现事件侦听器函数
12、实现一个事件委托
13、实现一个异步调度器
14、实现一个可以拖拽的 div
15、实现一个 sleep 函数
16、实现寄生组合继承
17、冒泡排序
18、插入排序
19、选择排序
20、快速排序
21、归并排序
22、二分法查
23、LRU 缓存淘汰算法
版权归原作者 小菜猿_ 所有, 如有侵权,请联系我们删除。