1、手写 call
- Symbol 在 Es5 的对象属性名中都是字符串,当一对象的属性名出现重复时,后者往往会覆盖前者. 若使用 Symbol 就能够保证每个属性的名字都是独一无二的,相当于生成一个唯一的标识 ID,这样就从根本上防止属性名的冲突
Function.prototype.myCall = function (context, ...args) {// 解构context 与arguments
if (typeof this !== 'function') {// this 必须是函数
throw new TypeError('must be a function');
}
if (!context) {// 没有context,或者传递的是 null undefined,则重置为window
context = window;
}
const fn = Symbol(); // 指定唯一属性,防止 delete 删除错误
context[fn] = this; // 将 this 添加到 context的属性上
const result = context[fn](...args); // 直接调用context 的 fn
delete context[fn]; // 删除掉context新增的symbol属性
return result;// 返回返回值
};
// 用法
const bar = { a: 1 };
function foo(b) {
console.log(this.a, b);
return this.a + b;
}
console.log(foo.myCall(bar, 2));
复制代码
2、手写 apply
Function.prototype.myApply = function (context, args=[]) {// 解构context 与arguments
if (typeof this !== 'function') {// this 必须是函数
throw new TypeError('must be a function');
}
if (!context) {// 没有context,或者传递的是 null undefined,则重置为window
context = window;
}
context.fn = this; // 将 this 添加到 context的属性上
const result = context.fn(...args); // 直接调用context 的 fn
delete context.fn; // 删除掉context新增的s
return result;// 返回返回值
};
// 用法
const bar = { a: 1 };
function foo(b) {
console.log(this.a, b);
return this.a + b;
}
console.log(foo.myApply(bar, [2]));
复制代码
3、手写 bind
Function.prototype.myBind = function(context,...args) {
const fn = this;
if(typeof fn !== "function"){
throw new TypeError("it must be a function")
}
if(!context){
context = window;
}
return function (...otherArgs) {
return fn.apply(context,[...args,...otherArgs]);
}
}
// 用法
const bar = {
a:1,
};
function foo(b,c,d) {
console.log(this.a,b,c,d) // 1 2 3 4
return this.a+b+c+d;
}
const fn = foo.myBind(bar,2)
console.log(fn(3,4))
复制代码
4、实现防抖
- 概念 触发事件后,在 n 秒内函数只执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间
function debounce(func, delay){
let timer;
return function() {
console.log('debounce: return: ',arguments);
clearTimeout(timer)
timer = setTimeout(()=>{
func(arguments)
},delay)
}
}
const aa = debounce((...args)=>{
console.log('debounce: 执行了',...args)
},3000);
aa('aa','bb');
复制代码
5、实现节流
function throttleOne (func, delay){
let preTime =0;
return function(){
// Date.now()要快于new.Date().getTime(),都是获取时间戳的
const nowTime = Date.now()
if(nowTime-preTime>delay){
func(arguments)
preTime=nowTime;
}
}
}
function throttleTwo (func, delay){
let isStart = true;
return function(){
if(isStart){
isStart=false
setTimeout(() => {
func(arguments)
isStart = true
}, delay);
}
}
}
// 用法
const bb = throttleTwo((...args)=>{
console.log('throttle: 执行了',...args)
},5000);
bb('aa','bb');
复制代码
6、手写 promise.all
const isPromise = value => typeof value.then === 'function';
Promise.all = function(promises) { // 全部成功才成功
return new Promise((resolve, reject) => {
// 异步 :并发 (使用for循环迭代执行) 和 串行(借助回调 第一个完成后调用第二个)
// 遍历数组 依次拿到执行结果
let arr = [];
let index = 0;
const processData = (key, data) => {
arr[key] = data; // 不能使用数组的长度来计算
if(++index === promises.length){
resolve(arr);
}
}
for (let i = 0; i < promises.length; i++) {
let result = promises[i];
if (isPromise(result)) {
result.then((data) => {
processData(i, data)
}, reject)
} else {
processData(i, result)
}
}
});
}
let p1 = new Promise((resolve, reject) => {
reject('成功了')
})
let p2 = new Promise((resolve, reject) => {
resolve('success')
})
Promise.all([p1,p2]).then((res)=>{
console.log('-result-',res)
})
复制代码
7、手写 promise.race
const isPromise = value => typeof value.then === 'function';
Promise.race = function(promises) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
let result = promises[i];
if(isPromise(result)){
result.then(resolve,reject)
}else{
resolve(result);
}
}
});
}
// p1 和p2 同上6
Promise.all([p1,p2]).then((res)=>{
console.log('result: ',res)
})
复制代码
8、手写 继承
//Object.create的理解
const arr = [{"1":1},2,3,5];
// Object.create 创建了一个新对象,这个新对象的 原型是arr
const arr3 = Object.create(arr)
// 实现一个组合集成
function Parent(){
this.name ="father";
this.age = 11;
}
Parent.prototype.say= function (){
console.log('老爸讲话了')
}
// 构造函数继承,可以传参,多继承
function Child(color){
Parent.call(this)
this.color = color;
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
/*
* 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
* Child.prototype = new Parent();
*/
/*
* Child.prototype = Parent.prototype
* 构造函数会覆盖
*/
const child1 = new Child('黄色');
const child2 = new Child('黄色');
const parent1 = new Parent();
console.log(child1.constructor)
console.log(child2.constructor)
console.log(parent1.constructor)
复制代码
9、手写 new
- a.创建一个空的简单 JavaScript 对象(即{});
- b.链接该对象(即设置该对象的构造函数)到另一个对象 ;( 通俗理解就是新对象隐式原型proto链接到构造函数显式原型 prototype 上。)
- c.将步骤 1 新创建的对象作为 this 的上下文 ;( 实际是执行了构造函数 并将构造函数作用域指向新对象 )
- d.如果该函数没有返回对象,则返回 this。( 实际是返回一个空对象, new Object()就是返回一个空对象{} )
function myNew(fn, ...args) {
let obj = Object.create(fn.prototype);
let res = fn.call(obj, ...args);
if (res && (typeof res === "object" || typeof res === "function")) {
return res;
}
return obj;
}
用法如下:
// // function Person(name, age) {
// // this.name = name;
// // this.age = age;
// // }
// // Person.prototype.say = function() {
// // console.log(this.age);
// // };
// // let p1 = myNew(Person, "lihua", 18);
// // console.log(p1.name);
// // console.log(p1);
// // p1.say();
复制代码
10、手写-setTimeout 模拟实现 setInterval
function mySetInterval(fun, time) {
let timer = null;
// 递归实现
function setIntervalMe() {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fun();
setIntervalMe(fun, time);
}, time);
}
setIntervalMe();
return () => {
// 清除,取消 重复执行
clearTimeout(timer);
};
}
// 用法
const cancel = mySetInterval(() => {
console.log('aa', new Date());
}, 2000);
setTimeout(() => {
cancel();
}, 5000);
复制代码
11、虚拟 dom 转换真实 dom 的简单实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
.app {
height: 500px;
width: 500px;
background-color: bisque;
}
</style>
</head>
<body>
<div id="root"></div>
<script>
const vnode = {
tag: 'DIV',
attrs: {
id: 'app',
class: 'app'
},
children: [
{
tag: 'DIV',
attrs: {
style: { height: '100px', width: '100px', backgroundColor: 'blue' }
},
children: [
{
tag: 'A',
attrs: {
style: { color: '#fff' }
},
children: [{ value: 'hello word' }]
}
]
},
{
tag: 'SPAN',
children: [
{ tag: 'A', children: [] },
{ tag: 'A', children: [] }
]
}
]
};
// 虚拟dom上映射着真实dom
function _render(vnode) {
if (typeof vnode.tag !== 'string') {
return document.createTextNode(vnode.value);
}
const dom = document.createElement(vnode.tag);
// 给元素添加上属性
if (vnode.attrs) {
for (let key in vnode.attrs) {
if (key === 'style') {
for (let styleName in vnode.attrs[key]) {
dom.style[styleName] = vnode.attrs[key][styleName];
}
} else {
dom.setAttribute(key, vnode.attrs[key]);
}
}
}
vnode.children &&
vnode.children.forEach(child => {
// 递归创建儿子节点,将儿子节点扔到父节点中
return dom.appendChild(_render(child));
});
// 如果不是标签就是文本
return dom;
}
const nodes = _render(vnode);
document.getElementById('root').appendChild(nodes);
</script>
</body>
</html>
复制代码
12、真实 dom 转换 虚拟 dom 简单实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="root" class="tt" style="height: 100px; width: 100px; background-color: bisque">
<div title="tt1">hello1</div>
</div>
<script>
class VNode {
constructor(tag, attrs, value, type) {
this.tag = tag && tag.toLowerCase();
this.attrs = attrs;
this.value = value;
this.type = type;
this.children = [];
}
appendChild(vnode) {
this.children.push(vnode);
}
}
function getVNode(node) {
const nodeType = node.nodeType;
let _vnode = null;
if (nodeType === 1) {
// 是元素
const nodeName = node.nodeName;
const attrs = Array.from(node.attributes); // 类数组
let _attrObj = {};
attrs &&
attrs.forEach(item => {
_attrObj[item.nodeName] = item.nodeValue;
});
_vnode = new VNode(nodeName, _attrObj, undefined, nodeType);
const childNodes = node.childNodes;
childNodes.forEach(childNode => {
_vnode.appendChild(getVNode(childNode));
});
} else if (nodeType === 3) {
// 文本节点
_vnode = new VNode(undefined, undefined, node.nodeValue, nodeType);
}
return _vnode;
}
const root = document.getElementById('root');
const vRoot = getVNode(root);
console.log(vRoot);
console.log(JSON.stringify(vRoot));
</script>
</body>
</html>
复制代码
13、手写-实现一个对象的 flatten 方法(阿里)
const obj = {
a: {
b: 1,
c: 2,
d: { e: 5 }
},
b: [1, 3, { a: 2, b: 3 }, [1, 2]],
c: 3
};
// 整体用的递归,通过对对象和数组的不同处理
let flattenObj = {};
function flatten(obj = {}, pre) {
if (typeof obj === 'object' && !Array.isArray(obj)) {// 是对象,而不是数组
for (let key in obj) {
if (typeof obj[key] !== 'object') {
flattenObj[`${pre ? pre + '.' : ''}${key || ''}`] = obj[key];
} else {
const pre1 = pre ? `${pre}.${key}` : key;
flatten(obj[key || ''], pre1);
}
}
} else if (Array.isArray(obj)) { // 数组的处理
console.log('flatten ', obj, pre);
obj.forEach((item, index) => {
const pre1 = `${pre}[${index}]`;
if (typeof item === 'object') {
flatten(item, pre1);
} else {
flattenObj[`${pre1}`] = item;
}
});
}
return flattenObj;
}
// 结果
console.log(flatten(obj));
// {
// 'a.b': 1,
// 'a.c': 2,
// 'a.d.e': 5,
// 'b[0]': 1,
// 'b[1]': 3,
// 'b[2].a': 2,
// 'b[2].b': 3,
// 'b[3][0]': 1,
// 'b[3][1]': 2,
// c: 3
// }
复制代码