前言

我最近在写 Vue 进阶的内容。在这个过程中,有些人问我看 Vue 源码需要有哪些准备吗?所以也就有了这篇计划之外的文章。

当你想学习 Vue 源码的时候,需要有扎实的 JavaScript 基础,下面罗列的只是其中的一部分比较具有代表性的知识点。如果你还不具备 JavaScript 基础的话,建议不要急着看 Vue 源码,这样你会很容易放弃的。

我会从以下 7 点来展开:

  1. Flow 基本语法

  2. 发布/订阅模式

  3. Object.defineProperty

  4. ES6+ 语法

  5. 原型链、闭包

  6. 函数柯里化

  7. event loop

必要知识储备

需要注意的是这篇文章每个点不会讲的特别详细,我这里就是把一些知识点归纳一下。每个详细的点仍需自己花时间学习。

Flow 基本语法

相信看过 Vue、Vuex 等源码的人都知道它们使用了 Flow 静态类型检查工具。

我们知道 JavaScript 是弱类型的语言,所以我们在写代码的时候容易出现一些始料未及的问题。也正是因为这个问题,才出现了 Flow 这个静态类型检查工具。

这个工具可以改变 JavaScript 是弱类型的语言的情况,可以加入类型的限制,提高代码质量。

// 未使用 Flow 限制
function sum(a, b) {
return a + b;
}
// 使用 Flow 限制 a b 都是 number 类型。
function sum(a: number, b:number) {
return a + b;
}

基础检测类型

Flow 支持原始数据类型,有如下几种:

boolean
number
string
null
void( 对应 undefined )

在定义变量的同时在关键的地方声明类型,使用如下:

let str:string = 'str';
// 重新赋值
str = 3 // 报错

复杂类型检测

Flow 支持复杂类型检测,有如下几种:

Object
Array
Function
自定义的 Class

需要注意直接使用 flow.js,JavaScript 是无法在浏览器端运行的,必须借助 babel 插件,vue 源码中使用的是 babel-preset-flow-vue 这个插件,并且在 babelrc 进行配置。

详细的 Flow 语法可以看以下资料:

这里推荐两个资料

  1. 官方文档:https://flow.org/en/

  2. Flow 的使用入门:https://zhuanlan.zhihu.com/p/26204569

发布/订阅模式

我们知道 Vue 是内部是实现了双向绑定机制,使得我们不用再像从前那样还要自己操作 DOM 了。

其实 Vue 的双向绑定机制采用数据劫持结合发布/订阅模式实现的: 通过 Object.defineProperty() 来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

我发现有的人把观察者模式和发布/订阅模式混淆一谈,其实订阅模式有一个调度中心,对订阅事件进行统一管理。而观察者模式可以随意注册事件,调用事件。

我画了一个大概的流程图,用来说明观察者模式和发布/订阅模式。如下:

这块我会在接下的文章中详细讲到,这里先给出一个概念,感兴趣的可以自己查找资料,也可等我的文章出炉。

其实我们对这种模式再熟悉不过了,但可能你自己也没发现:

let div = document.getElementById('#div');
div.addEventListener('click', () => {
console.log("div 被点击了一下")
})

可以思考下上面的事件绑定执行的一个过程,你应该会有共鸣。

函数柯里化

数据双向绑定基础:Object.defineProperty()

一、数据属性

数据属性包含一个数据值的位置。这个位置可以读取和写入值。数据属性有 4 个描述他行为的特性:

属性 描述
Configurable 能否用 delete 删除属性从而重新定义属性。默认为 true
Enumerable 能否通过 for-in 遍历,即是否可枚举。默认为 true
Writable 是否能修改属性的值。默认为 true
Value 包含这个属性的数据值,读写属性的时候其实就在这里读写。默认为 undefined

如果你想要修改上述 4 个默认的数据属性,就需要使用 ECMAScript 的 Object.defineProperty() 方法。

该方法包含3个参数:属性所在的对象,属性名,描述符对象。描述符对象的属性必须在上述 4 个属性中。

var person = {
name: '',
};
// 不能修改属性的值
Object.defineProperty(person, "name",{
writable: false,
value: "小生方勤"
});
console.log(person.name); // "小生方勤"
person.name = "方勤";
console.log(person.name); // "小生方勤"

二、访问器属性

访问器属性不包含数据值,他们包含一对 gettersetter 函数(非必须)。在读写访问器属性的值的时候,会调用相应的 gettersetter 函数,而我们的 vue 就是在 gettersetter 函数中增加了我们需要的操作。

需要注意的是【value 或 writable】一定不能和【get 或 set】共存。

访问器属性有以下 4 个特性:

特性 描述
Configurable 能否用 delete 删除属性从而重新定义属性。默认为 true
Enumerable 能否通过 for-in 遍历,即是否可枚举。默认为 true
get 读取属性时调用的函数,默认 undefined
set 写入属性时调用的函数,默认 undefined

接下来给个例子:

var person = {
_name : "小生方勤"
};
Object.defineProperty(person, "name", {
//注意 person 多定义了一个 name 属性
set: function(value){
this._name = "来自 setter : " + value;
},
get: function(){
return "来自 getter : " + this._name;
}
});
console.log( person.name ); // 来自 getter : 小生方勤
person.name = "XSFQ";
console.log( person._name ); // 来自 setter : XSFQ
console.log( person.name ); // 来自 getter : 来自 setter : XSFQ

如果之前都不清楚有 Object.defineProperty() 方法,建议你看《JavaScript 高级程序设计》的 139 - 144 页。

额外讲讲 Object.create(null)

我们在源码随处可以 this.set=Object.create(null) 这样的赋值。为什么这样做呢?这样写的好处就是不需要考虑原型链上的属性,可以真正的创建一个纯净的对象。

首先 Object.create 可以理解为继承一个对象,它是 ES5 的一个特性,对于旧版浏览器需要做兼容,基本代码如下:

if (!Object.create) {
Object.create = function (o) {
function F() {} // 定义了一个隐式的构造函数
F.prototype = o;
return new F(); // 其实还是通过new来实现的
};
}

ES6+ 语法

其实这点应该是默认你需要知道的,不过鉴于之前有人问过我一些相关的问题,我稍微讲一下。

exportdefault 和 export 的区别

  1. 在一个文件或模块中 export 可以有多个,但 exportdefault 仅有一个

  2. 通过 export 方式导出,在导入时要加 { },而 exportdefault 则不需要

1.export
//a.js
export const str = "小生方勤";
//b.js
import { str } from 'a'; // 导入的时候需要花括号
2.export default
//a.js
const str = "小生方勤";
export default str;
//b.js
import str from 'a'; // 导入的时候无需花括号

exportdefaultconsta=1; 这样写是会报错的哟。

箭头函数

这个一笔带过:

  1. 箭头函数中的 this 指向是固定不变的,即是在定义函数时的指向

  2. 而普通函数中的 this 指向时变化的,即是在使用函数时的指向

class 继承

Class 可以通过 extends 关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。

class staff {
constructor(){
this.company = "ABC";
this.test = [1,2,3];
}
companyName(){
return this.company;
}
}
class employee extends staff {
constructor(name,profession){
super();
this.employeeName = name;
this.profession = profession;
}
}
// 将父类原型指向子类
let instanceOne = new employee("Andy", "A");
let instanceTwo = new employee("Rose", "B");
instanceOne.test.push(4);
// 测试
console.log(instanceTwo.test); // [1,2,3]
console.log(instanceOne.companyName()); // ABC
// 通过 Object.getPrototypeOf() 方法可以用来从子类上获取父类
console.log(Object.getPrototypeOf(employee) === staff)
// 通过 hasOwnProperty() 方法来确定自身属性与其原型属性
console.log(instanceOne.hasOwnProperty('test')) // true
// 通过 isPrototypeOf() 方法来确定原型和实例的关系
console.log(staff.prototype.isPrototypeOf(instanceOne)); // true

super 关键字,它在这里表示父类的构造函数,用来新建父类的 this 对象。

  1. 子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错。这是因为子类没有自己的 this 对象,而是继承父类的 this 对象,然后对其进行加工。

  2. 只有调用 super 之后,才可以使用 this 关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有 super 方法才能返回父类实例。

`super` 虽然代表了父类 `A` 的构造函数,但是返回的是子类 `B` 的实例,即` super` 内部的 `this ` 指的是 `B`,因此 `super()` 在这里相当于 A.prototype.constructor.call(this)

ES5 和 ES6 实现继承的区别

ES5 的继承,实质是先创造子类的实例对象 this,然后再将父类的方法添加到 this 上面( Parent.apply(this))。this (所以必须先调用 super() 方法),然后再用子类的构造函数修改 this

proxy

对最新动态了解的人就会知道,在下一个版本的 Vue 中,会使用 proxy 代替 Object.defineProperty 完成数据劫持的工作。

尤大说,这个新的方案会使初始化速度加倍,于此同时内存占用减半。

proxy 对象的用法:

var proxy = new Proxy(target, handler);

new Proxy() 即生成一个 Proxy 实例。target 参数表示所要拦截的目标对象,handler 参数也是一个对象,用来定制拦截行为。

var proxy = new Proxy({}, {
get: function(obj, prop) {
console.log('get 操作')
return obj[prop];
},
set: function(obj, prop, value) {
console.log('set 操作')
obj[prop] = value;
}
});
proxy.num = 2; // 设置 set 操作
console.log(proxy.num); // 设置 get 操作 // 2

除了 get 和 set 之外,proxy 可以拦截多达 13 种操作。

注意,proxy 的最大问题在于浏览器支持度不够,IE 完全不兼容。

倘若你基本不了解 ES6, 推荐下面这个教程:

阮一峰 ECMAScript 6 入门:http://es6.ruanyifeng.com/

原型链、闭包

原型链

因为之前我特意写了一篇文章来解释原型链,所以这里就不在讲述了:

原型链:https://juejin.im/post/5c335940f265da610e804097

闭包

这里我先放一段 Vue 源码中的 once 函数。这就是闭包调用 —— 函数作为返回值:

/**
* Ensure a function is called only once.
*/
export function once (fn: Function): Function {
let called = false
return function () {
if (!called) {
called = true
fn.apply(this, arguments)
}
}
}

这个函数的作用就是确保函数只调用一次。

为什么只会调用一次呢? 因为函数调用完成之后,其执行上下文环境不会被销毁,所以 called 的值依然在那里。

闭包到底是什么呢。《JavaScript 高级程序设计》的解释是:

闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。

简单讲,闭包就是指有权访问另一个函数作用域中的变量的函数。

给两段代码,如果你知道他们的运行结果,那么说明你是了解闭包的:

// 第一段
var num = 20;
function fun(){
var num = 10;
return function con(){
console.log( this.num )
}
}
var funOne = fun();
funOne(); // 20
// 第二段
var num = 20;
function fun(){
var num = 10;
return function con(){
console.log( num )
}
}
var funOne = fun();
funOne(); // 10

函数柯里化

所谓"柯里化",就是把一个多参数的函数,转化为单参数函数。

先说说我之前遇到过得一个面试题:

如何使 add(2)(3)(4)() 输出 9

在那次面试的时候,我还是不知道柯里化这个概念的,所以当时我没答上。后来我才知道这可以用函数柯里化来解,即:

function add(num){
var sum=0;
sum= sum+num;
return function tempFun(numB){
if(arguments.length===0){
return sum;
}else{
sum= sum+ numB;
return tempFun;
}
}
}

那这和 Vue 有什么关系呢?当然是有关系的:

我们是否经常这样写判断呢?

if( A ){
// code
}else if( B ){
// code
}

这个写法没什么问题,可是在重复的出现这种相同的判断的时候。这个就显得有点不那么智能了。这个时候函数柯里化就可以排上用场了。

因为 Vue 可以在不同平台运行,所以也会存在上面的那种判断。这里利用柯里化的特点,通过 createPatchFunction 方法把一些参数提前保存,以便复用。

// 这样不用每次调用 patch 的时候都传递 nodeOps 和 modules
export function createPatchFunction (backend) {
// 省略好多代码
return function patch (oldVnode, vnode, hydrating, removeOnly) {
// 省略好多代码
}
}

event loop

四个概念:

  1. 同步任务:即在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务

  2. 异步任务:指的是不进入主线程,某个异步任务可以执行了,该任务才会进入主线程执行

  3. macrotask:主要场景有 主代码块、setTimeout、setInterval 等

  4. microtask:主要场景有 Promise、process.nextTick 等

这一点网上教程已经很多了,再因为篇幅的问题,这里就不详细说了。

推荐一篇文章,说的很细致:

JavaScript 执行机制:https://juejin.im/post/59e85eebf265da430d571f89#heading-4

总结

这篇文章讲到这里就结束了。不过有一点我需要在说一篇,这篇文章的定位并不是面面俱到的将所有知识都讲一遍,这不现实我也没这个能力。

我只是希望通过这篇文章告诉大家一个观点,要想看源码,一些必备的 JavaScript 基础知识必须要扎实,否则你会举步维艰。

愿你每天都有进步。

原创系列推荐



4. 
5. 
6. 
7. 
点这,与大家一起分享本文吧~

【Vuejs】350- 学习 Vue 源码的必要知识储备的更多相关文章

  1. 学习 vue 源码 -- 响应式原理

    概述 由于刚开始学习 vue 源码,而且水平有限,有理解或表述的不对的地方,还请不吝指教. vue 主要通过 Watcher.Dep 和 Observer 三个类来实现响应式视图.另外还有一个 sch ...

  2. 一起学习vue源码 - Object的变化侦测

    作者:小土豆biubiubiu 博客园:www.cnblogs.com/HouJiao/ 掘金:https://juejin.im/user/58c61b4361ff4b005d9e894d 简书:h ...

  3. 手牵手,从零学习Vue源码 系列一(前言-目录篇)

    系列文章: 手牵手,从零学习Vue源码 系列一(前言-目录篇) 手牵手,从零学习Vue源码 系列二(变化侦测篇) 手牵手,从零学习Vue源码 系列三(虚拟DOM篇) 陆续更新中... 预计八月中旬更新 ...

  4. 手牵手,从零学习Vue源码 系列二(变化侦测篇)

    系列文章: 手牵手,从零学习Vue源码 系列一(前言-目录篇) 手牵手,从零学习Vue源码 系列二(变化侦测篇) 陆续更新中... 预计八月中旬更新完毕. 1 概述 Vue最大的特点之一就是数据驱动视 ...

  5. 一起学习vue源码 - Vue2.x的生命周期(初始化阶段)

    作者:小土豆biubiubiu 博客园:https://www.cnblogs.com/HouJiao/ 掘金:https://juejin.im/user/58c61b4361ff4b005d9e8 ...

  6. 学习Vue源码前的几项必要储备(二)

    7项重要储备 Flow 基本语法 发布/订阅模式 ES6+ 语法 原型链.闭包 函数柯里化 event loop 接上讲 聊到了ES6的几个重要语法,加下来到第四点继续开始. 4.原型链.闭包 原型链 ...

  7. 学习Vue源码前的几项必要储备(一)

    从接下来的一段时间里,Mg要进行阅读源码的工作.再阅读源码前,梳理一下准备工作. 7项重要储备 Flow 基本语法 发布/订阅模式 ES6+ 语法 原型链.闭包 函数柯里化 event loop 1. ...

  8. Vue源码学习(一):调试环境搭建

    最近开始学习Vue源码,第一步就是要把调试环境搭好,这个过程遇到小坑着实费了点功夫,在这里记下来 一.调试环境搭建过程 1.安装node.js,具体不展开 2.下载vue项目源码,git或svn等均可 ...

  9. Vue源码解析(一):入口文件

    在学习Vue源码之前,首先要做的一件事情,就是去GitHub上将Vue源码clone下来,目前我这里分析的Vue版本是V2.5.21,下面开始分析: 一.源码的目录结构: Vue的源码都在src目录下 ...

随机推荐

  1. QQ是怎样创造出来的?——解密好友系统的设计

    本篇介绍笔者接触的第一个后台系统,从自身见闻出发,因此涉及的内容相对比较基础,后台大牛请自觉略过. 什么是好友系统? 简单的说,好友系统是维护用户好友关系的系统.我们最熟悉的好友系统案例当属QQ,实际 ...

  2. 微擎 manifest.xml

    微擎 manifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns= ...

  3. ESP8266 智能配网 断电重连

    ESP8266 智能配网 断电重连 #include <ESP8266WiFi.h> bool autoConfig() { WiFi.begin(); for (int i = 0; i ...

  4. 基于.NetStandard的简易EventBus实现-基础实现

    一.问题背景 最近离职来到了一家新的公司,原先是在乙方工作,这回到了甲方,在这一个月中,发现目前的业务很大一部分是靠轮询实现的,例如:通过轮询判断数据处于B状态了,则轮询到数据后执行某种动作,这个其实 ...

  5. PostgreSQL空间数据库创建备份恢复(PostGIS vs ArcGIS)

    梯子 PostGIS创建备份恢复ArcGIS创建备份恢复 PostGIS 创建 安装就不必介绍了,windows下使用安装工具Application Stack Builder,选择空间扩展PostG ...

  6. secureCRT安装与激活

    SecureCRT安装及激活方式 百度网盘地址: SecureCRT及激活软件的地址: 1. 安装secureCRT 百度网盘下载,点击scrt814-x64.exe,按照提示安装secureCRT, ...

  7. mysql基础之数据类型

    一.整型 分为:tinyint .smallint .mediumint .int .bigint 常用的 分为以下三项: tinyint. smallint.int 数据类型 存储范围 字节 tin ...

  8. ZeroC ICE的远程调用框架 class与interface

    我们在ice文件中定义的class或interface,slice都会为我们生成stub存根类和skeleton骨架类.在这里要注意slice并没有分别生成两份单独用在客户端或服务端的接口给开发分发. ...

  9. elasticsearch安装踩坑记

    ES的安装与启动时问题解决 环境: ​ 系统环境:CentOS7 ​ JDK:jdk-8u131-linux-x64.tar.gz 不可以通过Root用户来启动ES 通过上面的错误提示可以看出,can ...

  10. 最省钱的爬虫解决方案,比IP代理更划算

    现状: 1.网上提供代理IP池的解决方案非常多,价格也有高有低,包天/月/年的都有,品质都要靠自己去尝试. 2.试过之后,发现成本相对高,每月要花200~300元, 所以希望研究一下是否有更性价比高的 ...