写于vue3.0发布前夕的helloworld
前言:
vue3.0马上要来了,于今昔写一篇vue将一个字符串hellowrold渲染于页面的过程,慰藉我这几个月写vue的‘枯燥’。
源码版本是2.6.10。
开始:
我们的模板足够简单:
<div id="app">{{msg}}</div>
vue实例的配置也足够简单:
new Vue({
el:'#app',
data:function(){
return{
msg: 'hello,world'
}
}
})
下面带着配置进入vue的构造函数:
function Vue (options) {
if (!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword');
}
this._init(options);
}
构造函数足够简单,判断了一下是否用new调用,然后进入实例的_init_方法:
Vue.prototype._init = function (options) {
var vm = this;
// a uid
vm._uid = uid$3++;
var startTag, endTag;
/* istanbul ignore if */
if (config.performance && mark) {
startTag = "vue-perf-start:" + (vm._uid);
endTag = "vue-perf-end:" + (vm._uid);
mark(startTag);
} // a flag to avoid this being observed
vm._isVue = true;
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options);
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
}
/* istanbul ignore else */
{
initProxy(vm);
}
// expose real self
vm._self = vm;
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, 'beforeCreate');
initInjections(vm); // resolve injections before data/props
initState(vm);
initProvide(vm); // resolve provide after data/props
callHook(vm, 'created'); /* istanbul ignore if */
if (config.performance && mark) {
vm._name = formatComponentName(vm, false);
mark(endTag);
measure(("vue " + (vm._name) + " init"), startTag, endTag);
} if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
};
}
init函数一进来,先使用vm保存了this,然后给组件定义了一个_uid属性,每初始化一个组建这个东西就+1从0开始,下来一个
config.performance && mark 这个属性是用来记录vue的性能的相关参数,因为是根组建,所以下来的if-else走else分支,然后开始mergeOptions, mergeOptions其实就是将vue实例上缺省的属性设置成了默认值,然后merge之后我们的option长这个亚子:
"{"components":{},"directives":{},"filters":{},"beforeCreate":[null],"destroyed":[null],"el":"#app"}"
接着开始proxy:
initProxy = function initProxy (vm) {
if (hasProxy) {
// determine which proxy handler to use
var options = vm.$options;
var handlers = options.render && options.render._withStripped
? getHandler
: hasHandler;
vm._renderProxy = new Proxy(vm, handlers);
} else {
vm._renderProxy = vm;
}
这个hasProxy是用于检测当前环境是否支持Proxy,如果支持之后就进行代理操作,三元运算符表示如果当前实例上已经有render就用getHandler代理操作,否则就用hasHandler,因为我们没有render故而得到了hasHandler代理操作:
var hasHandler = {
has: function has (target, key) {
var has = key in target;
var isAllowed = allowedGlobals(key) ||
(typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data));
if (!has && !isAllowed) {
if (key in target.$data) { warnReservedPrefix(target, key); }
else { warnNonPresent(target, key); }
}
return has || !isAllowed
}
};
proxy就不用细讲了,以上的代理的作用就是检测当前vm上有某个key时,也就是 key in vm时,proxy代理了这个操作。具体细节就是 先检测当前vm上有无此属性,然后isAllowed 这个值呢 由allowedGlobals产生,allowedGlobals呢是一个由全局对象或着js语言关键词组成的闭包map函数,长这样:
首先是关键字部分:
var allowedGlobals = makeMap(
'Infinity,undefined,NaN,isFinite,isNaN,' +
'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' +
'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' +
'require' // for Webpack/Browserify
);
makeMap就是用这个字符串产生一个map对象,然后返回一个闭包函数,makeMap长这样:
function makeMap (
str,
expectsLowerCase
) {
var map = Object.create(null);
var list = str.split(',');
for (var i = 0; i < list.length; i++) {
map[list[i]] = true;
}
return expectsLowerCase
? function (val) { return map[val.toLowerCase()]; }
: function (val) { return map[val]; }
}
回到has那个方法里,也就是说如果现在key是一个关键字那么就不用执行后面的东西啦,此时isAllowed就是true,否则,如果key是个字符串而且以_开头,而且vm.$data无法访问,那么此时isAllowed就是false啦。下来那个if就是说如果vm上没有属性key,并且isAllowed是false,就会执行下边的两个警告。最后返回结果。然后代理完成,并在当前vm实例上绑定了一个属性_renderProxy引用当前经过proxy之后的vm。接下来进入各种初始化过程,包含生命周期钩子函数,事件,render,然后在调用了第一个生命周期钩子,beforeCreate,随后初始化injection,state,以及provider。injection和provider是vue的依赖注入机制,这里我们不需要细讲,我们着重进入initState:
function initState (vm) {
vm._watchers = [];
var opts = vm.$options;
if (opts.props) { initProps(vm, opts.props); }
if (opts.methods) { initMethods(vm, opts.methods); }
if (opts.data) {
initData(vm);
} else {
observe(vm._data = {}, true /* asRootData */);
}
if (opts.computed) { initComputed(vm, opts.computed); }
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch);
}
}
一进来,先初始化了一个_watchers,然后开始初始化props和methods,因为这里我们没有定义这两个东西所以直接来到initData:
function initData (vm) {
var data = vm.$options.data;
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {};
if (!isPlainObject(data)) {
data = {};
warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
);
}
// proxy data on instance
var keys = Object.keys(data);
var props = vm.$options.props;
var methods = vm.$options.methods;
var i = keys.length;
while (i--) {
var key = keys[i];
{
if (methods && hasOwn(methods, key)) {
warn(
("Method \"" + key + "\" has already been defined as a data property."),
vm
);
}
}
if (props && hasOwn(props, key)) {
warn(
"The data property \"" + key + "\" is already declared as a prop. " +
"Use prop default value instead.",
vm
);
} else if (!isReserved(key)) {
proxy(vm, "_data", key);
}
}
// observe data
observe(data, true /* asRootData */);
}
首先拿到data,(敲黑板,这里拿到data之后,还会在当前实例上声明一个_data用于保存当前组建得data数据,这个_data后期会代理vm上访问data里得值)。然后进入while循环,如果data中某个key值与props或者methods上的key重复了,会予以警告,否则进入另一个代理程序,proxy:
function proxy (target, sourceKey, key) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
};
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val;
};
Object.defineProperty(target, key, sharedPropertyDefinition);
}
这个代理呢实际就是将vm.key 的get和set代理到了 vm._data.key上,往后在vue实例中使用 this.msg = 'balabal'时,实际上是将这个值set到了this,_data.msg上。这是后话。代理完成之后,来到了observe:
/**
* Attempt to create an observer instance for a value,
* returns the new observer if successfully observed,
* or the existing observer if the value already has one.
*/
function observe (value, asRootData) {
if (!isObject(value) || value instanceof VNode) {
return
}
var ob;
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__;
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value);
}
if (asRootData && ob) {
ob.vmCount++;
}
return ob
}
入口处的判断表明如果value是原始值或者是VNode的实例时不予以ovserve的。随后如果此对象已经被observe过了的话会有一个'__ob__'属性引用了observe当前对象之后的结果,如果有ob就用以前已经存在的否则经过一系列判断之后进入Observer,这些判断条件包括一个全局变量shouldObserve声明的默认值就是true,一个是否时服务端渲染的函数这里当然是true,然后判断是否为对象或者数组,这里value就是我们data的原始值,所以为true,然后判断当前对象是否可扩展,of course是ture,然后下边这个属性因为现在还没有,取反就是true,然后进入Observer:
var Observer = function Observer (value) {
this.value = value;
this.dep = new Dep();
this.vmCount = 0;
def(value, '__ob__', this);
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods);
} else {
copyAugment(value, arrayMethods, arrayKeys);
}
this.observeArray(value);
} else {
this.walk(value);
}
};
这里,observe实例先保存了传入的value,然后会有一个dep实例生成,然后下来定义了一个observe 判断时会用到的属性__ob__,接着判断是数组,还是对象,这里我们的data是一个对象走walk:
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
Observer.prototype.walk = function walk (obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
defineReactive$$1(obj, keys[i]);
}
};
walk遍历对象,每一个键值,使用defineReactive$$1监听其get,set方法:
/**
* Define a reactive property on an Object.
*/
function defineReactive$$1 (
obj,
key,
val,
customSetter,
shallow
) {
var dep = new Dep(); var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return
} // cater for pre-defined getter/setters
var getter = property && property.get;
var setter = property && property.set;
if ((!getter || setter) && arguments.length === 2) {
val = obj[key];
} var childOb = !shallow && observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
var value = getter ? getter.call(obj) : val;
if (Dep.target) {
dep.depend();
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value
},
set: function reactiveSetter (newVal) {
var value = getter ? getter.call(obj) : val;
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (customSetter) {
customSetter();
}
// #7981: for accessor properties without setter
if (getter && !setter) { return }
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify();
}
});
}
defineReactive$$1一进来也是生成了一个dep对象,这里看下dep长什么亚子:
{"id":3,"subs":[]}
基本属性是一个id,还有一个subs,subs里会保存依赖收集时对应组建的watcher,这个我们下面会讲到,然后原型方法上是一组关于subs的操作方法,不再细讲。接着取到属性描述符,判断是否configurable,取得已经事先配置好的getter setter,然后如果是非shallow模式,会继续observe,这里我们的val是一个字符串,非对象,所以observe会直接返回,然后我们继续走到Object.defineProperty这里,对于data上的属性,都成为响应式属性,即get方法收集依赖,(他里边这个Dep.target就是收集依赖的时候的watcher实例),set唤起更新程序。设置完毕,层层返回直到observe函数里。返回ob对象。然后observe完毕。继续返回到initState函数里,下来initState函数会继续初始化computed和watch属性,我们代码里没有设置直接返回到最开始的init函数里,随后在当前vue实例初始化完毕之后,created钩子会触发。
写于vue3.0发布前夕的helloworld的更多相关文章
- VUE3.0发布,自己搞个文档网站
9月19日,尤大神发表了VUE3.0版本的演说,强大且震撼,这两天一直在找网站文档,可能还未被百度收录,未找到文档网站.后来在github上面找到了中文代码. 地址为:https://github.c ...
- 预计2019年发布的Vue3.0到底有什么不一样的地方?
摘要: Vue 3.0预览. 原文:预计今年发布的Vue3.0到底有什么不一样的地方? 作者:小肆 微信公众号:技术放肆聊 Fundebug经授权转载,版权归原作者所有. 还有几个月距离 vue2 的 ...
- 把酒言欢话聊天,基于Vue3.0+Tornado6.1+Redis发布订阅(pubsub)模式打造异步非阻塞(aioredis)实时(websocket)通信聊天系统
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_202 "表达欲"是人类成长史上的强大"源动力",恩格斯早就直截了当地指出,处在蒙昧时代即低 ...
- 纯小白入手 vue3.0 CLI - 3.3 - 路由的导航守卫
vue3.0 CLI 真小白一步一步入手全教程系列:https://www.cnblogs.com/ndos/category/1295752.html 尽量把纷繁的知识,肢解重组成为可以堆砌的知识. ...
- 纯小白入手 vue3.0 CLI - 2.1 - 组件 ( component )
vue3.0 CLI 真小白入手全教程系列:https://www.cnblogs.com/ndos/category/1295752.html 我的 github 地址 - vue3.0Study ...
- 快速进阶Vue3.0
在2019.10.5日发布了Vue3.0预览版源码,但是预计最早需要等到 2020 年第一季度才有可能发布 3.0 正式版. 可以直接看 github源码. 新版Vue 3.0计划并已实现的主要架构改 ...
- Vue3实战系列:Vue3.0 + Vant3.0 搭建种子项目
最近在用 Vue3 写一个开源的商城项目,开源后让大家也可以用现成的 Vue3 大型商城项目源码来练练手,目前处于开发阶段,过程中用到了 Vant3.0,于是就整理了这篇文章来讲一下如何使用 Vue3 ...
- Rubinius 2.0 发布,Ruby 虚拟机
Rubinius 2.0 发布了,官方发行说明请看这里. Rubinius是一个运行Ruby程序的虚拟机,其带有Ruby的核心库. Rubinius的设计决定了其调试功能的强大,使得在运行时常规的Ru ...
- Restful.Data v2.0发布,谢谢你们的支持和鼓励
v1.0发布后,承蒙各位博友们的热心关注,也给我不少意见和建议,在此我真诚的感谢 @冰麟轻武 等朋友,你们的支持和鼓励,是这个开源项目最大的推动力. v2.0在除了细枝末节外,在功能上主要做了一下更新 ...
- 开源搜索引擎Iveely 0.7.0发布,不一样,那就让他不一样!
2012年08月05日,Iveely Search Engine 0.1.0发布,今天,怀着对于未来的追求,终于,0.7.0如期和大家见面了,7个版本,历时2年4个月,感谢大家的支持,感谢我不离不弃的 ...
随机推荐
- mybatis:自定义映射关系resultMap
创建表t_emp 定义实体类 package org.example.entity; public class Emp { private Integer empId; private String ...
- Solution Set - NOIP2022
种花 枚举 C 或者 F 最左边的那一竖,考虑对于每一个这一竖上的全 \(0\) 区间 \([l,r]\) 求答案. 记每个点向右延伸最多延伸到 \(L_{i,j}\),对于 C 的情况,枚举列 \( ...
- 一台服务器部署ShareWAF,后面接多台Web服务器,该如何配置?
ShareWAF做为WAF,可以不只是WAF,还可以充当负载或路由的角色. 比如可以有这样一种部署架构: 在此结构中,ShareWAF部署于一台服务器,后面接多台独立的WEB服务器. ShareWAF ...
- 多个pie环形图 逆时针旋转
效果图如下 代码如下 data = [ { name: "经济目的", value: 754, }, { name: "网络安全爱好者", value: 61 ...
- 用到的jar包作用随笔,吼吼
名称 版本 说明 spring spring.jar(2.5) spring基础包 公司基础包 isskill-pro0.7.1.2.jar(0.7.1.2) 包含 ...
- etcdctl 安装与使用
介绍 etcdctl是一个提供简洁命令的etcd客户端,使用etcdctl可以直接和etcd服务打交道,对etcd中的键值对进行增删改查. 安装etcdctl 下载etcdctl工具 下载地址:etc ...
- springboot项目的创建和兼容jsp和注解开发,详细有效(注解和配置两种)
如果这篇博客能给你带来帮助不胜荣幸,如果有不对的地方,都是码农欢迎指正.直接进入正题 在这里首先我要声明一下我个人的感觉如果你会ssm(spring+mybatis+springmvc)的话,那么你可 ...
- Visual Studio Code 使用总结
记录一下个人在使用 VS Code 中的一些插件和设置. 该配置在编写 vue + iview 项目时使用. 文件路径 用户文件路径:%AppData%/Code/User 用户设置:sett ...
- RabbitMQ-01-使用Java进行简单消息发送与接收
前言 这里使用手动管理jar与使用Maven管理jar两种方式,分别演示消息的发送和接收. 手动管理jar实现消息发送与接收 添加jar amqp-client-5.7.1.jar slf4j-api ...
- QML与python互相通信
解决python与QML的通信问题: QML中直接调用python函数 python发送信号,QML响应信号并进行相应处理 py文件 # This Python file uses the follo ...