IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> 【前端源码解析】指令和生命周期 -> 正文阅读

[JavaScript知识库]【前端源码解析】指令和生命周期

参考:Vue 源码解析系列课程

本章源码:https://gitee.com/szluyu99/vue-source-learn/tree/master/Directive_Study

系列笔记:

JavaScript 知识储备

documentFragment

Document.createDocumentFragment() - Web API 接口参考 | MDN (mozilla.org)

document.createDocumentFragment() 用来创建一个虚拟的节点对象(文档片段对象)

fragment 认为是一个 dom 节点的收容器,原本要向 dom 中插入多少个节点就需要浏览器回流多少次,fragment 之后把要插入的节点先放入这个收容器中,这样再一次性插入,可以使浏览器只回流一次。

使用这个来模拟 Vue 中的 AST 抽象语法树(简化版的 AST)

DOM nodeType

nodeType 属性返回节点类型(只读)

  • 元素节点,nodeType 属性返回 1
  • 属性节点, nodeType 属性返回 2
  • 文本节点,nodeType 属性返回 3
  • 注释节点,nodeType 属性返回 8

类数组

产生情况

类数组就是形式像数组一样,但是没有数组的方法(有 length 属性)的对象

// 这是个类数组
let arrayLike = {
   0: "java",
   1: "C++",
   2: "javascript",
   length: 3
}

JavaScript 在以下情况中的对象是 类数组

  • 函数里面的参数对象 arguments
  • 使用 getElementsByName / TagName / ClassName 获得到的 HTMLCollection
  • querySelector 获得的 NodeList

arguments

function sum(a, b, c) {
    console.log(arguments)
    console.log(typeof arguments) // object
	console.log(Object.prototype.toString.call(arguments)) // [object Arguments]
	console.log(arguments.callee)
}
sum(1, 2, 3)

HTMLCollection

<div class="son">
   张三
</div>
<div class="son">
   李四
</div>
<div class="son">
   王五
</div>

<script>
	var arrayList = document.getElementsByClassName("son")
	console.log(arrayList)
	console.log(Array.isArray(arrayList)) // false
	console.log(typeof arrayList) // object
	console.log(Object.prototype.toString.call(arrayList)) // [object HTMLCollection]
</script>

NodeList

<div class="son">
   张三
</div>
<div class="son">
   李四
</div>
<div class="son">
   王五
</div>

<script>
    var arrayList = document.querySelector(".son")
	console.log(Array.isArray(arrayList)) // fasle
    console.log(arrayList) // object
    console.log(typeof arrayList) // [object HTMLDivElement]
    console.log(Object.prototype.toString.call(arrayList))
</script>

应用场景

遍历参数操作:

function sum() {
      var sum = 0, len = arguments.length
      for (var i = 0; i < len; i++) {
        sum += arguments[i]
      }
	return sum
}

console.log(sum(1,2,3)) // 6

定义连接字符串函数:

类数组是没有 join 方法的,只能将其先转换为数组,再使用 join 方法

function myConcat(separa){
	// 类数组转数组
	var args = Array.prototype.slice.call(arguments, 1)
	return args.join(separa)
}

console.log(myConcat(",", "张三", "李四", "王五")) // 张三,李四,王五

将参数从一个函数传递到另一个函数:

function bar(a, b, c) {
   console.log(a, b, c)
}

function foo() {
   bar.apply(this, arguments)
}

bar(1, 2, 3) // 1 2 3
foo(1, 2, 3) // 1 2 3

类数组 转 数组

方法1:使用 ES6 中的 Array.from()

function fun () {
	let arr = Array.from(arguments)
	console.log(Array.isArray(arr)) // true
}

方法2:使用 ES6 中的 ...

function fun () {
	let arr = [...arguments]
	console.log(Array.isArray(arr)) // true
}

方法3:借数组方法

通过 [] 创建一个空数组,使用 slice 方法返回了一个新的数组

function fun () {
    let arr = [].slice.call(arguments)
    console.log(Array.isArray(arr))
}
function fun () {
    let arr = Array.prototype.slice.call(arguments)
    console.log(Array.isArray(arr))
}

手写一个简易版 Vue

本章源码:https://gitee.com/szluyu99/vue-source-learn/tree/master/Directive_Study

这里很多内容基于前面的基础上进行

简易版 Vue 的使用效果:

<div id="app">
	{{a}}
	<button onclick="add()">增加数字</button>
	<input type="text" v-model="a">
</div>

<script src="/xuni/bundle.js"></script>    

<script>
	let vm = new Vue({
		el: '#app',
		data: {
			a: 10,
		},
		watch: {
			a() {
				console.log('a 变化了');
			}
		}
	})
	console.log(vm);

	function add() {
		vm.a++
	}
</script>

Vue 这个类应当挂载在 windows 对象上:

window.Vue = Vue

Vue.js:简易版的 Vue 类

export default class Vue {
    constructor(options) {
        this.$options = options || {}
        this._data = options?.data || undefined 
        // 将数据变成响应式
        observe(this._data)
        // 将数据添加到实例中
        this._initData()
        // 调用默认的 watch
        this._initWatch()
        // 模板编译
        new Compile(options.el, this)
    }

    /**
     * 将数据添加到实例中
     */
    _initData() {
        let self = this
        Object.keys(this._data).forEach(key => {
            Object.defineProperty(self, key, {
                get() {
                    return self._data[key]
                },
                set (newVal) {
                    self._data[key] = newVal
                }
            })
        })
    }

    /**
     * 初始化 watch
     */
    _initWatch() {
        let self = this
        let watch = this.$options.watch 
        Object.keys(watch).forEach(key => {
            new Watcher(self, key, watch[key])
        })
    }
}

Compile.js:模板编译和指令解析

/**
 * 模板编译类,指令解析
 */
export default class Compile {
    constructor(el, vue) {
        // vue 实例
        this.$vue = vue
        // 挂载点
        this.$el = document.querySelector(el)
        if (this.$el) {
            // fragment 是 JS 提供的虚拟节点(弱化版 AST)
            let $fragment = this.node2Fragment(this.$el)
            // 编译
            this.compile($fragment)
            // 编译后内容,上树
            this.$el.appendChild($fragment)
        }
    }

    /**
     * DOM 节点转换成 Fragment
     */
    node2Fragment(el) {
        let fragment = document.createDocumentFragment()
        let child
        while (child = el.firstChild)
            fragment.append(child)
        return fragment
    }
    /**
     * 模板编译
     */
    compile(el) {
        let self = this
        let childNodes = el.childNodes

        // 匹配 {{val}} 中 val 
        let reg = /\{\{(.*)\}\}/ 

        childNodes.forEach(node => {
            let text = node.textContent

            if (node.nodeType === 1) {
                // console.log('是元素节点');
                self.compileElement(node)
            } else if (node.nodeType === 3 && reg.test(text)) {
                // console.log('是文本节点');
                let name = text.match(reg)[1] // 获取 {{val}} 中 val
                // console.log(`文本节点:${name}`);
                self.compileText(node, name)
            }
        })
    }
    /**
     * 编译元素
     */
    compileElement(node) {
        let self = this
        // 获取到节点的属性
        let nodeAttrs = node.attributes;
        // console.log(nodeAttrs)

        // nodeAttrs 是类数组,转成数组
        [].slice.call(nodeAttrs).forEach(attr => {
            // 例如:class="title"
            let attrName = attr.name // class
            let value = attr.value // title

            // 分析指令,都是 v- 开头的,如:v-if、v-model
            if (attrName.startsWith('v-')) { // 判断是指令
                let dir = attrName.substring(2)
                if (dir == 'model') {
                    // console.log(`v-model 指令:${value}`);
                    // 实现 v-model 的双向绑定功能
                    new Watcher(self.$vue, value, newVal => {
                        node.value = newVal
                    })

                    let v = self.getVueVal(self.$vue, value)
                    node.value = v
                    node.addEventListener('input', e => {
                        let newVal = e.target.value
                        self.setVueVal(self.$vue, value, newVal)
                        v = newVal
                    })
                } else if (dir == 'if') {
                    // console.log(`v-if 指令:${value}`);
                }
            }
        })
    }
    /**
     * 编译文本
     */
    compileText(node, name) {
        // console.log('compileText: ' + name);
        console.log(`getVueVal: ${this.getVueVal(this.$vue, name)}`);

        node.textContent = this.getVueVal(this.$vue, name)
        // 创建一个观察者,监听数据变化
        new Watcher(this.$vue, name, value => {
            node.textContent = value
        })
    }
    /**
     * 根据 a.b.c 形式的表达式从 vue 实例中获取值 
     */
    getVueVal(vue, exp) {
        let val = vue
        exp.split('.').forEach(key => 
            val = val[key]
        )
        return val
    }
    /**
     * 根据 a.b.c 形式的表达式给 vue 实例设置值
     */
    setVueVal(vue, exp, value) {
        let val = vue
        exp.split('.').forEach((key, index) => {
            if (index < key.length - 1) {
                val = val[key]
            } else {
                val[key] = value
            }
        })
    }
}
  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-07-03 10:40:34  更:2022-07-03 10:42:27 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/11 10:14:35-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码