Spring都没弄明白凭什么拿高薪,动手实践去实现 Vue 2,前端h5开发

5  }

6

7  // 订阅

8  addSub(watcher) {

9    this.subs.push(watcher);

10  }

11

12  // 通知

13  notify() {

14    this.subs.forEach(watcher => watcher.update());

15  }

16}

4、依赖收集

在我们更新视图的时候进行依赖收集,给每个属性创建一个发布订阅的功能,当我们的值在 set 中改变时,我们就触发订阅者的通知,让各个依赖该数据的视图进行更新。

1defineReactive(obj, key, value) {

2  // 递归创建 响应式数据,性能不好

3  this.observer(value);

4  let dep = new Dep(); // 给每一个属性都加上一个具有发布订阅的功能

5  Object.defineProperty(obj, key, {

6    get() {

7      // 创建 watcher 时,会取到响应内容,并且把 watcher 放到了全局上

8      Dep.target && dep.addSub(Dep.target);  // 增加观察者

9      return value;

10    },

11    set: newValue => {

12      if (newValue !== value) {

13        // 设置某个 key 的时候,可能是一个对象

14        this.observer(value);

15        value = newValue;

16        console.log(‘-------------------------视图更新-----------------------------’)

17        dep.notify(); // 通知

18      }

19    }

20  });

剩下的就是我们调用 new Watcher 地方了,这个过程在编译模板里边。

三、编译模板

对于模板的编译,我们首先需要判断传入的 el 类型,然后拿到页面的结点到内存中去,把节点上有数据编译的地方,比如:v-model、v-on、{{student.name}} 进行数据的替换,然后再塞回页面,就完成的页面的显示。

1// 编译类

2class Compile {

3  constructor(el, vm) {

4    // 判断 el 传入的类型

5    this.el = this.isElementNode(el) ? el : document.querySelector(el);

6    this.vm = vm;

7

8    // 把当前节点放到内存中去 —— 之所以塞到内存中,是因为频繁渲染造成回流和重绘

9    let fragment = this.nodefragment(this.el);

10

11    // 把节点在内存中将表达式和命令等进行数据替换

12    this.compile(fragment);

13

14    // 把内容塞回页面

15    this.el.appendChild(fragment);

16  }

17}

1、将 DOM 拿到内存

首先我们之前已经声明好 data 了,如下:

1 let vm = new Vue({

2     el: ‘#app’,

3     data: {

4         student: {

5             name: ‘小鹿’,

6             age: 20,

7         },

8     }

9 })

然后我们需要拿到页面的模板,将页面中的一些指令(v-model=“student.name”)或者表达{{student.name}} 的结点替换成我们对应的属性值。

我们需要通过传入的 el 属性值先拿到页面的 dom 到内存中。

1/**

2   * 将 DOM 拿到内存中

3   * @param {*} node DOM

4   */

5nodefragment(node) {

6    let fragment = document.createDocumentFragment();

7    let firstChild;

8    while ((firstChild = node.firstChild)) {

9        fragment.appendChild(firstChild);

10    }

11    return fragment;

12}

2、数据替换

我们下一步需要将页面中的这些表达式,替换成相对应的 data 中的属性值,那么页面就将完成的呈现出带有数据的视图来。

1

2    

3    {{student.age}}

4

通过上边的方法,已经将所有的页面结点循环遍历拿到。下一步开始进行一层层的遍历,将数据在内存中进行替换。

1/**

2 * 核心编译方法

3 * 编译内存中的 DOM 节点

4 * @param {*} node

5 */

6compile(node) {

7  let childNodes = node.childNodes;

8  […childNodes].forEach(child => {

9    // 判断当前的是元素还是文本节点

10    if (this.isElementNode(child)) {

11      this.compileElement(child);

12      // 如果是元素的话,需要把自己传进去,再去遍历子节点

13      this.compile(child);

14    } else {

15      this.compileText(child); // 文本节点有 {{student.age}}

16    }

17  });

18}

19

20/**

21 * 判断当前传入的节点是不是元素节点

22 * @param {*} node 节点

23 */

24isElementNode(node) {

25  return node.nodeType == 1; // 1 代表元素节点

26}

this.isElementNode(child)

页面是由很多的 node 结点构成,在上边的页面中,v-model=“student.name” 主要存在与元素节点中,{{student.age}} 表达式的值存在于文本节点中,所以我们需要通过 this.isElementNode(child) 进行判断当前是否为元素节点,然后对当前节点进行不同的处理。

对于元素节点,我们调用 compileElement(child)方法,当然,元素节点中可能存在子节点的情况,所以我们需要递归判断元素节点里是否还有子节点,再次调用 this.compile(child); 方法。

我们以解析 v-model 指令为例,开始对节点进行解析判断赋值。

1

1/**

2 * 编译元素节点 —— 判断是否存在 v- 指令

3 * @param {*} node

4 */

5compileElement(node) {

6  let attributes = node.attributes;

7  […attributes].forEach(attr => {

8    // type = “text” v-model=“student.name”

9    let { name, value: expr } = attr; // name:v-model  expr:“student.name”

10    // 判断当前是否存在属性为 v- 的指令

11    if (this.isDirective(name)) {

12      // v-html  v-bind  v-model

13      let [, directive] = name.split(“-”);

14      let [directiveName, eventName] = directive.split(“:”); // v-on:click

15      // 调用不同的指令来处理

16      CompileUtildirectiveName;

17    }

18  });

19}

20

21/**

22 * 判断是够是 v- 开头的指令

23 * @param {*} attrName

24 */

25isDirective(attrName) {

26  return attrName.startsWith(“v-”);

27}

同时我们还有一个工具类 CompileUtil,主要用于把对应的 data 数据插入到对应节点中。

上一步中,我们通过 let [directiveName, eventName] = directive.split(“:”) 解析出了 directiveName=  v-model ,eventName = student.name。

然后我们将两个参数 directiveName 和 eventName 传入工具类对象中。

1// node: 当前节点  expr:当前表达式(student.name) vm:当前 vue 实例

2CompileUtildirectiveName;

通过调用不同的指令进行不同的处理。

1/**

2 * 工具类(把数据插入到 DOM 中)

3 * expr: 指令的值(v-model=“student.name” 中的 student.name)

4 */

5let CompileUtil = {

6  // ---------------------- 匹配指令或者表达式的函数 ----------------------

7  // 匹配 v-model

8  model(node, expr, vm) {

9    let fn = this.updater[“modelUpdater”];

10    new Watcher(vm, expr, newValue => {

11      // 给输入框添加一个观察者,如果数据更新了,会触发此方法,将新值付给 input

12      fn(node, newValue);

13    });

14    // 给 input 绑定事件

15    node.addEventListener(“input”, e => {

16      let value = e.target.value; // 获取用户输入的内容

17      this.setValue(vm, expr, value);

18    });

19    let value = this.getValue(vm, expr);

20    fn(node, value);

21  },

22

23  // ---------------- 其他用到的工具函数 -------------------

24  // $data取值 [student, name]

25  getValue(vm, expr) {

26    return expr.split(“.”).reduce((data, current) => {

27      return data[current];

28    }, vm.$data);

29  },

30

31  // 给 vm.$data 中数据赋值

32  setValue(vm, expr, value) {

33    expr.split(“.”).reduce((data, current, index, arr) => {

34      // 如果遍历取到最后一个,我就给赋值

35      if (index == arr.length - 1) {

36        return (data[current] = value);

37      }

38      return data[current];

39    }, vm.$data);

40  },

41

42  // -------------- 给对应的 dom 进行赋值 -------------------

43  updater: {

44    modelUpdater(node, value) {

45      // 处理指令结点 v-model

46      node.value = value;

47    }

48  }

49};

以上就会触发这个函数:

1// 匹配 v-model

2model(node, expr, vm) {

3    let fn = this.updater[“modelUpdater”];

4    new Watcher(vm, expr, newValue => {

5        // 给输入框添加一个观察者,如果数据更新了,会触发此方法,将新值付给 input

6        fn(node, newValue);

7    });

8    // 给 input 绑定事件

9    node.addEventListener(“input”, e => {

10        let value = e.target.value; // 获取用户输入的内容

11        this.setValue(vm, expr, value);

12    });

13    let value = this.getValue(vm, expr);

14    fn(node, value);

15},

同时我们看到了 new Watch 对该属性创建一个观察者,用于以后数据更新时,通知视图进行相应的更新的。

1new Watcher(vm, expr, newValue => {

2    // 给输入框添加一个观察者,如果数据更新了,会触发此方法,将新值付给 input

3    fn(node, newValue);

4});

同时又给 input 绑定了一个事件,用于实现对 input 框的监听,相对应的 data 也要更新,这就实现了v-model输入框的双向绑定功能。

1// 给 input 绑定事件

2node.addEventListener(“input”, e => {

3    let value = e.target.value; // 获取用户输入的内容

4    this.setValue(vm, expr, value);

5});

每当 data 数据被改变,我们就触发 this.updater 中的视图更新函数。

1let fn = this.updater[“textUpdater”];

2fn(node, value);

1// 给 dom 文本结点赋值数据

2updater: {

3  modelUpdater(node, value) {

4    // 处理指令结点 v-model

5    node.value = value;

6  }

7}

对于文本节点,调用 this.compileText(child) 方法和以上同样的实现方法。这一部分的整体实现代码如下:

1**

2 * 工具类(把数据插入到 DOM 中)

3 * expr: 指令的值(v-model=“school.name” 中的 school.name)

4 */

5let CompileUtil = {

6  // $data取值 [school, name]

7  getValue(vm, expr) {

8    return expr.split(“.”).reduce((data, current) => {

9      return data[current];

10    }, vm.$data);

11  },

12

13  // 给 vm.$data 中数据赋值

14  setValue(vm, expr, value) {

15    expr.split(“.”).reduce((data, current, index, arr) => {

16      // 如果遍历取到最后一个,我就给赋值

17      if (index == arr.length - 1) {

18        return (data[current] = value);

19      }

20      return data[current];

21    }, vm.$data);

22  },

23

24  // 匹配 v-model

25  model(node, expr, vm) {

26    let fn = this.updater[“modelUpdater”];

27    new Watcher(vm, expr, newValue => {

28      // 给输入框添加一个观察者,如果数据更新了,会触发此方法,将新值付给 input

29      fn(node, newValue);

30    });

31    // 给 input 绑定事件

32    node.addEventListener(“input”, e => {

33      let value = e.target.value; // 获取用户输入的内容

34      this.setValue(vm, expr, value);

35    });

36    let value = this.getValue(vm, expr);

37    fn(node, value);

38  },

39

40  html(node, expr, vm) {

41    //xss

42    let fn = this.updater[“htmlUpdater”];

43    new Watcher(vm, expr, newValue => {

44      console.log(newValue);

45      fn(node, newValue);

46    });

47    let value = this.getValue(vm, expr);

48    fn(node, value);

49  },

50

51  // 获取 {{a}} 中的值

52  getContentValue(vm, expr) {

53    // 遍历表达式 将内容 重新特换成一个完整的内容 返还出去

54    return expr.replace(/{{(.+?)}}/g, (…args) => {

55      return this.getValue(vm, args[1]);

56    });

57  },

58

59  // v-on:click=“change”

60  on(node, expr, vm, eventName) {

61    node.addEventListener(eventName, e => {

62      vm[expr].call(vm, e);

63    });

64  },

65

66  // 可能存在 {{a}} {{b}} 多个样式

67  text(node, expr, vm) {

68    let fn = this.updater[“textUpdater”];

69    let content = expr.replace(/{{(.+?)}}/g, (…args) => {

70      // 给表达式 {{}} 中的值添加一个观察者,如果数据更新了,会触发此方法

71      new Watcher(vm, args[1], () => {

72        fn(node, this.getContentValue(vm, expr)); // 返回一个全新的字符串

73      });

74      return this.getValue(vm, args[1]);

75    });

76    fn(node, content);

77  },

78

79// 给 dom 文本结点赋值数据

80updater: {

81  modelUpdater(node, value) {

82    // 处理指令结点 v-model

83    node.value = value;

84  },

85  textUpdater(node, value) {

86    // 处理文本结点 {{}}

87    node.textContent = value;

88  },

89  htmlUpdater(node, value) {

90    // 处理指令结点 v-html

91    node.innerHTML = value;

92  }

93}

94};

3、塞回页面

此时,我们将渲染好的 fragment 塞回到真实 DOM中就可以正常显示了。

1this.el.appendChild(fragment);

当我们在输入框中输入数据时,相对应的视图上 {{student.name}} 的地方进行实时的更新;当我们通过 vm.$data.student.name 改变数据时,输入框内的数据也会发生改变。

从头到尾我们实现了一个双向绑定。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
img

总结

  • 框架原理真的深入某一部分具体的代码和实现方式时,要多注意到细节,不要只能写出一个框架。

  • 算法方面很薄弱的,最好多刷一刷,不然影响你的工资和成功率😯

  • 在投递简历之前,最好通过各种渠道找到公司内部的人,先提前了解业务,也可以帮助后期优秀 offer 的决策。

  • 要勇于说不,对于某些 offer 待遇不满意、业务不喜欢,应该相信自己,不要因为当下没有更好的 offer 而投降,一份工作短则一年长则 N 年,为了幸福生活要慎重选择!!!

    CodeChina开源项目:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

喜欢这篇文章文章的小伙伴们点赞+转发支持,你们的支持是我最大的动力!

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
[外链图片转存中…(img-jdnPX115-1712263890823)]

总结

  • 框架原理真的深入某一部分具体的代码和实现方式时,要多注意到细节,不要只能写出一个框架。

  • 算法方面很薄弱的,最好多刷一刷,不然影响你的工资和成功率😯

  • 在投递简历之前,最好通过各种渠道找到公司内部的人,先提前了解业务,也可以帮助后期优秀 offer 的决策。

  • 要勇于说不,对于某些 offer 待遇不满意、业务不喜欢,应该相信自己,不要因为当下没有更好的 offer 而投降,一份工作短则一年长则 N 年,为了幸福生活要慎重选择!!!

    CodeChina开源项目:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

喜欢这篇文章文章的小伙伴们点赞+转发支持,你们的支持是我最大的动力!

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

  • 26
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值