# 手写题
- 手写题
- call、apply、bind
- 手写一个 count 函数
- 手写一个 sleep 睡眠函数
- 手写 forEach
- 手写 filter 方法
- 手写 reduce 方法
- 用 reduce 实现 map
- 手写 find 方法
- 手写 findIndex 方法
- 实现 (5).add(3).minus(2) 功能
- ['1', '2', '3'].map(parseInt)返回值
- 手写一个 promise
- 手写Promise的静态方法
- Promisify
- Promise 实现网络超时判断
- 基于 Promise.all 实现 Ajax 的串行和并行
- 手写 Vue 双向数据绑定
- 手写虚拟 dom 过程(html 转为 json 格式)
- 手写一个 Ajax
- 实现一个红绿灯(3s 打印 red,2s 打印 green,1s 打印 yellow)
- 函数柯里化
- 手写一个单例模式
- 手写一个观察者模式
- 手写一个发布订阅模式
- 实现一个 JSON.stringify
- 图片懒加载
- 对一个页面打印所有的结点类型和结点名称
- 打印页面 HTML 种类数量
- 场景题
- JS 树(非二叉树)结构操作
- 写一个 mySetInterVal(fn, a, b),每次间隔 a,a+b,a+2b 的时间,然后写一个 myClear,停止上面的 mySetInterVal
- 实现 lodash 的 chunk 方法--数组按指定长度拆分
- 实现字符串首字母大写
- 数组转为树
- 输出结果题集
- 给定一个数组,按找到每个元素右侧第一个比它大的数字,没有的话返回-1 规则返回一个数组
- 输出一个随机的 16 进制颜色
- 设计一个函数,奇数次执行的时候打印 1,偶数次执行的时候打印 2
- 给定起始日期,输入出之前的日期
- 请写一个函数,输出出多级嵌套结构的 Object 的所有 key 值
- 写出打印结果,并解释为什么
- versions 是一个项目的版本号列表,因多人维护,不规则,动手实现一个版本号处理函数
- 实现 input 框的 autocomplete 属性
- 手写 trim()函数,去掉首尾空格
- 对输入的字符串,去除其中的字符'b'以及连续出现的'a'和'c'
- 时间复杂度为 O(n)的情况下随机不重复输出 1~100
- JavaScript:字符串内字母排序(升序和降序)
- 打印 1-100 以内的质数
- 异步调度器
- js 实现简易版模板引擎
- 给你一个连续的数字字符串,要求从尾部 3 个一个的输出,例如 12345678 输出 12,345,678
- 输出一个随机字符串
- 判断是否是电话号码
- 验证 ip 地址的合法性
- 计算字符出现次数最多的前数字之和
- 查找字符串中出现最多的字符和个数
- 将数字转为中文
- 判断循环引用
- 实现一个延时队列 Queue
- 如何做到更新路有参数,但是不刷新页面
- 编写一个阶乘
- 实现仿百度输入框,带有输入提示
- 实现一个弹窗组件
- 手撕组件(分页器,搜索框,弹窗,评分,日期选择器组件)
- 异步加载图片
- 手写倒计时
- Lodash.get
- 反转数字
- 正则提取
# 实现一个闭包并分析一段代码讲出原因
实现一个简单闭包
function sum(a) {
return function(b) {
return a + b;
};
}
var result = sum(1)(2);
console.log(result); // 3
分析代码
for (let i = 1; i < 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}
//输出 1,2,3,4
//let 绑定 for 循环,将其重新绑定到每一次的迭代中,保证每次迭代结束都会重新赋值
//有自己的作用域块,
//var 没有自己的作用域块,所以循环变量就会后一个覆盖前一个,循环完毕只有一个值输出;
for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, i * 1000);
}
// 输出 5,5,5,5
# 手写 JSONP
JSONP 函数一共需要三个参数: url,params,callback
声明一个挂在全局的函数,函数名为 callback,获取服务器的返回的
data
将
callback
和params
作为一个对象拼接参数新建
script
标签,将src
设置为拼接好的参数,然后挂在到body
上
function JSONP({ url, params, callback }) {
return new Promise((resolve, reject) => {
let script = document.createElement("script");
// 声明回调
window[callback] = function(data) {
resolve(data);
document.body.removeChild(script);
};
// 拼接参数
params = { ...params, callback };
let arr = [];
for (let key in params) {
arr.push(`${key}=${params[key]}`);
}
// 赋值src
script.src = `${url}?${arr.join("&")}`;
document.body.append(script);
});
}
// 使用实例
jsonp({
url: "http://localhost:3000/say",
params: { wd: "Iloveyou" },
callback: "show"
}).then(data => {
console.log(data);
});
# 手写 repeat 函数
function repeat(func, times, wait) {
// TODO
}
const repeatFunc = repeat(alert, 4, 3000);
// 调用这个 repeatFunc ("hellworld"),会alert4次 helloworld, 每次间隔3秒
async function sleep(fn, wait, args) {
return new Promise(resolve => {
setTimeout(() => {
fn.apply(this, args);
resolve();
}, wait);
});
}
function repeat(func, times, wait) {
return async function() {
for (let i = 0; i < times; i++) {
await sleep(func, wait, arguments);
}
};
}
var repeatFunc = repeat(alert, 4, 3000);
repeatFunc("helloworld");
# 解析 URL
protocol://username:passwrod + @ + hostname:port + pathname + search + hash
协议 + 用户名 + 用户名: 密码 + 域名 + 端口 +路径 + 参数 +哈希
https://keith:miao@www.foo.com:80/file?test=3&miao=4#heading-0
function parseUrl(url) {
const urlObj = new URL(url);
return {
protocol: urlObj.protocol,
username: urlObj.username,
password: urlObj.password,
hostname: urlObj.hostname,
port: urlObj.port,
pathname: urlObj.pathname,
search: urlObj.search,
hash: urlObj.hash
};
}
# 解析 URL 参数
//拆分字符串形式
function queryToObj() {
const res = {};
const search = location.search.substr(1); //去掉前面的“?”
search.split("&").forEach(paramStr => {
const arr = paramStr.split("=");
const key = arr[0];
const val = arr[1];
res[key] = val;
});
return res;
}
//正则形式
function queryRegExp(name) {
const search = location.search.substr(1);
const reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`, "i");
const res = search.match(reg);
if (res == null) {
return null;
}
return res[2];
}
// URLSearchParams
function queryParams() {
let search = location.search.slice(1);
let p = new URLSearchParams(search);
/**
* get(key) :获取参数值
* getAll(key): 获取所有的参数
* has(key) 检验是否有
* keys和values返回两个迭代器,可以使用for of 遍历拿到值
* */
// 解析为对象
return Object.fromEntries(p.entries());
}
# 实现驼峰转换和反转
/**
* 输入:
content-type
输出:
contentType
* */
function camel(str) {
// TODO
let ans = "";
let upper = false;
for (let index = 0; index < str.length; index++) {
const element = str[index];
if (element == "_" || element == "-" || element == "@") {
upper = true;
} else {
if (upper) {
ans += element.toUpperCase();
} else {
ans += element;
}
upper = false;
}
}
return ans;
}
// 正则
var f = function(s) {
return s.replace(/-\w/g, function(x) {
return x.slice(1).toUpperCase();
});
};
# 求数组(字符串)的交集和并集合差集
var s1 = "acacger";
var s2 = "abcgac";
var a1 = [1, 2, 3, 4, 5, 6, 6];
var a2 = [2, 3, 4];
const a = [...new Set(a1)];
const b = [...new Set(a2)];
// 交集
console.log(a.filter(item => b.includes(item)));
// 补集
console.log([...new Set([...a, ...b])]);
// 差集
console.log(a.filter(item => !b.includes(item)));
# 深层判断对象是否相同
/*
* @param x {Object} 对象1
* @param y {Object} 对象2
* @return {Boolean} true 为相等,false 为不等
*/
export const deepEqual = (x, y) => {
// 指向同一内存时
if (x === y) {
return true;
} else if (typeof x == "object" && x != null && typeof y == "object" && y != null) {
if (Object.keys(x).length !== Object.keys(y).length) {
return false;
}
for (var prop in x) {
if (y.hasOwnProperty(prop)) {
if (!deepEqual(x[prop], y[prop])) return false;
} else {
return false;
}
}
return true;
} else {
return false;
}
};
# 数组去重
function unique(arr) {
return [...new Set(arr)];
}
function unique(arr) {
// 当前值 下标 数组
return arr.filter((cur, index, array) => {
return array.indexOf(cur) === index;
});
}
function unique(arr) {
return arr.reduce((pre, cur) => {
if (!pre.includes(cur)) {
return pre.concat(cur);
} else {
return pre;
}
}, []);
}
function arrayNonRepeatfy(arr) {
let map = new Map();
let array = new Array(); // 数组用于返回结果
for (let i = 0; i < array.length; i++) {
if (map.has(array[i])) {
// 如果有该key值
map.set(array[i], true);
} else {
map.set(array[i], false); // 如果没有该key值
array.push(array[i]);
}
}
return array;
}
# 浅拷贝
只是复制栈的内存地址,如果另一个对象改变堆的值,本身会发生改变
- 引用复制
function shallowCopy(target) {
const res = {};
for (let key in target) {
res[key] = target[key];
}
return res;
}
//
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;
}
- Object.assign
var x = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
var y = Object.assign({}, x);
console.log(y.b.f === x.b.f); // true
- 利用扩展运算符
# 深拷贝
完全拷贝,互不影响
- 递归实现
const deepClone = (target = {}, map = new WeakMap()) => {
// 基本数据
if (typeof target !== "object") return target;
// Date
if (target instanceof Date) return new Date(target);
// RegExp source - 文本,flags - 标志
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp
if (target instanceof RegExp) return new RegExp(target.source, target.flags);
// function
if (target instanceof Function) {
return function() {
return target.apply(this, [...arguments]);
};
}
// 避免循环引用
if (map.has(target)) return target;
map.set(target, true);
let res = Array.isArray(target) ? [] : {};
for (let key in target) {
if (target.hasOwnProperty(key)) {
res[key] = deepClone(target[key]);
}
}
return res;
};
# JS 继承方式
# 原型链继承
此类继承方式虽然简单,但是会继承很多多余的属性
Grand.prototype.lastName = "deng";
function Grand() {}
var grand = new Grand();
Father.prototype = grand;
function Father() {
this.name = "hiehie";
}
var father = new Father();
Son.prototype = father;
function Son() {}
var son = new Son();
# 构造函数继承
这种调用父级构造函数逇方式其实和上面那个一样,虽然写法好看了,但是无法借用构造函数的原型
function Person(name, age) {
this.age = age;
this.name = name;
}
function Student(name, age, grad) {
Person.call(this, name, age);
this.grad = grad;
}
var student = new Student("1", 2, 2);
# 公有原型继承
这种方式缺点是无法新增自己的原型属性,会互相影响
Father.prototype.lastName = "李雷";
function Father() {}
function Son() {}
// 两个构造函数继承同一个原型
Son.prototype = Father.prototype;
var son = new Son();
var father = new Father();
Son.prototype.name = "lihua"; //这样会导致Father.prototype = "lihua"
# 圣杯模式继承
使用了一个中间函数做过渡,弥补前两种方式的缺点,解决了公有原型无法增加私有原型属性的缺点
function inherit(Target, Origin) {
function F() {}
F.prototype = Origin.prototype;
// 通过原型链继承 只不过经过了中间层F
Target.prototype = new F();
// son.__proto ===> newF().__proto__===>Father.prototype
// 这一步是因为原型链将son的__proto指向了Father的prototype 所以要指回去
Target.prototype.constuctor = Target;
}
# 寄生组合继承
function inheritPrototype(subType, superType) {
var prototype = Object.create(superType.prototype); // 创建对象,创建父类原型的一个副本
prototype.constructor = subType; // 增强对象,弥补因重写原型而失去的默认的constructor 属性
subType.prototype = prototype; // 指定对象,将新创建的对象赋值给子类的原型
}
- 只调用一次父类的构造函数,避免了在子类原型上创建不必要的,多余的属性
- 原型链保持不变
# 对象数组去重
const responseList = [
{ id: 1, a: 1 },
{ id: 2, a: 2 },
{ id: 3, a: 3 },
{ id: 1, a: 4 }
];
const result = responseList.reduce((acc, cur) => {
const ids = acc.map(item => item.id);
return ids.includes(cur.id) ? acc : [...acc, cur];
}, []);
console.log(result); // -> [ { id: 1, a: 1}, {id: 2, a: 2}, {id: 3, a: 3} ]
# 手写数组扁平化
function flatten(arr) {
return [].concat(
...arr.map(v => {
return Array.isArray(v) ? flatten(v) : v;
})
);
}
function flatten(arr) {
return arr.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
}, []);
}
function flat(arr, num = 1) {
return num > 0
? arr.reduce((prev, cur) => {
return prev.concat(Array.isArray(cur) ? flat(arr, num - 1) : cur);
}, [])
: arr.slice();
}
Array.prototype.flat5 = function() {
const stack = [...this];
const result = [];
while (stack.length > 0) {
const next = stack.pop();
if (Array.isArray(next)) {
stack.push(...next);
} else {
result.push(next);
}
}
// 反转恢复原来顺序
return result.reverse();
};
# 手写对象扁平化
function objectFlat(obj = {}) {
const res = {};
function flat(item, preKey = "") {
Object.entries(item).forEach(([key, val]) => {
// 如果有前一个key,则拼接当前的newKey
const newKey = preKey ? `${preKey}.${key}` : key;
if (val && typeof val === "object") {
// 递归
flat(val, newKey);
} else {
res[newKey] = val;
}
});
}
flat(obj);
return res;
}
// 测试
const source = { a: { b: { c: 1, d: 2 }, e: 3 }, f: { g: 2 } };
console.log(objectFlat(source));
# 实现防抖函数(debounce)
核心思想: 每次事件触发则删除原来的定时器,建立新的定时器。跟王者荣耀的回城功能类似,你反复触发回城功能,那么只认最后一次,从最后一次触发开始计时。
function debounce(fn, delay) {
let timer = null;
return function(...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(function() {
fn.apply(this, args);
}, delay);
};
}
适用场景:
- 按钮提交场景:防止多次提交按钮,只执行最后提交的一次
- 服务端验证场景:表单验证需要服务端配合,只执行一段连续的输入事件的最后一次,还有搜索联想词功能类似
# 实现节流函数(throttle)
节流的核心思想: 如果在定时器的时间范围内再次触发,则不予理睬,等当前定时器完成,才能启动下一个定时器任务。这就好比公交车,10 分钟一趟,10 分钟内有多少人在公交站等我不管,10 分钟一到我就要发车走人!
function throttle(fn, interval) {
let flag = true;
return function(...args) {
if (!flag) return;
flag = false;
setTimeout(() => {
fn.apply(this, args);
flag = true;
}, interval);
};
}
适用场景:
拖拽场景:固定时间内只执行一次,防止超高频次触发位置变动
缩放场景:监控浏览器 resize
动画场景:避免短时间内多次触发动画引起性能问题
# 洗牌算法和获取随机数
function getRandomInt(min, max) {
// 取min-max的数据
return Math.floor(Math.random() * (max - min + 1) + min);
}
export function shuffle(arr) {
// 当前数组值与取到的随机数组值交换 从而打乱数据
let _arr = arr.slice();
for (let i = 0; i < _arr.length; i++) {
let j = getRandomInt(0, i);
let t = _arr[i];
_arr[i] = _arr[j];
_arr[j] = t;
}
return _arr;
}
# 实现完整版 typeof
function getType(obj) {
if (obj === null) return String(obj);
return typeof obj === "object"
? Object.prototype.toString
.call(obj)
.replace("[object ", "")
.replace("]", "")
.toLowerCase()
: typeof obj;
}
// 调用
getType(null); // -> null
getType(undefined); // -> undefined
getType({}); // -> object
getType([]); // -> array
getType(123); // -> number
getType(true); // -> boolean
getType("123"); // -> string
getType(/123/); // -> regexp
getType(new Date()); // -> date
# 实现 instanceof
function instanceof(L, R) {
let O = R.prototype;
L = L.__proto__;
while (true) {
if (L === null) {
return false;
}
if (L === O) {
return true;
}
L = L.__proto__;
}
}
# 模拟 new
function MyNew(ctor, ...args) {
const obj = Object.create(ctor.prototype);
const res = ctor.apply(obj, args);
return res instanceof Object ? res : obj;
}
#
# Object.create
const myCreate = function(obj) {
function F() {}
F.prototype = obj;
return new F(); // 创建一个继承 obj 原型属性的纯净对象
};
#
# call、apply、bind
var foo = {
value: 1
};
function bar() {
console.log(this.value);
}
bar.call(foo); // 1
// 想要将bar的this指向foo,就应该将bar作为foo对象的属性,用foo去调用就可以完成赋值
// 以下context可以视为 foo,context[caller]视为bar
var foo = {
value: 1,
bar: function() {
console.log(this.value);
}
};
foo.bar(); // 1
// call
Function.prototype.call_ = function MyCall(context, ...args) {
context = context || window;
const caller = new Simbol("caller");
context[caller] = this;
const res = context[caller](...args);
delete context.caller;
return res;
};
//apply
Function.prototype.apply_ = function MyApply(context, args) {
context = context || window;
const caller = new Simbol("caller");
context[caller] = this;
const res = context[caller](...args);
delete context.caller;
return res;
};
//bind
Function.prototype.bind_ = function() {
// 转为数组
const args = Array.prototype.slice.call(arguments);
// 取出this同时取出调用参数
let t = args.shift();
// 返回的是一个函数
return function() {
this.apply(t, args);
};
};
# 手写一个 count 函数
每次调用一个函数自动加 1
count() 1
count() 2
count() 3
var count = (function() {
var a = 0;
return function() {
console.log(++a);
};
})();
count(); // 1
count(); // 2
count(); // 3
# 手写一个 sleep 睡眠函数
比如 sleep(1000)代表等待 1000ms
方法一:ES5 方式实现
function sleep(callback, time) {
if (typeof callback == "function") {
setTimeout(callback, time);
}
}
function output() {
console.log(111);
}
sleep(output, 2000);
方法二:使用 promise 方式
const sleep = time => {
return new Promise(resolve => {
setTimeout(resolve, time);
});
};
sleep(2000).then(() => {
console.log(111);
});
# 手写 forEach
forEach()方法对数组的每个元素执行一次给定的函数。
arr.forEach(function(currentValue, currentIndex, arr) {}, thisArg);
//currentValue 必需。当前元素
//currentIndex 可选。当前元素的索引
//arr 可选。当前元素所属的数组对象。
//thisArg 可选参数。当执行回调函数时,用作 this 的值。
Array.prototype._forEach = function(fn, thisArg) {
let arr = Array.prototype.slice.call(this);
for (let i = 0; i < arr.length; i++) {
fn.call(thisArg, arr[i], i, arr);
}
};
// test
let arr = [1, 2, 3, 4, 5];
arr._forEach((item, index) => {
console.log(item, index);
});
// test thisArg
# 手写 filter 方法
每次需要将符合条件的元素放到返回数组中去
Array.prototype._fillter = function(fn, thisArg) {
let arr = Array.prototype.slice.call(this);
let resArr = [];
for (let i = 0; i < arr.length; i++) {
fn.call(thisArg, arr[i], i, arr) && resArr.push(arr[i]);
}
};
# 手写 reduce 方法
Array.prototype.myReduce = function(fn, initialValue) {
var arr = Array.prototype.slice.call(this);
var res, startIndex;
res = initialValue ? initialValue : arr[0]; // 不传默认取数组第一项
startIndex = initialValue ? 0 : 1;
for (var i = startIndex; i < arr.length; i++) {
// 把初始值、当前值、索引、当前数组返回去。调用的时候传到函数参数中 [1,2,3,4].reduce((initVal,curr,index,arr))
res = fn.call(null, res, arr[i], i, this);
}
return res;
};
# 用 reduce 实现 map
reduce 是一个累加方法,是对数组累积执行回调函数,返回最终计算结果。
array.reduce(function(prev, currentValue, currentIndex, arr) {}, initialValue);
//pre 上一次回调返回值。
//currentValue 必需。当前元素
//currentIndex 可选。当前元素的索引
//arr 可选。当前元素所属的数组对象。
//initialValue可选。传递给函数的初始值
map 是遍历数组的每一项,并执行回调函数的操作,返回一个对每一项进行操作后的新数组。
array.map(function(currentValue,index,arr), thisArg);
//currentValue 必须。当前元素的值
//index 可选。当前元素的索引值
//arr 可选。当前元素属于的数组对象
//thisArg 可选。对象作为该执行回调时使用,传递给函数,用作 "this" 的值。如果省略了 thisArg,或者传入 null、undefined,那么回调函数的 this 为全局对象。
用 reduce 实现 map 方法
Array.prototype.myMap = function(fn, thisArg) {
var res = [];
thisArg = thisArg || [];
this.reduce(function(pre, cur, index, arr) {
res.push(fn.call(thisArg, cur, index, arr));
}, []);
return res;
};
var arr = [2, 3, 1, 5];
arr.myMap(function(item, index, arr) {
console.log(item, index, arr);
});
let res = arr.myMap(v => v * 2);
console.log(res); // [4,6,2,10]
ES5 实现
Array.prototype.MyMap = function(fn, context) {
// 转换类数组
var arr = Array.prototype.slice.call(this); //由于是ES5所以就不用...展开符了
var mappedArr = [];
for (var i = 0; i < arr.length; i++) {
// 把当前值、索引、当前数组返回去。调用的时候传到函数参数中 [1,2,3,4].map((curr,index,arr))
mappedArr.push(fn.call(context, arr[i], i, arr));
}
return mappedArr;
};
# 手写 find 方法
Array.prototype.myFind = function(callback) {
// var callback = function (item, index) { return item.id === 4 }
for (var i = 0; i < this.length; i++) {
if (callback(this[i], i)) {
return this[i];
}
}
};
# 手写 findIndex 方法
Array.prototype.myFindIndex = function(callback) {
// var callback = function (item, index) { return item.id === 4 }
for (var i = 0; i < this.length; i++) {
if (callback(this[i], i)) {
// 这里返回
return i;
}
}
};
# 实现 (5).add(3).minus(2) 功能
例: 5 + 3 - 2,结果为 6
Number.prototype.add = function(n) {
return this.valueOf() + n;
};
Number.prototype.minus = function(n) {
return this.valueOf() - n;
};
# ['1', '2', '3'].map(parseInt)返回值
首先返回值为: [1, NaN, NaN]
map 是传入的函数是有 3 个参数的: value, index, arr, 而 parseInt 有两个参数:
parseInt(string, radix);
string
要被解析的值。如果参数不是一个字符串,则将其转换为字符串(使用toString 抽象操作)。字符串开头的空白符将会被忽略。
radix 可选
从 2 到 36,表示字符串的基数。例如指定 16 表示被解析值是十六进制数。请注意,10不是默认值!
所以['1', '2', '3'].map(parseInt)的过程是这样子的:
parseInt("1", 0); // radix是0的情况见如下解释 ,当基数等于0时,radix被假定为10进制
parseInt("2", 1); // radix基数只能取到 2 - 36 之间,所以NaN
parseInt("3", 2); // radix=2 表示是二进制数,只能有0和1,解析的字符串是'3',所以是NaN
# 手写一个 promise
const STATUS_MAP = {
PENDING: "pending",
RESOLVED: "resolved",
REJECTED: "rejected"
};
function myPromise1(constructor) {
let self = this;
self.status = STATUS_MAP.PENDING;
self.value = undefined;
self.reason = undefined;
function resolve(value) {
if (self.status === STATUS_MAP.PENDING) {
self.value = value;
self.status = STATUS_MAP.RESOLVED;
}
}
function reject(reason) {
if (self.status === STATUS_MAP.PENDING) {
self.reason = reason;
self.status = STATUS_MAP.REJECTED;
}
}
// 捕获构造异常
try {
constructor(resolve, reject);
} catch (error) {
reject(error);
}
}
myPromise1.prototype.then = function(onFullfilled, onRejected) {
let self = this;
switch (self.status) {
case STATUS_MAP.RESOLVED:
onFullfilled(self.value);
break;
case STATUS_MAP.REJECTED:
onRejected(self.reason);
default:
break;
}
};
myPromise1.race = function(iterator) {
if (!Array.isArray(iterator)) {
return;
}
return new Promise((resolve, reject) => {
for (let i of iterator) {
Promise.resolve(i)
.then(data => resolve(data))
.catch(e => {
reject(e);
});
}
});
};
myPromise1.all = function(iterator) {
if (!Array.isArray(iterator)) {
return;
}
let res = [];
let count = 0;
return new Promise((resolve, reject) => {
for (let i of iterator) {
Promise.resolve(i)
.then(data => {
res[count++] = data;
if (count === iterator.length) {
resolve(res);
}
})
.catch(e => {
reject(e);
});
}
});
};
const test = new myPromise1((resolve, reject) => {
resolve(1);
});
test.then(res => {
console.log(res);
});
# 手写Promise
的静态方法
# race
myPromise1.race = function(iterator) {
if (!Array.isArray(iterator)) {
return;
}
return new Promise((resolve, reject) => {
for (let i of iterator) {
Promise.resolve(i)
.then(data => resolve(data))
.catch(e => {
reject(e);
});
}
});
};
# all
myPromise1.all = function(iterator) {
if (!Array.isArray(iterator)) {
return;
}
let res = [];
let count = 0;
return new Promise((resolve, reject) => {
for (let i of iterator) {
Promise.resolve(i)
.then(data => {
res[count++] = data;
if (count === iterator.length) {
resolve(res);
}
})
.catch(e => {
reject(e);
});
}
});
};
Promise.all = function(promises) {
return new Promise((resolve, reject) => {
let result = [];
let index = 0;
let len = promises.length;
if (len === 0) {
resolve(result);
return;
}
for (let i = 0; i < len; i++) {
// 为什么不直接 promise[i].then, 因为promise[i]可能不是一个promise
Promise.resolve(promise[i])
.then(data => {
result[i] = data;
index++;
if (index === len) resolve(result);
})
.catch(err => {
reject(err);
});
}
});
};
# resolve
Promise.resolve = function(value) {
return new Promise((resolve, reject) => {
resolve(value);
});
};
# reject
Promise.reject = function(reason) {
return new Promise((resolve, reject) => {
reject(reason);
});
};
# Promisify
// Promisify
function Promisify(origin) {
return function(...args) {
return new Promise((resolve, reject) => {
// args 尾部添加回调函数(参数为错误信息,values),用于改变promise状态
args.push(function callback(error, ...values) {
if (error) {
return reject(error);
}
return resolve(...values);
});
// 执行原函数
origin.call(this, ...args);
});
};
}
// callback 方式: stat(path, (err, res) => .....)
// promise 方式: promisify(stat)(path).then(res => console.log(res))
# Promise 实现网络超时判断
const uploadFile = (url, params) => {
return Promise.race([uploadFilePromise(url, params), uploadFileTimeout(3000)]);
};
function uploadFilePromise(url, params) {
return new Promise((resolve, reject) => {
axios
.post(url, params, {
headers: { "Content-Type": "multipart/form-data" }, // 以formData形式上传文件
withCredentials: true
})
.then(res => {
if (res.status === 200 && res.data.code === 0) {
resolve(res.data.result);
} else {
reject(res.data);
}
});
});
}
function uploadFileTimeout(time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject({ timeoutMsg: "上传超时" });
}, time);
});
}
function f(fn, time) {
let timeFn = new Promise((_, reject) => {
setTimeout(() => {
reject("error");
}, time);
});
return Promise.race(fn, timeFn);
}
# 基于 Promise.all 实现 Ajax 的串行和并行
# 串行
所谓串行也就是执行完一个操作之后再去执行另一个操作
const serial2 = tasks => {
let index = 0;
const resArr = [];
return new Promise((resolve, reject) => {
const doTask = () => {
fecth(tasks[index])
.then(res => {
index++;
resArr.push(res);
if (index == tasks.length) {
return resolve(resArr);
}
doTask();
})
.catch(err => {
return reject(err);
});
};
doTask();
});
};
# 并行
使用 Promise.all()
实现并行操作
var promises = function() {
return [1000, 2000, 3000].map(current => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(current);
}, current);
});
});
};
Promise.all(
promises().then(() => {
console.log("end");
})
);
实现一个批量请求函数 multiRequest(urls, maxNum),要求如下:
• 要求最大并发数 maxNum
• 每当有一个请求返回,就留下一个空位,可以增加新的请求
• 所有请求完成后,结果按照 urls 里面的顺序依次打出
使用递归调用的方式进行实现
function mutilyRequest(urls, limit) {
let length = urls.length;
let result = Array(length).fill(false);
let count = 0;
return new Promise(resolve => {
while (count < limit) {
next();
}
function next() {
let current = count++;
if (current >= length) {
!result.includes(false) && resolve(result);
return;
}
let url = urls[current];
console.log(`开始请求第${current}个接口,请求的url为${url}`);
ajax(url)
.then(data => {
result[current] = data;
console.log(`第${current}个请求已完成`);
if (current < length) {
next();
}
})
.catch(error => {
result[current] = error;
console.log(`第${current}个请求发生错误`);
if (current < length) {
next();
}
});
}
});
}
function ajax(url) {
return new Promise(resolve => {
resolve(`当前请求${url}`);
});
}
mutilyRequest(["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"], 4).then(res => {
console.log(res);
});
// 打印结果如下
// 开始请求第0个接口,请求的url为0
// 开始请求第1个接口,请求的url为1
// 开始请求第2个接口,请求的url为2
// 开始请求第3个接口,请求的url为3
// 第0个请求已完成
// 开始请求第4个接口,请求的url为4
// 第1个请求已完成
// 开始请求第5个接口,请求的url为5
// 第2个请求已完成
// 开始请求第6个接口,请求的url为6
// 第3个请求已完成
// 开始请求第7个接口,请求的url为7
// 第4个请求已完成
// 开始请求第8个接口,请求的url为8
// 第5个请求已完成
// 开始请求第9个接口,请求的url为9
// 第6个请求已完成
// 开始请求第10个接口,请求的url为10
// 第7个请求已完成
// 第8个请求已完成
// 第9个请求已完成
// 第10个请求已完成
// [
// '当前请求0', '当前请求1',
// '当前请求2', '当前请求3',
// '当前请求4', '当前请求5',
// '当前请求6', '当前请求7',
// '当前请求8', '当前请求9',
// '当前请求10'
// ]
# 手写 Vue 双向数据绑定
# Vue2 Object.definPropetry()
对象,包含复杂对象
值类型
数组,改造原型
const oldArrayPrototype = Array.prototype;
// 将ArraayObject原型指向他,然后扩展方法 即继承数组方法,不影响定义新方法
const newArrayObject = Object.create(oldArrayPrototype)[
// 扩展常用方法
("push", "shift", "unshift", "pop")
].forEach(methodName => {
// 扩展的方法
newArrayObject[methodName] = function() {
console.log("视图更新");
// 真正意义上的调用原生方法
oldArrayPrototype[methodName].call(this, ...arguments);
};
});
function observer(target) {
if (typeof target !== object || target == null) {
// 非对象或数组
return target;
}
if (target instanceof Array) {
target.__proto__ = newArrayObject;
}
for (let key in target) {
defineReactive(target, key, target[key]);
}
}
function defineReactive(data, key, value) {
// value可能是复杂对象 递归监听
observer(value);
Object.definePropetry(data, key, {
get: function() {
return value;
},
set: function(newVal) {
// 深度监听
observer(value);
if (newVal === value) {
return;
}
value = newVal;
}
});
}
const data = {
nums: [1, 2]
};
observer(data);
data.nums.push(3); // 视图更新
# Vue 3 Proxy
// 数据
const data = {
text: "default"
};
const input = document.getElementById("input");
const span = document.getElementById("span");
// 数据劫持
const handler = {
set(target, key, value) {
target[key] = value;
// 数据变化 --> 修改视图
input.value = value;
span.innerHTML = value;
return value;
}
};
const proxy = new Proxy(data, handler);
// 视图更改 --> 数据变化
input.addEventListener("keyup", function(e) {
proxy.text = e.target.value;
});
# 手写虚拟 dom 过程(html 转为 json 格式)
将 HTML 字符串去<>,处理为一个数组
提取树形结构
将树形结构转 JSON
const str1 = "<div>1<span>2<a>3</a>4</span>5<span>6<a>7</a>8<a>9</a>10</span>11</div>";
function Dom2JSON(str) {
str = str.split("<").map(x => x.split(">"));
let res = [],
stack = [],
temp = {},
cur = {},
key = 0;
// 获取树形结构
for (let i = 1; i < str.length; i++) {
if (str[i][0].indexOf("/") === -1) {
temp = {};
temp["key"] = key++;
temp["tag"] = str[i][0];
temp["value"] = str[i][1];
temp["children"] = [];
temp["parent"] = stack.length === 0 ? 0 : stack[0]["key"];
stack.unshift(temp);
} else {
cur = stack.shift();
// 当前元素为根元素时栈为空
stack.length !== 0 && (stack[0]["value"] = stack[0]["value"] + cur["value"] + str[i][1]);
res.unshift(cur);
}
}
// 使得遍历时索引与key值匹配
res = res.sort((x, y) => x["key"] - y["key"]);
for (let i = res.length - 1; i > 0; i--) {
temp = {};
temp["tag"] = res[i]["tag"];
temp["value"] = res[i]["value"];
temp["children"] = res[i]["children"];
res[res[i]["parent"]]["children"].unshift(temp);
}
res = res[0];
delete res["parent"];
delete res["key"];
return JSON.parse(JSON.stringify(res));
}
console.log(Dom2JSON(str1));
// 转换结果如下
// let res ={
// tag: "div",
// value: "1234567891011",
// children: [
// {
// tag: "span",
// value: "234",
// children: [
// {
// tag: "a",
// value: "3",
// children: [],
// }
// ],
// },
// {
// tag: "span",
// value: "678910",
// children: [
// {
// tag: "a",
// value: "7",
// children: [],
// },
// {
// tag: "a",
// value: "9",
// children: [],
// }
// ]
// }
// ]}
# 手写一个 Ajax
一般实现:
let xhr = new XMLHttpRequest();
// readyState 为 4 和 status 为 200 的时候,是正常情况
// Step1: 监听状态
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
xhr.status === 200 && console.log(xhr.responseText);
}
};
// xhr.open(method: [get, post], url: string, async: [true, false])
// async: 默认是 true; 代表异步请求
// 如果async = false, 那么 xhr.send() 会阻塞
// Step2: 打开请求
xhr.open("GET", "http://localhost:5050/search/song?key=周杰伦&page=1&limit=10&vendor=qq", true);
// Step3: 发送请求
xhr.send();
promise 封装实现:
const ajax = (url, method, async, data) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
// 已经接收到全部响应数据,而且已经可以在客户端使用了
/*0: 请求未初始化
1: 服务器连接已建立
2: 请求已接收
3: 请求处理中
4: 请求已完成,且响应已就绪*/
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(JSON.parse(xhr.responseText));
} else if (xhr.status > 400) {
reject("发生错误");
}
}
};
xhr.open(url, method, async);
xhr.send(data || null);
});
};
# 实现一个红绿灯(3s 打印 red,2s 打印 green,1s 打印 yellow)
let setColor = function(color, delay) {
return new Promise(resolve => {
setTimeout(() => {
console.log(color);
resolve();
}, delay);
});
};
async function sett() {
await setColor("red", 3000);
await setColor("green", 2000);
await setColor("yellow", 1000);
await sett();
}
sett();
function red() {
console.log("red");
}
function green() {
console.log("green");
}
function yellow() {
console.log("yellow");
}
var light = function(timmer, cb) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
cb();
resolve();
}, timmer);
});
};
var step = function() {
Promise.resolve()
.then(function() {
return light(3000, red);
})
.then(function() {
return light(2000, green);
})
.then(function() {
return light(1000, yellow);
})
.then(function() {
step();
});
};
step();
# 函数柯里化
用闭包把参数保存起来,当参数的数量足够执行函数了,就开始执行函数
function add(a, b) {
return a + b;
}
var addCurry = curry(add, 1, 2);
addCurry(); // 3
//或者
var addCurry = curry(add, 1);
addCurry(2); // 3
//或者
var addCurry = curry(add);
addCurry(1, 2); // 3
ES6 实现
function curry(fn, ...args) {
// 判断参数的长度是否已经满足函数所需参数的长度 够了就执行,否则新增参数然后返回函数
return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);
}
// test
let add = curry((a, b, c) => a + b + c);
console.log(add(1)(2)(3));
console.log(add(1, 2)(3));
console.log(add(1)(2, 3));
# 手写一个单例模式
单例模式的定义是:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
class SingleTon {
constructor(name) {
this.name = name;
this.instance = null;
}
static getInstance(name) {
// 新建对象时判断全局是否有该对象,如果有,就返回该对象,没有就创建一个新对象返回。
if (!this.instance) {
this.instance = new SingleTon(name);
}
return this.instance;
}
}
var oA = SingleTon.getInstance("Lee");
var oB = SingleTon.getInstance("Fan");
console.log(oA === oB); // true
static 关键字解释:类相当于实例的原型, 所有在类中定义的方法, 都会被实例继承。 如果在一个方法前, 加上 static 关键字, 就表示该方法不会被实例继承, 而是直接通过类来调用, 这就称为“ 静态方法”。
# 手写一个观察者模式
ES6 写法:
class Event {
constructor() {
this.events = {};
}
subscribe(eventName, callback) {
if (this.events[eventName]) {
this.events[eventName] = [callback];
} else {
this.events[eventName].push(callback);
}
}
publish(eventName) {
if (this.events[eventName]) {
this.events[eventName].forEach(callback => callback());
}
}
reomve(eventName, callback) {
if (this.events[eventName]) {
this.events[eventName] = this.events[eventName].filter(fn => fn !== callback);
}
}
reomveAll(eventName) {
if (this.events[eventName]) {
this.events[eventName] = [];
}
}
}
# 手写一个发布订阅模式
// 发布订阅模式
class EventEmitter {
constructor() {
// 事件对象,存放订阅的名字和事件
this.events = {}
}
// 订阅事件的方法
on(eventName, callback) {
if (!this.events[eventName]) {
// 注意是数据,一个名字可以订阅多个事件函数
this.events[eventName] = [callback]
} else {
// 存在则push到指定数组的尾部保存
this.events[eventName].push(callback)
}
}
// 触发事件的方法
emit(eventName) {
// 遍历执行所有订阅的事件
this.events[eventName] && this.events[eventName].forEach((cb) => cb())
}
// 移除订阅事件
removeListener(eventName, callback) {
if (this.events[eventName]) {
this.events[eventName] = this.events[eventName].filter(
(cb) => cb != callback
)
}
}
// 只执行一次订阅的事件,然后移除
once(eventName, callback) {
// 绑定的时fn, 执行的时候会触发fn函数
let fn = () => {
callback() // fn函数中调用原有的callback
this.removeListener(eventName, fn) // 删除fn, 再次执行的时候之后执行一次
}
this.on(eventName, fn)
}
}
// test
let em = new EventEmitter();
let workday = 0;
em.on("work", function() {
workday++;
console.log("work everyday");
});
em.once("love", function() {
console.log("just love you");
});
function makeMoney() {
console.log("make one million money");
}
em.on("money",makeMoney);
let time = setInterval(() => {
em.emit("work");
em.removeListener("money",makeMoney);
em.emit("money");
em.emit("love");
if (workday === 5) {
console.log("have a rest")
clearInterval(time);
}
}, 1);
// 输出
// work everyday
// just love you
// work everyday
// work everyday
// work everyday
// work everyday
// have a rest
# 实现一个 JSON.stringify
# 图片懒加载
function lazyload() {
const imgs = document.getElementsByTagName("img");
const len = imgs.length;
// 可视窗口高度
const viewHeight = document.documentElement.clientHeight;
// 滚动条高度
const scrollHeight = document.documentElement.scrollTop || document.body.scrollTop;
for (let i = 0; i < len; i++) {
// 元素距顶高度
const offsetHeight = imgs[i].offsetTop;
if (offsetHeight < viewHeight + scrollHeight) {
const src = imgs[i].dataset.src;
imgs[i].src = src;
}
}
}
// 可以使用节流优化一下
window.addEventListener("scroll", lazyload);
# 对一个页面打印所有的结点类型和结点名称
var nodes = [...document.getElementsByTagName("*")];
nodes.forEach(node => {
console.log(node.nodeType, node.nodeName);
});
# 打印页面 HTML 种类数量
function fn() {
return [...new Set([...document.getElementsByTagName("*")].map(el => el.tagName))].length;
}
# 场景题
# JS 树(非二叉树)结构操作
let tree = [
{
id: "1",
title: "节点1",
children: [
{
id: "1-1",
title: "节点1-1"
},
{
id: "1-2",
title: "节点1-2"
}
]
},
{
id: "2",
title: "节点2",
children: [
{
id: "2-1",
title: "节点2-1"
}
]
}
];
# 列表转成树
let list = [
{
id: "1",
title: "节点1",
parentId: ""
},
{
id: "1-1",
title: "节点1-1",
parentId: "1"
},
{
id: "1-2",
title: "节点1-2",
parentId: "1"
},
{
id: "2",
title: "节点2",
parentId: ""
},
{
id: "2-1",
title: "节点2-1",
parentId: "2"
}
];
function listToTree(list = []) {
let info = list.reduce((map, node) => {
map[node.id] = node;
node.children = [];
return map;
}, {});
return list.filter(node => {
if (info[node.parentId]) {
// 寻找children
info[node.parentId].children.push(node);
}
return !node.parentId;
});
}
# 树转成列表
//递归实现
function treeToList(tree, result = [], level = 0) {
tree.forEach(node => {
result.push(node);
node.level = level + 1;
node.children && treeToList(node.children, result, level + 1);
});
return result;
}
// 循环实现
function treeToList(tree) {
let node,
result = tree.map(node => ((node.level = 1), node));
for (let i = 0; i < result.length; i++) {
if (!result[i].children) continue;
let list = result[i].children.map(node => ((node.level = result[i].level + 1), node));
result.splice(i + 1, 0, ...list);
}
return result;
}
# 树结构筛选
function treeFilter(tree, func) {
// 拷贝一份树
return tree
.map(node => ({ ...node }))
.filter(node => {
// 保证子节点符合条件
node.children = node.children && treeFilter(node.children, func);
// 符合条件的返回
return func(node) || (node.children && node.children.length);
});
}
# 查找节点
function treeFind(tree, func) {
for (let node of tree) {
if (func(node)) {
return node;
}
if (node.children) {
const res = treeFind(node.children, func);
if (res) return res;
}
}
return null;
}
# 查找节点路径
function treeFindPath(tree, func, path = []) {
if (!tree) return [];
for (const data of tree) {
path.push(data.id);
if (func(data)) return path;
if (data.children) {
const findChildren = treeFindPath(data.children, func, path);
if (findChildren.length) return findChildren;
}
path.pop();
}
return [];
}
let tree = [
{
id: "1",
title: "节点1",
children: [
{
id: "1-1",
title: "节点1-1"
},
{
id: "1-2",
title: "节点1-2"
}
]
},
{
id: "2",
title: "节点2",
children: [
{
id: "2-1",
title: "节点2-1"
}
]
}
];
let list = [
{
id: "1",
title: "节点1",
parentId: ""
},
{
id: "1-1",
title: "节点1-1",
parentId: "1"
},
{
id: "1-2",
title: "节点1-2",
parentId: "1"
},
{
id: "2",
title: "节点2",
parentId: ""
},
{
id: "2-1",
title: "节点2-1",
parentId: "2"
}
];
function listToTree(list = []) {
let info = list.reduce((map, node) => {
map[node.id] = node;
node.children = [];
return map;
}, {});
return list.filter(node => {
if (info[node.parentId]) {
// 寻找children
info[node.parentId].children.push(node);
}
return !node.parentId;
});
}
// 筛选
function treeFilter(tree, func) {
// 拷贝一份树
return tree
.map(node => ({ ...node }))
.filter(node => {
// 保证子节点符合条件
node.children = node.children && treeFilter(node.children, func);
// 符合条件的返回
return func(node) || (node.children && node.children.length);
});
}
// 查找节点
function treeFind(tree, func) {
for (let node of tree) {
if (func(node)) {
return node;
}
if (node.children) {
const res = treeFind(node.children, func);
if (res) return res;
}
}
return null;
}
var filter = node => node.id === "1";
console.log(listToTree(list));
console.log(tree.map(node => ({ ...node })));
console.log(treeFilter(tree, filter));
console.log(treeFind(tree, filter));
# 写一个 mySetInterVal(fn, a, b),每次间隔 a,a+b,a+2b 的时间,然后写一个 myClear,停止上面的 mySetInterVal
function mySetInterVal(fn, a, b) {
this.a = a;
this.b = b;
this.time = 0;
this.handle = -1;
this.start = () => {
this.handle = setTimeout(() => {
fn();
this.time++;
this.start();
console.log(this.a + this.time * this.b);
}, this.a + this.time * this.b);
};
this.stop = () => {
clearTimeout(this.handle);
this.time = 0;
};
}
var a = new mySetInterVal(
() => {
console.log("123");
},
1000,
2000
);
a.start();
a.stop();
# 实现 lodash 的 chunk 方法--数组按指定长度拆分
/**
* @param input
* @param size
* @returns {Array}
*/
_.chunk(["a", "b", "c", "d"], 2);
// => [['a', 'b'], ['c', 'd']]
_.chunk(["a", "b", "c", "d"], 3);
// => [['a', 'b', 'c'], ['d']]
_.chunk(["a", "b", "c", "d"], 5);
// => [['a', 'b', 'c', 'd']]
_.chunk(["a", "b", "c", "d"], 0);
// => []
function chunk(arr, length) {
let newArr = [];
for (let i = 0; i < arr.length; i += length) {
newArr.push(arr.slice(i, i + length));
}
return newArr;
}
# 实现字符串首字母大写
"hello world".toLowerCase().replace(/( |^)[a-z]/g, str => str.toUpperCase());
// Hello World
# 数组转为树
要求将 [{id:1, parentId: 0}, {id:2, parentId:1},{id:3, parentId:1}] 输出成树结构
[{id:1, parentId: 0,children:[{id:2, parentId:1},{id:3, parentId:1}]}]
var list = [
{ id: 1, name: "部门A", parentId: 0 },
{ id: 3, name: "部门C", parentId: 1 },
{ id: 4, name: "部门D", parentId: 1 },
{ id: 5, name: "部门E", parentId: 2 },
{ id: 6, name: "部门F", parentId: 3 },
{ id: 7, name: "部门G", parentId: 2 },
{ id: 8, name: "部门H", parentId: 4 }
];
function convert(list) {
const map = list.reduce((acc, item) => {
acc[item.id] = item;
return acc;
}, {});
const result = [];
for (const key in map) {
const item = map[key];
if (item.parentId === 0) {
result.push(item);
} else {
const parent = map[item.parentId];
if (parent) {
parent.children = parent.children || [];
parent.children.push(item);
}
}
}
return result;
}
var result = convert(list);
# 输出结果题集
function Foo() {
getName = function() {
console.log(1);
};
return this;
}
var getName;
function getName() {
console.log(5);
}
Foo.getName = function() {
console.log(2);
};
Foo.prototype.getName = function() {
console.log(3);
};
getName = function() {
console.log(4);
};
Foo.getName(); // ? 2
getName(); // ? 4
Foo().getName(); // ?
getName(); // ?
new Foo.getName(); // ?
new Foo().getName(); // ?
new new Foo().getName(); // ?
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
console.log("script start");
setTimeout(function() {
console.log("setTimeout");
}, 0);
async1();
new Promise(function(resolve) {
console.log("promise1");
resolve();
}).then(function() {
console.log("promise2");
});
console.log("scripts end");
// 写出代码执行完成打印的结果
var a = {
name: "A",
fn() {
console.log(this.name);
}
};
a.fn();
a.fn.call({ name: "B" });
var fn1 = a.fn;
fn1();
// 写出打印结果
let int = 1;
setTimeout(function() {
console.log(int);
int = 2;
new Promise((resolve, reject) => {
resolve();
}).then(function() {
console.log(int);
int = 7;
});
console.log(int);
});
int = 3;
console.log(int);
new Promise((resolve, reject) => {
console.log(int);
return resolve((int = 4));
}).then(function(res) {
console.log(int);
int = 5;
setTimeout(function() {
console.log(int);
int = 8;
});
return false;
});
console.log(int);
// 写出打印结果
function a(obj) {
obj.a = 2;
obj = { a: 3 };
return obj;
}
const obj = { a: 1 };
a(obj);
console.log(obj);
Function.prototype.a = () => {
console.log(1);
};
Object.prototype.b = () => {
console.log(2);
};
function A() {}
const a = new A();
console.log(a.__proto__, A.prototype.__proto__);
a.a();
a.b();
// 写出执行结果
let a = 0;
console.log(a);
console.log(b);
let b = 0;
console.log(c);
function c() {}
// 写出执行结果
var x = 10;
function a(y) {
var x = 20;
return b(y);
}
function b(y) {
return x + y;
}
a(20);
// 写出执行结果
console.log(1);
setTimeout(() => {
console.log(2);
});
process.nextTick(() => {
console.log(3);
});
setImmediate(() => {
console.log(4);
});
new Promise(resolve => {
console.log(5);
resolve();
console.log(6);
}).then(() => {
console.log(7);
});
Promise.resolve().then(() => {
console.log(8);
process.nextTick(() => {
console.log(9);
});
});
// 写出执行结果
[1, 2, 3, 4, 5].map(parselnt);
// 写出执行结果
typeof typeof typeof [];
// 写出执行结果
function Foo() {
getName = function() {
console.log(1);
};
return this;
}
Foo.getName = function() {
console.log(2);
};
Foo.prototype.getName = function() {
console.log(3);
};
getName = function() {
console.log(4);
};
// 请写出下面的输出结果
getName();
Foo.getName();
new Foo().getName();
var fullname = "Test1";
var obj = {
fullname: "Test2",
prop: {
fullname: "Test3",
getFullname: function() {
return this.fullname;
}
}
};
console.log(obj.prop.getFullname());
var test = obj.prop.getFullname;
console.log(test());
# 给定一个数组,按找到每个元素右侧第一个比它大的数字,没有的话返回-1 规则返回一个数组
/*
*示例:
*给定数组:[2,6,3,8,10,9]
*返回数组:[6,8,8,10,-1,-1]
*/
function handler(arr = []) {
const result = [];
for (let i = 0, len = arr.length; i < len - 1; i++) {
for (let j = i + 1; j < len; j++) {
if (arr[j] > arr[i]) {
result[i] = arr[j];
break;
}
result[i] = -1;
}
}
result[arr.length - 1] = -1;
return result;
}
console.log(handler([2, 6, 3, 8, 10, 9]));
# 输出一个随机的 16 进制颜色
var color =
"#" +
Math.random()
.toString(16)
.substr(-6);
document.body.style.backgroundColor = color;
# 设计一个函数,奇数次执行的时候打印 1,偶数次执行的时候打印 2
function countFn() {
let count = 0;
return function(...args) {
count++;
if (count & (1 === 1)) return console.log(1);
console.log(2);
};
}
const testFn = countFn();
testFn(); // 1
testFn(); // 2
testFn(); // 1
testFn(); // 2
testFn(); // 1
testFn(); // 2
# 给定起始日期,输入出之前的日期
// 输入两个字符串 2018-08 2018-12
// 输出他们中间的月份 [2018-10, 2018-11]
// 给定起止日期,返回中间的所有月份
function getDate(dateStr = "", addMonth = 0) {
const [year, month] = dateStr.split("-");
return new Date(year, month - 1 + addMonth);
}
function formateDate(dateStr = new Date()) {
return `${dateStr.getFullYear()}-${String(dateStr.getMonth() + 1).padStart(2, 0)}`;
}
const getRangeMonth = (startDateStr = "", endDateStr = "") => {
const result = [];
let startTime = getDate(startDateStr) ? getDate(startDateStr, 1).getTime() : 0;
const endTime = getDate(endDateStr) ? getDate(endDateStr).getTime() : 0;
while (startTime < endTime) {
const curTime = new Date(startTime);
result.push(formateDate(curTime));
curTime.setMonth(curTime.getMonth() + 1);
startTime = curTime.getTime();
}
return result;
};
console.log(getRangeMonth("2018-08", "2018-12")); // [ '2018-09', '2018-10', '2018-11' ]
# 请写一个函数,输出出多级嵌套结构的 Object 的所有 key 值
var obj = {
a: "12",
b: "23",
first: {
c: "34",
d: "45",
second: { 3: "56", f: "67", three: { g: "78", h: "89", i: "90" } }
}
};
// => [a,b,c,d,e,f,g,h,i]
var obj = {
a: "12",
b: "23",
first: {
c: "34",
d: "45",
second: { 3: "56", f: "67", three: { g: "78", h: "89", i: "90" } }
}
};
// => [a,b,c,d,e,f,g,h,i]
function getAllKeys(obj = {}, res = []) {
if (typeof obj !== "object" || obj === null) return [];
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
if (obj[key] && typeof obj[key] === "object") {
return getAllKeys(obj[key], res);
}
res.push(key);
}
}
return res;
}
console.log(getAllKeys(obj));
# 写出打印结果,并解释为什么
var a = { k1: 1 };
var b = a;
a.k3 = a = { k2: 2 };
console.log(a);
console.log(b);
{k2:2} {k1:1,k3:{k2:2}}
第一步 a被赋值{k1:1},设为对象A
第二步 b浅拷贝a,地址不变
第三步 .运算符优先级最高,所以a:{k1:1,k3:{k2:2}},b:{k1:1,k3:{k2:2}}
第四步 a被赋值 a:{k2:2}
所以输出以上结果
# versions 是一个项目的版本号列表,因多人维护,不规则,动手实现一个版本号处理函数
var versions = ["1.45.0", "1.5", "6", "3.3.3.3.3.3.3"];
// 要求从小到大排序,注意'1.45'比'1.5'大
function sortVersion(versions) {
// TODO
}
// => ['1.5','1.45.0','3.3.3.3.3.3','6']
var versions = ["1.45.0", "1.5", "6", "3.3.3.3.3.3.3"];
// 要求从小到大排序,注意'1.45'比'1.5'大
function sortVersion(versions) {
if (!versions || !versions.length) return [];
const result = versions.sort((a, b) => {
const arrA = a.split("."),
arrB = b.split(".");
const length = Math.max(a.length, b.length);
for (let i = 0; i < length; i++) {
const x = Number(arrA[i] || 0);
const y = Number(arrB[i] || 0);
if (x - y !== 0) return x - y;
}
});
return result;
}
console.log(sortVersion(versions)); // [ '1.5', '1.45.0', '3.3.3.3.3.3.3', '6' ]
# 实现 input 框的 autocomplete 属性
window.onload = () => {
const input = document.getElementById("input");
const words = ["珠海", "广州", "上海", "杭州", "成都"];
input.addEventListener(
"input",
debounce(function(e) {
const value = e.target.value;
// 保证value只存在words里面的值
const index = words.findIndex(item => value && item.indexOf(value) !== -1);
if (index !== -1) {
e.target.value = words[index];
}
}, 500)
);
function debounce(fn, wait = 500) {
let timeout;
return function() {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => {
fn.apply(this, [...arguments]);
}, wait);
};
}
};
# 手写 trim()函数,去掉首尾空格
Function.prototype.trim = function() {
return this.replace(/^\s+/, "").replace(/\s+$/, "");
};
# 对输入的字符串,去除其中的字符'b'以及连续出现的'a'和'c'
'aacbd' -> 'ad'
'aabcd' -> 'ad'
'aaabbccc' -> ''
function fn(str) {
let res = str.replace(/b/g, "");
while (res.match(/(ac)+/)) {
res = res.replace(/ac/, "");
}
return res;
}
# 时间复杂度为 O(n)的情况下随机不重复输出 1~100
let a = [];
for (let i = 0; i < 100; i++) {
a[i] = i + 1;
}
a.sort(() => 0.5 - Math.random());
console.log(a);
# JavaScript:字符串内字母排序(升序和降序)
var s = "kadfjkajfkhgofqnmvc";
console.log(
Array.from(s)
.sort()
.join("")
); //aacdfffghjjkkkmnoqv
console.log(
Array.from(s)
.sort()
.reverse()
.join("")
);
# 打印 1-100 以内的质数
质数,大于 1 的正整数满足除自身外,无其他因数的数
function isPrimeNumber(x) {
var tmp = true;
// 因数从2开始取
for (let i = 2; i < x; i++) {
if (x % i === 0) {
tmp = false;
break;
}
}
return x > 1 && tmp;
}
let res = [];
for (let i = 1; i <= 100; i++) {
if (isPrimeNumber(i)) {
res.push(i);
}
}
console.log(res);
# 异步调度器
JS 实现一个带并发限制的异步调度器 Scheduler,保证同时运行的任务最多有两个。完善代码中 Scheduler 类,使得以下程序能正确输出。
class Scheduler {
add(promiseCreator) { ... }
// ...
}
const timeout = (time) => new Promise(resolve => {
setTimeout(resolve,time)
}
const scheduler = new Scheduler()
const addTask = (time, order) => {
scheduler.add(() => timeout(time))
.then(() => console.log(order))
}
addTask(1000, '1')
addTask(500, '2')
addTask(300, '3')
addTask(400, '4')// output: 2 3 1 4// 一开始,1、2两个任务进入队列// 500ms时,2完成,输出2,任务3进队// 800ms时,3完成,输出3,任务4进队// 1000ms时,1完成,输出1// 1200ms时,4完成,输出4
class Scheduler {
constructor() {
this.count = 0;
this.waitQueue = [];
}
add(promiseCreator) {
if (this.count < 2) {
this.count += 1;
return this.run(promiseCreator);
} else {
return new Promise(resolve => {
this.waitQueue.push(() => promiseCreator().then(resolve));
});
}
}
run(promiseCreator) {
return promiseCreator().then(() => {
this.count -= 1;
if (this.waitQueue.length) {
this.run(this.waitQueue.shift());
}
});
}
}
# js 实现简易版模板引擎
function compile(tpl, data) {
// data必须要是{name:value} 形式 ,key作为模板变量
const regex = /\{\{([^}]*)\}\}/g;
const string = tpl.trim().replace(regex, function(match, $1) {
if ($1) {
return data[$1];
} else {
return "";
}
});
return string;
}
compile(`${name}+的${box}`, { name: "杨", box: "手机" });
# 给你一个连续的数字字符串,要求从尾部 3 个一个的输出,例如 12345678 输出 12,345,678
两种方法取一即可
function localString(str) {
// 每次将模1000的余数从首部推入数组 然后再将数字除以1000向下取整
let num = Number(str);
let arr = [];
while (num > 0) {
arr.unshift(num % 1000);
num = Math.floor(num / 1000);
}
return arr.join(",");
}
let num = 12345678;
console.log(num.toLocaleString());
# 输出一个随机字符串
function randomString(length) {
var str = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
var result = "";
for (var i = length; i > 0; --i) result += str[Math.floor(Math.random() * str.length)];
return result;
}
alert(randomString(6));
# 判断是否是电话号码
function isPhone(tel) {
var regx = /^1[34578]\d{9}$/;
return regx.test(tel);
}
# 验证 ip 地址的合法性
function isIP(ip) {
var reSpaceCheck = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
if (reSpaceCheck.test(ip)) {
ip.match(reSpaceCheck);
if (
RegExp.$1 <= 255 &&
RegExp.$1 >= 0 &&
RegExp.$2 <= 255 &&
RegExp.$2 >= 0 &&
RegExp.$3 <= 255 &&
RegExp.$3 >= 0 &&
RegExp.$4 <= 255 &&
RegExp.$4 >= 0
) {
return true;
} else {
return false;
}
} else {
return false;
}
}
# 计算字符出现次数最多的前数字之和
// 给定数组 ['111a','2b','13c','5a'] ,输出出现次数最多的字母前数字之和 6。
function f(arr) {
let res = 0;
let stringArray = arr.map(item => item.replace(/\d+/, ""));
let map = new Map();
for (let item of stringArray) {
map.set(item, map.has(item) ? map.get(item) + 1 : 1);
}
let s = Array.from(map).sort((a, b) => b[1] - a[1])[0][0];
let numArray = arr.map(item => {
if (item.match(s)) {
// 取出字符串里面的数字
return parseInt(item.replace(/[^0-9]/gi, ""));
} else {
return 0;
}
});
return numArray.reduce((prev, cur) => prev + cur);
}
console.log(f(["1111a", "2b", "13c", "5a", "7a"]));
# 查找字符串中出现最多的字符和个数
let s = "abcabcabcbbccccc";
var f = str => {
let map = new Map();
for (let c of str) {
map.set(c, map.has(c) ? map.get(c) + 1 : 1);
}
for (let [key, value] of map) {
if (value === Math.max(...[...map.values()])) {
console.log(key + "=" + value);
}
}
};
f(s);
# 将数字转为中文
function transform(num) {
var cNums = "零一二三四五六七八九";
var chineseUnits = "个十百千万";
var numArr = num.toString().split("");
var lastIndex = numArr.length - 1;
var chineseStr = numArr.reduceRight((result, n, index) => {
var number = cNums[n];
var unit = index !== lastIndex && num !== 0 ? chineseUnits[lastIndex - index] : "";
return number + unit + result;
}, "");
// 最后去考虑多余的零
return chineseStr.replace(/零(?=零)|(零+$)/g, "");
}
# 判断循环引用
// 判断循环引用 循环引用代表父级子元素的值等于父级
let isCycle = false;
function cycle(obj, parent) {
// 定义父级数组
var parentArr = parent || [obj];
for (let key in obj) {
if (typeof obj[key] === "object") {
// 判断是否存在和子集相同
parentArr.forEach(item => {
if (item === obj[key]) {
obj[key] = "[cycle]";
isCycle = true;
}
});
cycle(obj[key], [...parentArr, obj[key]]);
}
}
return isCycle;
}
var a = {
b: {
c: {}
}
};
a.b.c.d = a;
console.log(cycle(a));
# 实现一个延时队列 Queue
class Queue {
constructor() {
this.event = [];
}
task(fn, time) {
function callback() {
return new Promise(resolve => {
setTimeout(() => {
fn();
resolve();
}, time);
});
}
this.event.push(callback);
// 最后返回实例
return this;
}
start() {
for (let item of this.event) {
item();
}
}
}
var queue = new Queue();
queue
.task(() => {
console.log(1);
}, 100)
.task(() => {
console.log(2);
}, 200)
.task(() => {
console.log(3);
}, 300)
.start();
# 如何做到更新路有参数,但是不刷新页面
https://developer.mozilla.org/zh-CN/docs/Web/API/History_API
if (window.history) {
history.replace(null, "", "url");
}
# 编写一个阶乘
function fnc(n) {
if (n === 1) {
return 1;
}
return n * fnc(n - 1);
}
# 实现仿百度输入框,带有输入提示
原生写法
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>Document</title>
</head>
<style>
#kw {
width: 284px;
height: 30px;
border: 2px solid #333333;
line-height: 30px;
font-size: 16px;
box-sizing: border-box;
padding: 0 5px;
}
#append {
width: 286px;
box-sizing: border-box;
border: 2px solid #333333;
border-top: 0;
display: none;
}
#append .item:hover {
background-color: aqua;
cursor: pointer;
}
.item {
padding: 3px 5px;
cursor: pointer;
}
</style>
<body>
<div id="content">
<input id="kw" onkeyup="getContent(this);" placeholder="请输入" />
<div id="append"></div>
</div>
</body>
<script>
let data = [
"你好,我是Michael",
"你是谁",
"你最好啦",
"你最珍贵",
"你是我最好的朋友",
"你画我猜",
"你是笨蛋",
"你懂得",
"你为我着迷",
"你是我的眼"
];
/**
* let xhr = new xmlHttpRequest()
* xhr.open('GET','url',true)
* xhr.onreadystatechange=()=>{
* if(xhr.readyState === 4){
* xhr.status === 200 &&& console.log(xhr.responseText)
* }
* }
* xhr.send()
*
* xhr.open('POST','url',true)
*
* xhr.setRequestHeader('Content-type','application/x-www-form-urlencoded')
*
* xhr.send('a=1&b=2')
* */
let input = document.getElementById("kw");
let append = document.getElementById("append");
function getContent(obj) {
let inputValue = obj.value.trim();
// 首先为空值的情况,append不展示
if (inputValue === "") {
append.style.display = "none";
append.innerHTML = "";
return;
}
// 有值的情况下,遍历data,如果命中里面的数据,就创建一个item的html,并且么个都绑定点击事件
let html = "";
for (let i = 0; i < data.length; i++) {
if (data[i].indexOf(inputValue)) {
html += "<div class='item' onClick='getCon(this);'>" + data[i] + "</div>";
}
}
if (html !== "") {
append.style.display = "block";
append.innerHTML = html;
}
}
function getCon(obj) {
input.value = obj.innerText;
append.style.display = "none";
append.innerHTML = "";
}
</script>
</html>
# 实现一个弹窗组件
Vue 写法
弹窗层组件分为: 遮罩层 + 内容层
<template>
<div class="modal-background" v-show="show">
<div class="modal-wrapper">
<div class="modal-header">
{{ title }}
</div>
<div class="modal-content">
<slot></slot>
</div>
<div class="modal-footer">
<button type="button" @click="confirm" class="btn confirm">确定</button>
<button type="button" @click="cancel" class="btn cancel">取消</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: "",
props: {
show: {
type: Boolean,
default: false
},
title: {
type: String,
default: "弹窗标题"
}
},
methods: {
confirm() {
this.$emit("onConfirm");
},
cancel() {
this.$emit("onCancel");
}
}
};
</script>
<style scoped>
.modal-background {
position: fixed;
left: 0;
top: 0;
bottom: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 10;
}
.modal-wrapper {
display: flex;
flex-direction: column;
width: 400px;
height: 200px;
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.modal-header {
height: 60px;
line-height: 60px;
background: #ffffff;
border-bottom: 1px solid #000000;
}
.modal-footer {
display: flex;
justify-content: center;
align-items: center;
padding-bottom: 20px;
}
.btn {
display: inline-block;
text-align: center;
vertical-align: middle;
font-size: 14px;
font-weight: 400;
padding: 12px 20px;
border: 1px solid transparent;
line-height: 1;
border-radius: 4px;
cursor: pointer;
user-select: none;
outline: none;
}
.confirm {
background: #007bff;
color: #ffffff;
border-color: #007bff;
margin-right: 15px;
}
.cancel {
border-color: #dcdfe6;
background-color: #fff;
color: #606266;
}
</style>
使用组件
# 手撕组件(分页器,搜索框,弹窗,评分,日期选择器组件)
# 异步加载图片
// 异步加载图片
function loadImage(url) {
return new Promise((resolve, reject) => {
const image = new Image();
image.src = url;
image.onload = function () {
document.body.appendChild(image);
resolve('ok');
}
})
}
let p = Promise.resolve();
const imgs = [url1, url2, url3, ..., urln];
for (let url of imgs) {
p.then(() => loadImage(url))
}
# 手写倒计时
倒计时
截止时间 2020年11月11日 0点
显示“剩余XX天XX时XX分XX秒”
每秒刷新一次
let deadline = new Date('2020/11/11 00:00:00').getTime();
window.setInterval(getDeadline, 1000);
function getDeadline() {
let cur = Date.now();
let time = deadline - cur;
let day = parseInt(time / 1000 / 3600 / 24);
let hour = parseInt((time - day * 24 * 3600 * 1000) / 1000 / 3600);
let min = parseInt((time - day * 24 * 3600 * 1000 - hour * 3600 * 1000) / 1000 / 60);
let sec = parseInt((time - day * 24 * 3600 * 1000 - hour * 3600 * 1000 - min * 60 * 1000) / 1000);
// 都显示成两位数 1 -> 01
day = ('00' + day).slice(-2);
hour = ('00' + hour).slice(-2);
min = ('00' + min).slice(-2);
sec = ('00' + sec).slice(-2);
console.log(`剩余${day}天${hour}时${min}分${sec}秒`);
}
# Lodash.get
function get(object, path, defaultValue) {
if (!Array.isArray(path) && typeof path !== "string")
throw new TypeError(`path must be array or string, but you pass the type ${typeof path}`);
// object 的类型如果是 null 或者 undefined 直接返回 default value
if (object == null) return defaultValue;
let propArray = path;
if (typeof path === "string") {
// 如果 path 是字符串需要解析成 properties 数组,这里只是简单的替换 [expression] 成 .expression 后再根据 . 拆分成特性数组
// 其实 lodash 源码比这复杂的多
path = path.replace(/\[(\w*)\]/g, ".$1");
path = path.startsWith(".") ? path.slice(1) : path;
propArray = path.split(".");
}
let index = 0;
let property;
const { length } = propArray;
// 利用特性数组循环赋值, 取到 undefined 或者 null 就停止循环
// 有可能是中途某个属性本身是 null 或 undefined,也有可能是根本没有当前 property 这个属性
while (object != null && index < length) {
property = propArray[index++];
object = object[property];
}
return index === length ? object : defaultValue;
}
# 反转数字
-321 --> -123 1200 --> 21
var f = function(str) {
if (Number(str) < 0) {
return (
"-" +
str
.split("-")[1]
.split("")
.reverse()
.join("")
.replace(/\b(0)+/g, "")
);
} else {
return str
.split("")
.reverse()
.join("")
.replace(/\b(0)+/g, "");
}
};
console.log(f("-321"), f("1200"));
# 正则提取
提取数字....value.replace(/[^\d]/g,'')
提取中文....value.replace(/[^\u4E00-\u9FA5]/g,'')
提取英文.....value.replace(/[^a-zA-Z]/g,'')