前端手写题大全

93 阅读8分钟

手写题目整理

new
 function myNew(fn,...args){
   let obj=Object.create(fn.prototype) //这里已经创建了里面的函数
   fn.call(obj,...args) //初始化
   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();
call,apply,bind
 // 手写 call 函数
 Function.prototype.myCall = function(context, ...args) {
     // 将调用的函数保存为变量
     const fn = this;
     // 使用传入的 context 设置 this 值
     context = context || window;
     context.fn=fn
     // 在 context 上调用函数,并传入参数
     const result = context.fn(...args);
     // 删除添加到 context 上的函数属性
     delete context.fn;
     // 返回函数执行结果
     return result;
 };
 ​
 // 示例
 function greet(message) {
     console.log(`${message}, ${this.name}!`);
 }
 const person = { name: "Alice" };
 greet.myCall(person, "Hello"); // 输出 "Hello, Alice!"
 // 手写 apply 函数
 Function.prototype.myApply = function(context, args) {
     // 将调用的函数保存为变量
     const fn = this;
     // 使用传入的 context 设置 this 值
     context = context || window;
     context.fn=fn
     // 在 context 上调用函数,并传入参数
     const result = context.fn(...args);
     // 删除添加到 context 上的函数属性
     delete context.fn;
     // 返回函数执行结果
     return result;
 };
 ​
 // 示例
 function greet(message) {
     console.log(`${message}, ${this.name}!`);
 }
 const person = { name: "Alice" };
 greet.myApply(person, ["Hello","world"]); // 输出 "Hello, Alice!"
 // bind 函数实现
 Function.prototype.myBind=function(){
   // 用于将类数组对象 arguments 转换为真正的数组
   const args=[...arguments]
   // 取出数组的第一项 数组剩余的就是传递的参数
   const mythis=args.shift()
   const self=this
   return function(){
     //参数有两个部分 一个bind括号里面的 一个是后来括号里面的
     return self.apply(mythis,[...args,...arguments])
   }
 }
 ​
 // 示例
 function greet(message) {
     console.log(`${message}, ${this.name}!`);
 }
 const person = { name: "Alice" };
 greet.myBind(person,"Hello")(); // 输出 "Hello, Alice!"
instanceof
 function myInstanceof(left, right) {
   let proto = Object.getPrototypeOf(left), // 获取对象的原型
       prototype = right.prototype; // 获取构造函数的 prototype 对象
 ​
   // 判断构造函数的 prototype 对象是否在对象的原型链上
   while (true) {
     if (!proto) return false;
     if (proto === prototype) return true;
 ​
     proto = Object.getPrototypeOf(proto);
   }
 }
Object.is
 Object.is = function (x, y) {
   if (x === y) {
     // 当前情况下,只有一种情况是特殊的,即 +0 -0
     // 如果 x !== 0,则返回true
     // 如果 x === 0,则需要判断+0和-0,则可以直接使用 1/+0 === Infinity 和 1/-0 === -Infinity来进行判断
     return x !== 0 || 1 / x === 1 / y;
   }
 ​
   // x !== y 的情况下,只需要判断是否为NaN,如果x!==x,则说明x是NaN,同理y也一样
   // x和y同时为NaN时,返回true
   return x !== x && y !== y;
 };
 ​
大数相加
 function add(a ,b){
    //取两个数字的最大长度
    let maxLength = Math.max(a.length, b.length);
    //用0去补齐长度
    a = a.padStart(maxLength , 0);//"0009007199254740991"
    b = b.padStart(maxLength , 0);//"1234567899999999999"
    //定义加法过程中需要用到的变量
    let t = 0;
    let f = 0;   //"进位"
    let sum = "";
    for(let i=maxLength-1 ; i>=0 ; i--){
       t = parseInt(a[i]) + parseInt(b[i]) + f;
       f = Math.floor(t/10);
       sum = t%10 + sum;
    }
    if(f!==0){
       sum = '' + f + sum;
    }
    return sum;
 }
 ​
 //调用
 let a = "9007199254740991";
 let b = "1234567899999999999";
 let sum = add(a,b)
 ​
深拷贝
 // 深拷贝
 function deepClone(obj){
   if(typeof obj!=='object' || obj===null){
     return obj
   }
   let result
   if(obj instanceof Array){
     result=[]
   }else{
     result={}
   }
   for(let key in obj){
     if(obj.hasOwnProperty(key)){
       result[key]=deepClone(obj[key])
     }
   }
 }
 ​
 // JSON.stringify()
 // 这个方法可以简单粗暴的实现深拷贝,但是还存在问题,拷贝的对象中如果有函数,undefined,symbol,当使用过JSON.stringify()进行处理之后,都会消失。
 JSON.parse(JSON.stringify(obj))
深度比较
 // 手写深度比较
 function isObject(obj){
     return typeof obj === 'object' && obj !== null
 }
 ​
 function isEqual(obj1,obj2){
     if(!isObject(obj) || !isObject(obj2)){
       // 值类型(注意,参与equal的一般不会是函数)
       return obj1 ===obj2
     } 
     if(obj1 === obj2){
         return true
     }
     //两个都是对象或数组,而且不相等
     //1.先取出obj1和obj2的keys,比较个数
     const obj1Keys = Object.keys(obj1)
     const obj2Keys = Object.keys(obj2)
     if(obj1Keys.length !== obj2Keys.length){
         return false
     }
     //2.以obj1为基准,和bj2一次递归比较
     for(let key in obj1){
         //比较当前key的val --- 递归
         const res =isEqual(obj1[key],obj2[key])
         if(!res){
             return false
         }
     }
     return true
 }
函数柯里化
 // 手写柯里化函数
 function curry(fn) {
   return function curried(...args) {
     if (args.length >= fn.length) {
       return fn(...args);
     } else {
       return function (...moreArgs) {
         return curried(...args, ...moreArgs);
       };
     }
   };
 }
 ​
 // 示例函数
 function add(a, b, c) {
   return a + b + c;
 }
 ​
 // 使用柯里化函数包装示例函数
 const curriedAdd = curry(add);
 ​
 // 使用柯里化后的函数
 const result1 = curriedAdd(1)(2)(3); // 6
 const result2 = curriedAdd(1, 2)(3); // 6
 const result3 = curriedAdd(1)(2, 3); // 6
节流防抖
 //节流
 //函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。
 function throttle(func, delay) {
   let lastTime = 0;
   
   return function(...args) {
     const currentTime = Date.now();
     
     if (currentTime - lastTime >= delay) {
       func.apply(this, args);
       lastTime = currentTime;
     }
   };
 }
 ​
 //调用
 function doSomething() {
   console.log('Doing something...');
 }
 ​
 const throttledFunction = throttle(doSomething, 1000);
 ​
 // 然后在事件处理中使用throttledFunction,确保它不会被频繁调用
 document.addEventListener('scroll', throttledFunction);
 //防抖
 //函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。
 function debounce(func, delay) {
   let timeoutId;
 ​
   return function (...args) {
     clearTimeout(timeoutId);
 ​
     timeoutId = setTimeout(() => {
       func.apply(this, args);
     }, delay);
   };
 }
 ​
 //调用
 function doSomething() {
   console.log('Doing something...');
 }
 ​
 const debouncedFunction = debounce(doSomething, 1000);
 ​
 // 在事件处理中使用debouncedFunction,确保它在一段时间内只被调用一次 
 //可以用resize测试
 window.addEventListener('scroll', debouncedFunction);
 ​
settimeout 模拟实现 setinterval
 function simulateInterval(func, delay) {
     let timer=null
     function interval() {
         func();
         setTimeout(interval, delay);
     }
     timer=setTimeout(interval, delay);
     return {
         cancel:()=>clearTimeout(timer)
     }
 }
 ​
 let a=simulateInterval(function() {
     console.log("This will run every 1000ms");
 }, 1000);
 a.cancel()  //清除定时器
 ​
原生 XMLHttpRequest
 const SERVER_URL = "/server";
 let xhr = new XMLHttpRequest();
 // 创建 Http 请求
 xhr.open("GET", SERVER_URL, true);
 // 设置状态监听函数
 xhr.onreadystatechange = function() {
   if (this.readyState !== 4) return;
   // 当请求成功时
   if (this.status === 200) {
     handle(this.response);
   } else {
     console.error(this.statusText);
   }
 };
 // 设置请求失败时的监听函数
 xhr.onerror = function() {
   console.error(this.statusText);
 };
 // 设置请求头信息
 xhr.responseType = "json";
 xhr.setRequestHeader("Accept", "application/json");
 // 发送 Http 请求
 xhr.send(null);
 ​
Ajax
 const getJSON = function (url) {
   return new Promise((resolve, reject) => {
     const xhr = new XMLHttpRequest();
     xhr.open("GET", url, false);
     xhr.setRequestHeader("Content-Type", "application/json");
     xhr.onreadystatechange = function () {
       if (xhr.readyState !== 4) return;
       if (xhr.status === 200 || xhr.status === 304) {
         resolve(xhr.responseText);
       } else {
         reject(new Error(xhr.responseText));
       }
     };
     xhr.send();
   });
 };
 ​
订阅发布模式
 // 发布者对象
 function Publisher() {
   this.subscribers = []; // 存储订阅者的数组
 }
 ​
 // 添加订阅者
 Publisher.prototype.subscribe = function (subscriber) {
   this.subscribers.push(subscriber);
 };
 ​
 // 移除订阅者
 Publisher.prototype.unsubscribe = function (subscriber) {
   const index = this.subscribers.indexOf(subscriber);
   if (index !== -1) {
     this.subscribers.splice(index, 1);
   }
 };
 ​
 // 发布消息给所有订阅者
 Publisher.prototype.notify = function (message) {
   this.subscribers.forEach(function (subscriber) {
     subscriber.update(message);
   });
 };
 ​
 // 订阅者对象
 function Subscriber(name) {
   this.name = name;
 }
 ​
 // 订阅者的更新方法
 Subscriber.prototype.update = function (message) {
   console.log(`${this.name} 收到消息:${message}`);
 };
 ​
 // 创建发布者对象
 const newsPublisher = new Publisher();
 ​
 // 创建订阅者对象
 const subscriber1 = new Subscriber('订阅者1');
 const subscriber2 = new Subscriber('订阅者2');
 ​
 // 订阅者订阅发布者
 newsPublisher.subscribe(subscriber1);
 newsPublisher.subscribe(subscriber2);
 ​
 // 发布者发布消息
 newsPublisher.notify('新闻更新:重要通知!');
 ​
 // 输出:
 // 订阅者1 收到消息:新闻更新:重要通知!
 // 订阅者2 收到消息:新闻更新:重要通知!
 ​
 ​
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就是表示回调函数
 // 设置一个全局的callback函数来接收回调结果
 function handleRes(res) {
   console.log(res);
 }
是否有循环引用
 const isCycleObject = (obj, parent) => {
   // parent 参数用于追踪当前对象的祖先对象,初始值为 [obj],用于存储对象的引用链
   const parentArr = parent || [obj];
   
   // 遍历对象的属性
   for (let i in obj) {
     if (typeof obj[i] === 'object') {
       let flag = false;
       
       // 检查当前属性值是否已经在祖先链中存在
       parentArr.forEach((pObj) => {
         if (pObj === obj[i]) {
           flag = true;
         }
       });
       
       // 如果发现当前属性值在祖先链中存在,返回 true 表示存在循环引用
       if (flag) return true;
       
       // 递归调用 isCycleObject 检查当前属性值的子属性,传递更新的祖先链
       flag = isCycleObject(obj[i], [...parentArr, obj[i]]);
       
       // 如果在子属性中发现循环引用,返回 true
       if (flag) return true;
     }
   }
   
   // 如果没有发现循环引用,返回 false
   return false;
 };
 ​
 ​
 //调用
 // 创建一个对象 person1
 const person1 = {
   name: "Alice"
 };
 ​
 // 创建另一个对象 person2 并将 person1 作为属性引用
 const person2 = {
   name: "Bob",
   friend: person1
 };
 ​
 // 将 person2 作为 person1 的 friend 属性引用
 person1.friend = person2;
 ​
 // 现在,person1 和 person2 彼此引用,形成了循环引用
 ​
 console.log(isCycleObject(person1))
继承手写
 function Parent(name) {
   this.name = name;
   this.say = () => {
     console.log(111);
   };
 }
 Parent.prototype.play = () => {
   console.log(222);
 };
 function Children(name) {
   Parent.call(this);
   this.name = name;
 }
 Children.prototype = Object.create(Parent.prototype);  //这个是正规写法 不正规写法是子类显式原型指向父类实例(这样会直接继承那个实例的属性)
 Children.prototype.constructor = Children;  //不是必须的 是为了纠正原型链中的构造函数引用,以确保对象的 constructor 属性指向正确的构造函数。
 // let child = new Children("111");
 // // console.log(child.name);
 // // child.say();
 // // child.play();
 ​

数组

数组扁平化
 function flatter(arr) {
   if (!arr.length) return;
   return arr.reduce(
     (pre, cur) => {
       Array.isArray(cur) ? [...pre, ...flatter(cur)] : [...pre, cur]
     },[]
   );
 }
 // console.log(flatter([1, 2, [1, [2, 3, [4, 5, [6]]]]]));
 ​
 ​
 arr.flat(Infinity)  //直接扁平化
对象 flatten
 const obj = {
  a: {
         b: 1,
         c: 2,
         d: {e: 5}
     },
  b: [1, 3, {a: 2, b: 3}],
  c: 3
 }
 ​
 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
 //   c: 3
 // }
 ​
 function isObject(val) {
   return typeof val === "object" && val !== null;
 }
 ​
 function flatten(obj) {
   if (!isObject(obj)) {
     return;
   }
   let res = {};
   const dfs = (cur, prefix) => {
     if (isObject(cur)) {
       if (Array.isArray(cur)) {
         cur.forEach((item, index) => {
           dfs(item, `${prefix}[${index}]`);
         });
       } else {
         for (let k in cur) {
           dfs(cur[k], `${prefix}${prefix ? "." : ""}${k}`);
         }
       }
     } else {
       res[prefix] = cur;
     }
   };
   dfs(obj, "");
 ​
   return res;
 }
 flatten();
 ​
数组去重
 const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
 ​
 let res = Array.from(new Set(array)); // [1, 2, 3, 5, 9, 8]
列表转成树形结构
 function listToTree(data) {
   let temp = {};
   let treeData = [];
   for (let i = 0; i < data.length; i++) {
     temp[data[i].id] = data[i];
   }
   for (let i in temp) {
     if (+temp[i].parentId != 0) {
       if (!temp[temp[i].parentId].children) {
         temp[temp[i].parentId].children = [];
       }
       temp[temp[i].parentId].children.push(temp[i]);
     } else {
       treeData.push(temp[i]);
     }
   }
   return treeData;
 }
 ​
 [
     {
         id: 1,
         text: '节点1',
         parentId: 0 //这里用0表示为顶级节点
     },
     {
         id: 2,
         text: '节点1_1',
         parentId: 1 //通过这个字段来确定子父级
     }
     ...
 ]
 
 转成
 [
     {
         id: 1,
         text: '节点1',
         parentId: 0,
         children: [
             {
                 id:2,
                 text: '节点1_1',
                 parentId:1
             }
         ]
     }
 ]
 
树形结构转成列表
 function treeToList(data) {
   let res = [];
   const dfs = (tree) => {
     tree.forEach((item) => {
       if (item.children) {
         dfs(item.children);
         delete item.children;
       }
       res.push(item);
     });
   };
   dfs(data);
   return res;
 }
 ​
 [
     {
         id: 1,
         text: '节点1',
         parentId: 0,
         children: [
             {
                 id:2,
                 text: '节点1_1',
                 parentId:1
             }
         ]
     }
 ]
 转成
 [
     {
         id: 1,
         text: '节点1',
         parentId: 0 //这里用0表示为顶级节点
     },
     {
         id: 2,
         text: '节点1_1',
         parentId: 1 //通过这个字段来确定子父级
     }
     ...
 ]
 
 
排序
冒泡排序
 function bubbleSort(arr) {
   // 缓存数组长度
   const len = arr.length;
   // 外层循环用于控制从头到尾的比较+交换到底有多少轮
   for (let i = 0; i < len; i++) {
     // 内层循环用于完成每一轮遍历过程中的重复比较+交换
     for (let j = 0; j < len - 1; j++) {
       // 若相邻元素前面的数比后面的大
       if (arr[j] > arr[j + 1]) {
         // 交换两者
         [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
       }
     }
   }
   // 返回数组
   return arr;
 }
 // console.log(bubbleSort([3, 6, 2, 4, 1]));
 ​
选择排序
 function selectSort(arr) {
   // 缓存数组长度
   const len = arr.length;
   // 定义 minIndex,缓存当前区间最小值的索引,注意是索引
   let minIndex;
   // i 是当前排序区间的起点
   for (let i = 0; i < len - 1; i++) {
     // 初始化 minIndex 为当前区间第一个元素
     minIndex = i;
     // i、j分别定义当前区间的上下界,i是左边界,j是右边界
     for (let j = i; j < len; j++) {
       // 若 j 处的数据项比当前最小值还要小,则更新最小值索引为 j
       if (arr[j] < arr[minIndex]) {
         minIndex = j;
       }
     }
     // 如果 minIndex 对应元素不是目前的头部元素,则交换两者
     if (minIndex !== i) {
       [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
     }
   }
   return arr;
 }
 // console.log(quickSort([3, 6, 2, 4, 1]));
 ​
插入排序
 function insertSort(arr) {
   for (let i = 1; i < arr.length; i++) {
     let j = i;
     let target = arr[j];
     while (j > 0 && arr[j - 1] > target) {
       arr[j] = arr[j - 1];
       j--;
     }
     arr[j] = target;
   }
   return arr;
 }
 // console.log(insertSort([3, 6, 2, 4, 1]));
 ​
快排
 function quickSort(arr) {
   if (arr.length < 2) {
     return arr;
   }
   const cur = arr[arr.length - 1];
   const left = arr.filter((v, i) => v <= cur && i !== arr.length - 1);
   const right = arr.filter((v) => v > cur);
   return [...quickSort(left), cur, ...quickSort(right)];
 }
 // console.log(quickSort([3, 6, 2, 4, 1]));
 ​
归并排序
 function merge(left, right) {
   let res = [];
   let i = 0;
   let j = 0;
   while (i < left.length && j < right.length) {
     if (left[i] < right[j]) {
       res.push(left[i]);
       i++;
     } else {
       res.push(right[j]);
       j++;
     }
   }
   if (i < left.length) {
     res.push(...left.slice(i));
   } else {
     res.push(...right.slice(j));
   }
   return res;
 }
 ​
 function mergeSort(arr) {
   if (arr.length < 2) {
     return arr;
   }
   const mid = Math.floor(arr.length / 2);
 ​
   const left = mergeSort(arr.slice(0, mid));
   const right = mergeSort(arr.slice(mid));
   return merge(left, right);
 }
 // console.log(mergeSort([3, 6, 2, 4, 1]));
 ​

Promise

手写Promise
 //手写promise 
 class myPromise{
   static PENDING="待定"; static RESOLVED="解决"; static REJECTED="拒绝"
   constructor(fn){
     this.state=myPromise.PENDING
     this.result=null
     try{
       fn(this.resolve.bind(this),this.reject.bind(this))  //这里要捕获一下错误
     }catch(error){
       // console.log(error)
       this.state=myPromise.REJECTED
       this.result=error
     }
     
   }
   resolve(result){
     if(this.state===myPromise.PENDING){
       this.result=result
       this.state=myPromise.RESOLVED
     }
   }
   reject(result){
     if(this.state===myPromise.PENDING){
       this.result=result
       this.state=myPromise.REJECTED
     }
   }
   then(fn_success,fn_fail){
     return new myPromise((resolve,reject)=>{
       setTimeout(()=>{
         fn_success= fn_success instanceof Function ? fn_success:()=>{}
         fn_fail= fn_fail instanceof Function ? fn_fail:()=>{}
         if(this.state===myPromise.RESOLVED){
           fn_success(this.result)
         }
         if(this.state===myPromise.REJECTED){
           fn_fail(this.result)
         }
         resolve(this.result)
       },0)
     })
   }
 }
 ​
 // js自带的 .东西 会等待前面最终的状态结束之后再执行东西中的内容(这个也可能是错的 但是我的链式调用就是这么自动弄好的)
 console.log("第一步")
 let p=new Promise((resolve,reject)=>{
   console.log("第二步")
   resolve()
 }).then((res)=>{
   console.log("第四步")
 })
 console.log("第三步")
 ​
 //ceshi
 console.log("第一步111")
 let p1=new myPromise((resolve,reject)=>{
   console.log("第二步111")
   resolve()
 }).then(res=>{
   console.log("第四步111")
 },err=>{
   console.log(err)
 }).then(()=>{
   console.log("链式调用")
 })
 console.log("第三步111")
Promise.all
 function promiseAll(promises) {
   return new Promise(function(resolve, reject) {
     if(!Array.isArray(promises)){
         throw new TypeError(`argument must be a array`)
     }
     var resolvedCounter = 0;
     var promiseNum = promises.length;
     var resolvedResult = [];
     for (let i = 0; i < promiseNum; i++) {
       Promise.resolve(promises[i]).then(value=>{
         resolvedCounter++;
         resolvedResult[i] = value;
         if (resolvedCounter == promiseNum) {
             return resolve(resolvedResult)
           }
       },error=>{
         return reject(error)
       })
     }
   })
 }
 // test
 let p1 = new Promise(function (resolve, reject) {
     setTimeout(function () {
         resolve(1)
     }, 1000)
 })
 let p2 = new Promise(function (resolve, reject) {
     setTimeout(function () {
         resolve(2)
     }, 2000)
 })
 let p3 = new Promise(function (resolve, reject) {
     setTimeout(function () {
         resolve(3)
     }, 3000)
 })
 promiseAll([p3, p1, p2]).then(res => {
     console.log(res) // [3, 1, 2]
 })
 ​
Promise.race
 Promise.race = function (args) {
   return new Promise((resolve, reject) => {
     for (let i = 0, len = args.length; i < len; i++) {
       args[i].then(resolve, reject)
     }
   })
 }
实现有并行限制的 Promise 调度器
 class Scheduler {
   constructor(limit) {
     this.queue = [];
     this.maxCount = limit;
     this.runCounts = 0;
   }
   add(time, order) {
     const promiseCreator = () => {
       return new Promise((resolve, reject) => {
         setTimeout(() => {
           console.log(order);
           resolve();
         }, time);
       });
     };
     this.queue.push(promiseCreator);
   }
   taskStart() {
     for (let i = 0; i < this.maxCount; i++) {
       this.request();
     }
   }
   request(){
     if (!this.queue || !this.queue.length || this.runCounts >= this.maxCount) {
       return;
     }
     this.runCounts++;
     this.queue.shift()().then(() => {
         this.runCounts--;
         this.request();
       });
   }
 }
 const scheduler = new Scheduler(2);
 const addTask = (time, order) => {
   scheduler.add(time, order);
 };
 addTask(1000, "1");
 addTask(500, "2");
 addTask(300, "3");
 addTask(400, "4");
 scheduler.taskStart();
 ​
v-model 自定义组件
 <custom-input v-model="searchText">  //这个是自定义组件
 // <custom-input> 组件
 <input v-bind:value="message" v-on:input="onmessage"></aa-input>
 ​
 props:{message:"",}
 methods:{
     onmessage(e){
         $emit('input',e.target.value)
     }
 }

dom

将虚拟 Dom 转化为真实 Dom
 // 真正的渲染函数
 function _render(vnode) {
   // 如果是数字类型转化为字符串
   if (typeof vnode === "number") {
     vnode = String(vnode);
   }
   // 字符串类型直接就是文本节点
   if (typeof vnode === "string") {
     return document.createTextNode(vnode);
   }
   // 普通DOM
   const dom = document.createElement(vnode.tag);
   if (vnode.attrs) {
     // 遍历属性
     Object.keys(vnode.attrs).forEach((key) => {
       const value = vnode.attrs[key];
       dom.setAttribute(key, value);
     });
   }
   // 子数组进行递归操作
   vnode.children.forEach((child) => dom.appendChild(_render(child)));
   return dom;
 }
 ​
 调用
 {
   tag: 'DIV',
   attrs:{
   id:'app'
   },
   children: [
     {
       tag: 'SPAN',
       children: [
         { tag: 'A', children: [] }
       ]
     },
     {
       tag: 'SPAN',
       children: [
         { tag: 'A', children: [] },
         { tag: 'A', children: [] }
       ]
     }
   ]
 }
 把上诉虚拟Dom转化成下方真实Dom
 <div id="app">
   <span>
     <a></a>
   </span>
   <span>
     <a></a>
     <a></a>
   </span>
 </div>
 ​
 ​