面试必备(前端必须知道的知识点)金三银四助力大厂-🎉🎉🎉

2,323 阅读27分钟

请你一定不要停下来,成为你想要成为的人

不用循环生成1-1000的数组

Array.from({length:1000},(x,y)=>y+1)

css 性能优化

1.合并css,(因为加载31kb的css要比加载一个100kb的css要慢)
2.不要用@import 会影响当前css加载速度
3.避免css层级过深入(最好3级以内)
4.抽离公共样式,这样可以缓存部分css(会提高第一次css加载速度,看情况而定)
5.用好css的继承关心(比如父有color,font,子就继承的所以不用定义子的color,font)
6.使用雪碧图cssSprite(多个图片合并成一个图,这样我们可以减少图片的请求)
7.对css进行压缩

聊聊继承以及说说 ES5 和 ES6 继承的区别?

// es5 通过原型链 prototype 来继承当前方法的静态方法,用call或者apply来调用父组件的构造函数
// es5 继承
var fn = function(name){
    this.name = name
}
fn.prototype.xname = '张三' 
var x = function(name){
    fn.call(this,name)
}
x.prototype = fn.prototype
x.prototype.constructor = fn
var y = new x('小明')
// {name:'小明'} // __proto__ 上有当前xname 的属性 为 '张三'

// es6 通过 extends 关键字继承,fn组件的方法,使用super 来调用父组件的 构造函数
class fn{
    constructor(name){
        this.name = name
        this.xname = '张三'
    }
}
class x extends fn{
    // 必须添加 super 来执行父组件的 constructor 使子组件获取当前name的赋值
    constructor(name){
        super(name)
    }
}
let y = new x('张三')
// {name:'小明',xname:'张三'}

实现fn(1)(2)(3) = 6 并且后面添加进行无限扩展

// 解:当前用到了
// 1 return f(Function)这样就可以使当前可以无限使用方法
// 2 用到了toString和valueOf 当获取不到值的时候就会调用toString 和 valueOf 的方法 (写2个是为了兼容各个浏览器先后顺序不一致)
// 3 val 是一个闭包当前的val 一直呗f方法所调用所以一直没有被垃圾回收机制回收所以val 的值是一直记录着的
let fn = function(x){
    let val = x
    let f = function(y){
        val += y
        return f
    }
    f.toString = function(){
        return val + ''
    }
    f.valueOf = function(){
        return val
    }
    return f
}
fn(1)(2)(3// 6

vue2.x实现的原理

// 通过defineproperty来对象劫持当前的对象来进行双向绑定
// defineproperty 不能对数组进行劫持需要进行特殊对操作才可以
let ArrayPro = Array.prototype
let ArrayObj = Object.create(ArrayPro);
['pop','push','splice','shift','onshift'].forEach(item=>{
    ArrayObj[item] = function(){
        updataView()
        ArrayPro[item].call(this,...arguments)
    }
})
// 更新视图
function updateView(){
    console.log('更新视图')
}

let reactProperty = function(obj,item,val){
    // 递归向下监听,defineproperty只能监听一层
    observer(val)
    Object.defineProperty(obj,item,{
        get(){
            return val
        },
        set(newVal){
            if(newVal!==val){
                val = newVal
                updateView()
            }
        }
    })
}

let observer = function(obj){
    // 由于历史原因 null 会等于 typeof object
    if(typeof obj !== 'object' || obj === null){
        return 
    }
    // ArrayObj:'pop','push','splice','shift','onshift'
    // 带有这几个方法的对象执行的时候会调用数组自身的方法
    // 当前是数组的情况会把指针指向当前的 ArrayObj 这样我们的数组就可以被监听到了
    if(Array.isArray(obj)){
        obj.__proto__ = ArrayObj
    }
    for(let item in obj){
        // 判断当前数据在 obj 的里面
        if(obj.hasOwnProperty(item)){
            reactProperty(obj,item,obj[item])
        }
    }
}

let objectA = {
    a1,
    v: []
}
observer(objectA)
objectA.a = 2
objectA.v.push(1)

Vue.js 中的 DIFF 算法是怎么处理的?

1.只对同层级别进行对比
2.对当前不同的tag(dom),直接进行替换
3.当前key和tag一样的时候不进行对比

Vue.js 中 DIFF 算法的时间复杂度是多少?为什么?

新vdom 和 老的vdom 进行对比那么复杂度就是O(n2),对比后还要进行修改(新增或者删除),那么就是O(n3)

Vue.js 中有哪些周期函数?这些周期函数都是在什么时机执行的?

1.beforeCreate // 开始前
2.Creaed // 开始
// 判断当前是 template 模版 还是render 函数
3.bedoreMount // 创建dom前
4.Mounted // 创建dom后(挂载完成)
// beforeUpdate //更新前
// Updated // 更新后dom渲染完成
5.beforeDestroy // 销毁前
6.Destroyed // 销毁后

// 父子组件嵌套的生命周期
1.父 beforeCreate
2.父 Created 
3.父 beforeMount 
4.  子 beforeCreate
5.  子 Create
6.  子 beforeMount
7.  子 Mounted
8.父 Mounted

// 父子组件销毁
1.父 beforeDestroy
2.  子 beforeDestroy
3.  子 Destroyed
4.父 Destroyed

// 父子组件更新
1.父 beforeUpate
2.  子 beforeUpate
3.  子 Upated
4.父 Upated

什么是mvc?mvvm?

// mvc 通过文字我们可以得出model(模型),view(视图),controller(控制器)当我们点击一个按钮
(视图view)传递点击事件,控制器(逻辑)(controller)进行对某个字段对赋值(model及数据(变量)),
当我们当数据改变时,我们又对当前视图进行渲染dom
// mvvm m (model 模型)v (view 视图) vm (vue控制,视图模块(抽象视图))
mvvm就是数据改变视图,当我们数据改变当时候,通过vue(vm)来修改我们当视图,然后视图改变,通过(vm)
来修改我们当model

观察者模式和发布/订阅模式?

// 两个模式的简介
观察者模式就是比如你和小明都想和牛奶,然后拨打了牛奶站的电话。牛奶站的工作人员统一给你们进行配送这样的模式
就是观察者模式,而订阅发布则是你给牛奶站的代理商打电话,不直接和牛奶站直接接触,牛奶站只需要给代理商送货就
可以,而你只需要和代理商接触,这样牛奶站就结偶了不用处理送货之类的方法这样的方式就是订阅发布。

// 观察者模式
// 小明
let subject = function(name){
    this.name = name
}
subject.prototype.go = function(){
    console.log(`${this.name},酸酸甜甜真好喝`)
}
// 服务站
let observer = function(name){
    this.name = name
    this.list = []
}
observer.prototype.push = function(obj){
    this.list.push(obj)
}
observer.prototype.go = function(){
    this.list.forEach(item=>{
        item.go()
    })
}
let server = new observer('牛奶站')
// 小明订阅牛奶
server.push(new subject('小明'))
// 小红订阅牛奶
server.push(new subject('小红'))
// 牛奶站配送牛奶
server.go()
// 还有es6版本 需要的下面留言我会在这写,不过差不多

// 发布订阅
// 代理商
let subject = function(name){
    this.name = name
}
subject.prototype.go = function(min){
    console.log(`${min},酸酸甜甜真好喝`)
}
let pubSub = function(){
    this.fnList = {}
}
pubSub.prototype.push = function(type, fn){
    this.fnList[type] = fn
}
pubSub.prototype.go = function(type){
    let fn = this.fnList[type]
    fn.call(this,...arguments)
}
let pub = new pubSub()
let min = new subject('小明')
pub.push('min',min.go)
pub.go('min')

箭头函数跟普通函数的区别

1.箭头函数的this指向不会因为call,apply,bind的印象,一直指向上下文的this。普通函数指向调用者
2.箭头函数没有arguments用(...rest)=>{} 代替,箭头函数是匿名函数
3.箭头函数没有原型constructor

从输入url到页面展示的详细过程

1.输入网址
2.dns解析
3.建立tcp链接
4.客户端发送请求
5.服务端处理请求
6.服务端返回数据
7.客户端接收数据
8.客户端展示html
9.客户端发送请求html里面需要的资源(imgjscss
10.生成(dom树和styleRulesrender树然后渲染

https 实现原理(越详细越好)

1.客户端发送请求给服务端(443端口)(http是80端口)
2.服务端返回当前的数字证书和公钥
3.客户端校验数字证书,生成随机数,与公钥生成(对称加密)密钥,传递给服务端
4.服务端解析秘钥获取随机数,(通过对称加密对要传递对数据进行加密),传递给客户端
5.客户端收到加密数据进行解析获取数据
https 实现原理
https 实现原理

页面渲染过程

1.生成 dom 树
2.生成 css Rules 
3.加载js会影响我们的生成👆(会页面堵塞所以尽量放到最后)
3.生成我们的render树
4.渲染我们的页面
5.重绘和回流(回流的性能消耗要比重绘大)
// 重绘:页面结构不变情况,比如就color,font-size
// 回流:dom结构改变必然触发重绘,比如display:node等改变页面结构的

如何性能优化(问题比较大我们分层)

写代码
我们尽量少使用闭包
避免使用with语句
代码循环优化
多使用innerHTML性能快
合并dom操作
模块化

加载时
合并css
使用雪碧图(加载101k的图片要比加载1100k的要慢)
使用cnd
懒加载
使用缓存
使用懒加载
script 放后面

总体想法就是减少请求次数,减少cpu使用,多使用本地缓存

前端模块化的理解

模块话主要就是分治,复用,解耦,利于我们的开发。
如何进行抽离我们的模块有2个原则
1:第一个就是复用,减少我们没必要的重复的代码。
2:就是相对独立的一个模块。怎么理解?就是说比如你写一个音乐播放组件,就一个地方使用,但是这个相对独立
功能独立和其他组件没有粘连,
使用组件的时候我们应该多定义一些默认参数(看业务这个)

给你一个项目,从头开始你怎么考虑

1.先需求评审,看需求是否有漏洞,是否合理
2.需求拆分(评估时间)(有的会先看测试用例)
3.写文档前后端协商(有时间的尽量可以画一个uml类图)
4.分配需求(开始搞起)
5.完成(自测,单元测试)
6.测试测
7.项目上线
8.线上走一遍

script标签中defer和async属性的区别

1.async 就是异步加载
2.defer 就是先加载不执行,等元素都解析完成后执行,DOMContentLoaded事件触发执行之前

手写bind

首先我们考虑一下bind是返回一个 function 然后this指向传入的obj
首先先定一个一个obj
let obj = 
{
    name : '张三'
}
Function.prototype.myBind = function(){
    // 获取我们传的所有的值
    let arr = [...arguments]
    // 获取第一个对象
    let obj = arr.shift()
    // 后去当前方法
    let _this = this
    // bind 是 return 一个方法
    return function(){
        // this 指向我们传进来的 obj ,后面跟上我们的几个参数
        _this.apply(obj,[...arr,...arguments])
    }
}
// 使用
function name(name){
    console.log(name)
    this.name = name
}
name.myBind(obj,obj.name)()

手写 call

首先我们考虑一些call 是传入一堆的值(obj,x,x,x,x,x,x,x,x,x,x)而且是直接执行的
let obj = {
    name'张三'
}
Function.prototype.myCall = function(){
    let arr = [...arguments]
    // 当前如果没有传入那么就是 window
    let obj = arr.shift() || window
    // 赋值给fn 这样就可以赋值我们的this指向
    obj.fn = this
    // 执行
    obj.fn(...arr)
    delete obj.fn
}
// 执行
function name(name){
    console.log(this)
    console.log([...arguments])
    this.name = name
}
name.myCall(obj,obj.name,1,2,3,4,5)

手写 apply

apply 和call 区别就是传入的值是数组
let obj = {
    name'张三'
}
Function.prototype.myApply = function(){
    let arr = [...arguments]
    let obj = arr.shift() || window
    obj.fn = this
    obj.fn([...arr])
    delete obj.fn
}
// 执行
function name(name){
    console.log(this)
    console.log(name)
}
name.myApply(obj,obj.name,1,2,3,4,5)

手写 map 和 filter

function map(arr,fun){
    let ar = []
    for(let i=0;i<arr.length;i++){
        ar.push(fun(arr[i],i,arr))
    }
    return ar
}
map([1,2,3,4,5],(item,index)=>{return item+index})
// [1,3,5,7,9]

function filter(arr,fun){
     let ar = []
    for(let i=0;i<arr.length;i++){
        if(fun(arr[i],i,arr)){
            ar.push(arr[i])
        }
    }
    return ar
}
filter([1,2,3,4,5],(item)=>item>2)
// [3,4,5]

手写ajax

let ajax = new XMLHttpRequest()
ajax.open('get','www.xxx.xx/xxx',false)
ajax.onreadystatechange = function(){
    if (ajax.readystate == 4) {
        //响应内容解析完成,可以在客户端调用了
        if (ajax.status == 200) {
            //客户端的请求成功了
            alert(ajax.responseText);
        }
    }
}
ajax.send()

promise 限制并发数

// promise限制并发数量,比如小程序请求接口最多10个一起,那么超出的第11个必须等第10个结束后才可以进行请求,我们就通过
// promise来实现
class limitPromise{
    constructor(max){
        // 当前最大的请求次数
        this._max = max
        // 当前的请求次数
        this._count = 0
        // 当前超出的请求任务
        this._tasklist= []
    }
    // fn 当前请求的函数  args 当前请求的参数
    call(fn,...args){
        return new Promise((resolve,reject)=>{
            let task = this._createTask(fn,args,resolve,reject)
            if(this._count>=this._max){
                this._tasklist.push(task)
            }else{
                task()
            }
        })
    }
    // 创建任务
    _createTask(fn,args,resolve,reject){
        return ()=>{
            fn(...args)
                .then(resolve)
                .catch(reject)
                .finally(_=>{
                    this._count--
                    if(this._tasklist.length){
                        let task = this._tasklist.shift()
                        task()
                    }
                })
            this._count++
        }
    }
}

let limit = new limitPromise(2)

// 我是一个请求方式
let fn = function(){
    xxxx
}

let ajax = (data)=>{
    limit._call(fn,data)
}

浅拷贝

浅拷贝就是只拷贝第一层的数据
let obj = {
    a1,
    b2,
    arr: [1,{a1},[1,[1]]]
}
// 1
let obj = Object.assign({},obj)
// 2
let fn = (obj)=>{
    let ob = {}
    for(let item in obj){
        if(obj.hasOwnProperty(item)){
            ob[item] = obj[item]
        }
    }
    return ob
}
fn(obj)

深拷贝

深拷贝就是拷贝当前所有的层
let obj = {
    a: 1,
    b: 2,
    arr: [1,{a: 1},[1,[1]]]
}
// 转换字符串然后转换json  
// 这样是有问题的,我们的funciton symbol 等都不支持
let obj1 = JSON.pase(JSON.stringify(obj))

// 所以我们只能通过递归的方式 (object,和array)

let obj = {
    a: 1,
    b: 2,
    arr: [1,{a: 1},[1,[1]]]
}

let clone = (obj)=>{
    let val
    let type = Object.prototype.toString.call(obj)
    if(type === '[object Object]'){
        val = {}
    }else if(type==='[object Array]'){
        val = []
    }else{
        return obj
    }
    for(let item in obj){
        let value = obj[item]
        let type = Object.prototype.toString.call(obj)
        if(type === '[object Object]'||type==='[object Array]'){
            val[item] = clone(value)
        }else{
            val[item] = value
        }
    }
    return val
}
clone(obj)

手写pomise

function myPromise(fn){
    this.state = 'pending'
    this.value = ''
    this.error = ''
    this.onResolveFn = []
    this.onResjectFun = []
    let resolve = (value)=>{
        if(this.state==='pending'){
            this.state = 'resolved'
            this.value = value
            this.onResolveFn.forEach(fn=>fn(value))
        }
    }
    let resject = (error)=>{
        if(this.state==='pending'){
            this.state = 'resjected'
            this.error = error
            this.onResjectFun.forEach(fn=>fn(error))
        }
    }
    fn(resolve,resject)
}
myPromise.prototype.then = function(resolveFn,resjectFun){
    // 异步
    if(this.state==='pending'){
        if(typeof resolveFn ==='function'){
            this.onResolveFn.push(resolveFn)
        }
        if(typeof resjectFun ==='function'){
            this.onResjectFun.push(resjectFun)
        }
    }
    // 同步
    if(this.state==='resolved'){
        if(typeof resolveFn ==='function'){
            resolveFn(this.value)
        }
    }
    if(this.state==='resjected'){
        if(typeof resjectFun ==='function'){
            resjectFun(this.error)
        }
    }
}
let promise1 = new myPromise((resolve,resject)=>{
    resolve(1)
}).then(res=>{
    console.log(res)
})
let promise2 = new myPromise((resolve,resject)=>{
    setTimeout(function(){
        resolve(2)
    },2000)
})
promise2.then(res=>{
    console.log(res)
})

防抖

防抖指的是一定时间内指执行一次,有两种版本 第一个是先执行,然后多少时间后才能再次点击。另一个是延时执行
let dob = function(){
    let arr = [...arguments]
    let fn = arr.shift()
    let time = arr.shift()||1000
    let timeout = null
    // 延时执行
    return function(){
        let that = this
        if(timeout){
            clearTimeout(timeout)
        }
        timeout = setTimeout(function(){
            // 需要等定时结束才可以执行 所以是延时等
            fn.apply(that,[...arr])
        },time)
    }
    // 立即执行
    return function(){
        if(timeout){
            clearTimeout(timeout)
        }
        // 当前只有是null 等时候 show 才是true
        let show = !timeout
        timeout = setTimeout(function(){
            timeout = null
        },time)
        // 并不需要把当前的执行方法放再定时器里面
        // 所以一开始我们的 show 就是true 所以立即就执行,(时间未到)第二次点击的时候就是false,时间过去才可以
        if(show){
            fn.apply(this,[...arr])
        }
    }
}

节流

节流就是一定时间内只执行一次,不会覆盖之前的。有两种,一种是使用时间,另一种是使用定时器
let thr = function(){
     let arr = [...arguments]
    let fn = arr.shift()
    let time = arr.shift()||1000
    // 时间
    let oldDate = 0
    return function(){
        let newDate = Date.now()
        if(newDate>oldDate+time){
            oldDate = newDate
            fn.apply(this,[...arguments,...arr])
        }
    }
    // 定时器
    let show = true
    return function(){
        let context = this
        let arg = [...arguments]
        if(show){
            show = false
            setTimeout(function(){
                show = true
                fn.apply(context,[...arg,...arr])
            },time)
        }
    }
}

v-model原理

v-model 原理就是vue 帮我们内置了 $emit 的简写
// 父组件
<xxx v-model='message'></xxx>
// js
export default{
    ...
    data(){
        return{
            message: 1
        }
    }
}
// 子组件
<template>
    <div @click='$emit('input',value+1)'>
        {{value}}
    </div>
</template>
<script>
    export default{
        props:['value']
    }
</script>
// 这样也是可以用的,vue 帮我们内置了 @input 和 value 就是等于model 的值
// 默认就是
export default{
    model: {
        prop: 'value',
        event: 'input'
    },
    props:['value']
}

或者我们改下一下这边的值
<template>
    <div @click="$emit('click',value1+1)">
        {{value1}}
    </div>
</template>
<script>
    export default{
        model: {
            // 定义当前的值
            prop: 'value1',
            // 定义当前 return 的事件的
            event: 'click'
        },
        props:['value1']
    }
</script>
// 建议小伙伴们可以试试。自动动手丰衣足食

隐式转换

// 加
1+'1' // 11
1+'true' // 1true
1+true // 2 1+Number(true)
1+undefinede // NaN 相当于 1+Number(undefinede)
1+null // 1 1+Number(null)
// 对比
"2">10 // false Number(2)>10
"2">"10" // true 转换成字符对比
"abc">"b" // false 转换成了字符然后进行对比
![] == 0 // true ![] 取返得到false false 和 0 对比都是false 所以是true
[] == 0 // true []调用valueOf 和 toString 获得 空字符串  然后和0对比都是false 所以是true
[] == ![] // true 一个是 false 一个是 空字符串所以是true
[] == [] // false 两个引用类型进行对比就是对比存的栈,所以是false
{} == !{} // false {} valueOf 和 toString 获得的是[object Object] !{}是false 所以两个是不一致的
{} == {} // 两个引用类型对比是比较当前栈的

var a = ?
if(a==1&&a==2&&a==3){
    console.log('a等于啥')
}

a = {
    value0,
    toString: function(){
        return ++a.value
    }
}
// 对比前objec会先调用valueOf 或者 toString 的方法

重构 chenhongdong 老师的mvvm

1.添加对array 的监听
2.observe对数据进行监听
3.发布订阅模式 Dep watch
4.computed的形式
5.compile
6.mounted

let ArrayProtoType       = Array.prototype;
let arrayProtoTypeObject = Object.create(ArrayProtoType);
['pop','push','onshift','shift','splice'].forEach(item=>{
    // 对obj 添加数组的方法
    arrayProtoTypeObject[item] = function({
        ArrayProtoType[item].call(this,...arguments)
    }
})
const reactProperty = function(obj,item,value{
    let dep = new Dep()
    // 递归进行监听
    observer(value)
    Object.defineProperty(obj,item,{
        get(){
            Dep.target && dep.addSub(Dep.target)
            return value
        },
        set(newVal){
            // 当值不一样的时候才可以进行修改
            if(newVal!==value){
                value = newVal
                // 当值进行修改的时候我们再次加上监听
                observer(value)
                dep.notity()
            }
        }
    })
}

// 发布订阅
const Dep = function ({
    this.subs = []
}
Dep.prototype = {
    addSub(sub){
        this.subs.push(sub)
    },
    notity(){
        this.subs.forEach((sub)=>sub.update())
    }
}

const Watch = function(vm,reg,fn{
    this.vm    = vm
    this.fn    = fn
    this.reg   = reg
    Dep.target = this
    this.reg.split('.').reduce(function(x,y){
        return x[y]
    },this.vm)
    Dep.target = null
}
Watch.prototype.update = function({
    let val = this.reg.split('.').reduce(function(x,y){
        return x[y]
    },this.vm)
    this.fn(val)
}
// 对数据进行监听
const observer = function(obj{
    if(typeof obj!=='object'||obj === null){
        return
    }
    if(Array.isArray(obj)){
        // 指针切换
        obj.__proto__ = arrayProtoTypeObject
    }
    for(let item in obj){
        reactProperty(obj,item,obj[item])
    }
}
let Mvvm = function(option = {}{
    let data 
    this.$option = option
    this.$el     = option.el
    data         = this._data = option.data
    // 数据监听
    observer(data)
    // 对this 进行简化操作  使我们app.x 就能获取到 app.$option.data.x
    for(let item in data){
        Object.defineProperty(this,item,{
            // 可以删除
            configurable: true,
            get(){
                return data[item]
            },
            set(val){
                data[item] = val
            }
        })
    }
    initComputed.call(this)
    // 数据渲染
    compile(this.$el,this)
    this.$option.mounted.call(this)
}


// 对模版进行渲染
const compile = function(el,vm{
    // 获取当前的要放的总dom
    vm.$el = document.querySelector(el)
    // 创建一个内存容器 
    let fragment = document.createDocumentFragment()
    while(child = vm.$el.firstChild){
        // 把当前dom 内存中进行操作
        fragment.appendChild(child)
    }
    function replace(fragment){
        // 对当前内存容器下的dom进行替换操作
        let reg = /\{\{(.*?)\}\}/g
        Array.from(fragment.childNodes).forEach(node=>{
            // 当前是 v-model
            // dom 属性 上
            if(node.nodeType===1){
                // 获取当前dom 上的属性
                let nodeAttr = node.attributes
                Array.from(nodeAttr).forEach(attr=>{
                    // 当前属性名称 v-model
                    let name = attr.name
                    // 当前属性的值 message
                    let value = attr.value
                    if(name.indexOf('v-')>-1){
                        // 当前有v-的赋值上value 的属性上
                        // 赋值给当前的 node vm[value]在mvvm 进行了监听 
                        node.value = vm[value]
                    }
                    // 对属性进行监听
                    new Watch(vm,value,(newVal)=>{
                        node.value = newVal
                    })
                    // 添加 input 的方法 v-model 的默认值就是那么来的
                    node.addEventListener('input',function(event){
                        let newVal = event.target.value
                        // 触发set
                        vm[value] = newVal
                    })
                })
            }
            // dom text
            // 当前是差值表达式 {{}}
            if(node.nodeType===3){
                let text = node.textContent
                text.replace(reg,(x,y)=>{
                    // 针对 a.x.x的情况赋值
                    let val = y.split('.').reduce(function(x,y){
                        return x[y]
                    },vm)
                     // 对属性进行监听
                    new Watch(vm,y,(newVal)=>{
                        node.textContent = newVal
                    })
                    node.textContent = val
                })
            }
            // 对子的子进行递归调用可能会有很多级
            if(node&&node.childNodes){
                replace(node)
            }
        })
    }
    replace(fragment)
    vm.$el.appendChild(fragment)
}

const initComputed = function({
    let vm       = this
    let computed = this.$option.computed
    Object.keys(computed).forEach(item=>{
        Object.defineProperty(vm,item,{
            gettypeof computed[item] === 'function' ?computed[item]: computed[item].get,
            set()=>{}
        })
    })
}

// 定义 mvvm
let app = new Mvvm({
    el      : '#app',
    computed: {
        xx(){
            return this.message
        }
    },
    data: {
        a      : 1,
        message'hello world'
    },
    mounted(){

    }
})
setTimeout(function(){
    app.message = 'jinjinjin'
},2000)

最后

能看到最后🙏谢谢大家了,多多点赞在github 上面对❤️是对我最好对鼓励,我会尽量分享一些自己使用得心得以及正确对食用方式

求靠谱内推(北京地区)可以留言我 +。=