new Watcher(vm, expOrFn, cb, options),对于这个对应的文件在src/observer/watcher.js
关于这个也看了很多的文章,自己也有写了学习的笔记,不过最后还是决定引用一篇文章,因为大概
的思路也就是这样子,然后再Vue的实现里面还有很多复杂的东西,我也没怎么看懂,但是那些都是一
些辅助的东西,并不是主要的核心功能,看完下面这篇文章即可明白dep和watch和observer的关系
<p style="font-weight: bold;margin-bottom: 10px;color: #FF0000">看这里</p>
<p style="font-weight: bold;margin-bottom: 10px;color: #FF0000">看这里</p>
<p style="font-weight: bold;margin-bottom: 10px;color: #FF0000">看这里</p>
之前还有几个问题剩下的,今天把它看一下
Vue.set = set // 涉及到Vue的数据响应式系统,先保留
Vue.delete = del // 涉及到Vue的数据响应式系统,先保留
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue.set和Vue.delete</title>
</head>
<body>
<div id="app">
<h2>{{msg}}</h2>
<p v-for="v in list">{{v}}</p>
<button v-text="'click me'" @click="changeList()"></button>
<button v-text="'click me'" @click="deleteList()"></button>
</div>
</body>
<script type="text/javascript" src="vue.js"></script>
<script>
/**
* Set a property on an object. Adds the new property and
* triggers change notification if the property doesn't
* already exist.
*/
function set$1 (obj, key, val) {
if (Array.isArray(obj)) { // 如果是数组,则使用splice方法,简单粗暴
obj.length = Math.max(obj.length, key);
obj.splice(key, 1, val);
return val
}
if (hasOwn(obj, key)) { // 如果是obj并且有这个key,直接赋值
obj[key] = val;
return
}
var ob = obj.__ob__; // 获取这个obj的__ob__对象,这个也就是之前说的,经过observer之后会有这个东东挂载在上面
if (obj._isVue || (ob && ob.vmCount)) { // 一些操作上的判断
"development" !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
);
return
}
if (!ob) { // 如果有这个ob,那么则是被observer过的,直接赋值
obj[key] = val;
return
}
defineReactive$$1(ob.value, key, val); // 对这个值进行observer
ob.dep.notify(); // 触发dep的notify方法,就是对应的计算属性和watch的数据更新
return val
}
/**
* Delete a property and trigger change if necessary.
*/
function del (obj, key) {
var ob = obj.__ob__;
if (obj._isVue || (ob && ob.vmCount)) {
"development" !== 'production' && warn(
'Avoid deleting properties on a Vue instance or its root $data ' +
'- just set it to null.'
);
return
}
if (!hasOwn(obj, key)) { // 如果没有则返回
return
}
delete obj[key]; // 删除这个key
if (!ob) { // 没有ob过的则返回,不需要下面的notify了
return
}
ob.dep.notify();
}
// ------------上面是这两个方法对应的源码,这里用的是构建后的源码---------------
/*
vm.$set( target, key, value )
参数:
{Object | Array} target
{string | number} key
{any} value
返回值:设置的值。
用法:
这是全局 Vue.set 的别名。
参考:Vue.set
vm.$delete( target, key )
参数:
{Object | Array} target
{string | number} key
用法:
这是全局 Vue.delete 的别名。
*/
// -----------------上面是官方文档的api的使用说明----------------------------
var vm = new Vue({
el: '#app',
data () {
return {
msg: 'hello Vue',
list: [1,2,3,4,5]
}
},
created () {
},
methods: {
changeList () {
// this.list[2] = 999; // 用这种方法是不会更新视图的
Vue.set(this.list, 2, 999) // 用这种方法ok
},
deleteList () {
// this.list[2] = null; // 用这种方法是不会更新视图的
Vue.delete(this.list, 2) // 用这种方法ok
}
// 在2.1.7版本的Vue中,这两个方法会有一个bug,不过经过测试,在最新版本(2.5.13)中
// 已经修复了,这里就不说出来咯,对应的方法在上面
}
})
</script>
</html>
然后到了这个问题:initExtend(Vue),对应的源码在src/core/global-api/extend.js
// 这里我们用官网的例子来说
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue.extend</title>
</head>
<body>
<div id="mount-point">
</div>
</body>
<script type="text/javascript" src="vue.js"></script>
<script>
// 创建构造器
var Profile = Vue.extend({
template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
data: function () {
return {
firstName: 'Walter',
lastName: 'White',
alias: 'Heisenberg'
}
}
})
// 创建 Profile 实例,并挂载到一个元素上。
// new Profile().$mount('#mount-point')
new Profile({
el: '#mount-point',
data: function () {
return {
firstName: 'Walter from self',
lastName: 'White from self'
}
}
})
// 这里我们看到,冲突的数据会被覆盖了的,但是这种感觉有点奇怪,感觉就是把new Vue变成
// 了new Profile的样子,到现在还是不太明白其中的奥义所在,唯一的一种理解就是这样
// 比如组件化的功能,我们在很多页面要用到Alert组件,Toast组件,那么我们可以事先定义
// 然后在有需要的页面直接的使用new Alert和new Toast即可,但感觉这个功能应该不仅仅如此
</script>
</html>
// --------------下面是方法对应的源码-----------------------------------
export function initExtend (Vue: GlobalAPI) {
/**
* Each instance constructor, including Vue, has a unique
* cid. This enables us to create wrapped "child
* constructors" for prototypal inheritance and cache them.
*/
Vue.cid = 0
let cid = 1
/**
* Class inheritance
*/
Vue.extend = function (extendOptions: Object): Function {
extendOptions = extendOptions || {}
const Super = this // 重点,这个this是Vue的构造函数
const SuperId = Super.cid // 一个编号
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {}) // 缓存的值
if (cachedCtors[SuperId]) { // 是否有缓存过的
return cachedCtors[SuperId]
}
const name = extendOptions.name || Super.options.name // 获取name,不是重点
if (process.env.NODE_ENV !== 'production') { // 一些操作的判断和提示
if (!/^[a-zA-Z][\w-]*$/.test(name)) {
warn(
'Invalid component name: "' + name + '". Component names ' +
'can only contain alphanumeric characters and the hyphen, ' +
'and must start with a letter.'
)
}
}
const Sub = function VueComponent (options) { // 这个Sub就是最后返回的方法
this._init(options) // 就是我们初始化的Vue的步骤,很熟悉的东西
}
Sub.prototype = Object.create(Super.prototype) // 以Vue的构造函数为原型的原型
Sub.prototype.constructor = Sub // 自身的constructor
Sub.cid = cid++ // 静态属性cid,一个编号
Sub.options = mergeOptions( // 合并,很熟悉的东西,就是我们最开始new Vue的时候数据合并的步骤
Super.options,
extendOptions
)
Sub['super'] = Super // 获取Super上面的东西
// allow further extension/mixin/plugin usage
Sub.extend = Super.extend // 同理,获取Super上面的东西
Sub.mixin = Super.mixin // 同理,获取Super上面的东西
Sub.use = Super.use // 同理,获取Super上面的东西
// create asset registers, so extended classes
// can have their private assets too.
config._assetTypes.forEach(function (type) { // 这个也很熟悉,组件注册,过滤器,自定义指令的赋值
Sub[type] = Super[type]
})
// enable recursive self-lookup
if (name) {
Sub.options.components[name] = Sub
}
// keep a reference to the super options at extension time.
// later at instantiation we can check if Super's options have
// been updated.
Sub.superOptions = Super.options 获取各自的options
Sub.extendOptions = extendOptions 获取各自的options
// cache constructor
cachedCtors[SuperId] = Sub 缓存下来
return Sub // 返回这个构造函数
}
}
// 看到了这里,有种感觉就是这个东西把Vue上面的东西都继承下来了,最后返回它,然后new它又
// 调用了Vue的init方法初始化,好吧,懵逼,鉴于官方文档也没说的太详细,这个暂时就只能这么
// 理解了,不过这个应该还有更大的用处的
到了这里大概就剩下渲染的事情了,也就是initRender方法
initLifecycle(vm)
initEvents(vm)
callHook(vm, 'beforeCreate')
initState(vm)
callHook(vm, 'created')
initRender(vm)
主要就是执行vm.$mount方法,这里大概涉及的有:
模板的编译,
生成ast,
生成render,
生成Virtual DOM,
通过snabbdom的方法实现优化,
等等,就是Vue的渲染的操作了
<p style="font-weight: bold;margin-bottom: 10px;color: #FF0000">剩下的几个问题</p>
Vue.nextTick = util.nextTick // 水平有限,看不懂 - -#,dom的异步更新的问题
extend(Vue.options.directives, platformDirectives) // 水平有限,看不懂 - -#,内置的指令directives,v-bind,v-model,v-show
extend(Vue.options.components, platformComponents) // 水平有限,看不懂 - -#,内置的组件,transition组件
Vue.prototype.__patch__ // 水平有限,看不懂 - -#,渲染的方法
compileToFunctions // 水平有限,看不懂 - -#,模板编译的方法
const extendsFrom = child.extends // 水平有限,看不懂 - -#
initProxy(vm) // 水平有限,看不懂 - -#