深入理解Vue官方文档梳理之全局API
vue.extend
配置项data必须为function,否则配置无效。data的合并规则(可以看《vue官方文档梳理-全局配置》)源码如下:
传入非function类型的data(上图中data配置为{a:1}),在合并options时,如果data不是function类型,开发版会发出警告,然后直接返回了parentval,这意味着extend传入的data选项被无视了。
我们知道实例化vue的时候,data可以是对象,这里的合并规则不是通用的吗?注意上面有个if(!vm)的判断,实例化的时候vm是有值的,因此不同于vue.extend,其实下面的注释也做了说明(in a vue.extend merge, both should be function),这也是官方文档为何说data是个特例。
另外官方文档所说的“子类”,是因为vue.extend返回的是一个“继承”vue的函数,源码结构如下:
vue.extend = function (extendoptions) { //*** var super = this; var superid = super.cid; //*** var sub = function vuecomponent(options) { this._init(options); }; sub.prototype = object.create(super.prototype); sub.prototype.constructor = sub; //*** return sub };
vue.nexttick
既然使用vue,当然要沿着数据驱动的方式思考,所谓数据驱动,就是不要直接去操作dom,dom的所有操作完全可以利用vue的各种指令来完成,指令将数据和dom进行了“绑定”,操作数据不仅能实现dom的更新,而且更方便。
如果浏览器支持promise,或者用了promise库(但是对外暴露的必须叫promise,因为源码中的判断为typeof promise !== 'undefined'),nexttick返回的就是promise对象。
vue.nexttick().then(() => { // do sth })
vue执行nexttick的回调采用call的方式cb.call(ctx);ctx就是当前vue实例,因此在回调中可以直接使用this调用实例的配置。
nexttick可以简单理解为将回调放到末尾执行,源码中如果当前不支持promise和mutationobserver,那么会采用settimeout的方式来执行回调,这不就是我们常用的延后执行代码的方式。
if (typeof promise !== 'undefined' && isnative(promise)) { } else if (typeof mutationobserver !== 'undefined' && ( isnative(mutationobserver) || // phantomjs and ios 7.x mutationobserver.tostring() === '[object mutationobserverconstructor]' )) { } else { // fallback to settimeout /* istanbul ignore next */ timerfunc = function () { settimeout(nexttickhandler, 0); }; }
举个例子来实际看下:
<div id="app"> <div ref="dom">{{a}}</div> </div> new vue({ el: '#app', data: { a: 1 }, mounted: function name(params) { console.log('start'); this.$nexttick(function () { console.log('beforechange', this.$refs.dom.textcontent) }) this.a = 2; console.log('change'); this.$nexttick(function () { console.log('afterchange', this.$refs.dom.textcontent) }) console.log('end'); } }) // 控制台依次打印 // start // change // end // beforechange 1 // afterchange 2
你估计会有些纳闷,既然都是最后才执行,那为什么beforechange输出的是1而不是2,这是因为this.a=2背后触发dom更新也是采用nexttick的方式,上面的代码实际执行的顺序是:beforechange>更新dom>afterchange。
vue.set
vue.set( target, key, value ),target不能是 vue 实例,或者 vue 实例的根数据对象,因为源码中做了如下判断:
var ob = (target).__ob__; if (target._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 val }
target._isvue阻止了给vue实例添加属性,ob && ob.vmcount阻止了给vue实例的根数据对象添加属性。
vue.delete
如果vue能检测到delete操作,那么就不会出现这个api。如果一定要用delete来删除$data的属性,那就用vue.delete,否则不会触发dom的更新。
同vue.set,vue.delete( target, key )的target不能是一个 vue 示例或 vue 示例的根数据对象。源码中的阻止方式和vue.set相同。
在2.2.0+ 版本中target若为数组,key则是数组下标。因为vue.delete删除数组实际是用splice来删除,delete虽然能用于删除数组,但位置还在,不能算真正的删除。
var a = [1, 2, 3]; delete a[0]; console.log(a); // [undefined, 2, 3]
vue.use
vue.use 源码比较简单,可以全部贴出来。
vue.use = function (plugin) { var installedplugins = (this._installedplugins || (this._installedplugins = [])); if (installedplugins.indexof(plugin) > -1) { return this } // additional parameters var args = toarray(arguments, 1); args.unshift(this); if (typeof plugin.install === 'function') { plugin.install.apply(plugin, args); } else if (typeof plugin === 'function') { plugin.apply(null, args); } installedplugins.push(plugin); return this };
安装的插件放到了 installedplugins ,安装插件前通过installedplugins.indexof(plugin)来判断插件是否被安装过,进而阻止注册相同插件多次。
插件类型为 object,必须指定 install 属性来安装插件(typeof plugin.install === 'function'),另外插件执行采用plugin.install.apply(plugin, args);,因此 this 访问 object 的其他属性。此处的 args 是由 vue(args.unshift(this);) 和 vue.use 传入的除了 plugin 的其他参数(toarray(arguments, 1),1 表示从 arguments[1] 开始截取)。
vue.use({ a: 1, install: function (vue) { console.log(this.a) // 1 console.log(arguments) // [function vue(options),"a", "b", "c"] } }, 'a', 'b', 'c')
插件类型为 function,安装调用plugin.apply(null, args);,因此在严格模式下插件运行时上下文 this 为 null,非严格模式为 window。
'use strict' vue.use(function plugin() { console.log(this) // null console.log(arguments) // [function vue(options),"a", "b", "c"] }, 'a', 'b', 'c')
vue.compile
和众多 js 模板引擎的原理一样,预先会把模板转化成一个 render 函数,vue.compile 就是来完成这个工作的,目标是将模板(template 或 el)转化成 render 函数。
vue.compile 返回了{render:function,staticrenderfns:array},render 可直接应用于 vue 的配置项 render,而 staticrenderfns 是怎么来的,而且按照官网的例子,vue 还有个隐藏的配置项 staticrenderfns,先来个例子看看。
var compiled = vue.compile( '<div>' + '<header><h1>no data binding</h1></header>' + '<section>{{prop}}</section>' + '</div>' ) console.log(compiled.render.tostring()) console.log(compiled.staticrenderfns.tostring()) // render function anonymous() { with(this) { return _c('div', [_m(0), _c('section', [_v(_s(prop))])]) } } // staticrenderfns function anonymous() { with(this) { return _c('header', [_c('h1', [_v("no data binding")])]) } }
原来没有和数据绑定的 dom 会放到 staticrenderfns 中,然后在 render 中以_m(0)来调用。但是并不尽然,比如上述模板去掉<h1>,staticrenderfns 长度为 0,header 直接放到了 render 函数中。
function anonymous() { with(this) { return _c('div', [_c('header', [_v("no data binding")]), _c('section', [_v(_s(prop))])]) } }
vue.compile 对应的源码比较复杂,上述渲染 <header> 没有放到 staticrenderfns 对应源码的核心判断如下:
// for a node to qualify as a static root, it should have children that // are not just static text. otherwise the cost of hoisting out will // outweigh the benefits and it's better off to just always render it fresh. if (node.static && node.children.length && !( node.children.length === 1 && node.children[0].type === 3 )) { node.staticroot = true; return } else { node.staticroot = false; }
<header> 不符判断条件 !(node.children.length === 1 && node.children[0].type === 3), <header> 有一个子节点 textnode(nodetype=3)。 注释也说明了一个 node 符合静态根节点的条件。
另外官网说明了此方法只在独立构建时有效,什么是独立构建?这个官网做了详细的介绍,不再赘述。对应官网地址:。
仔细观察编译后的 render 方法,和我们自己写的 render 方法有很大区别。但是仍然可以直接配置到 render 配置选项上。那么里面的那些 _c()、_m() 、_v()、_s() 能调用?随便看一个 vue 的实例的 __proto__ 就会发现:
// internal render helpers. // these are exposed on the instance prototype to reduce generated render // code size. vue.prototype._o = markonce; vue.prototype._n = tonumber; vue.prototype._s = tostring; vue.prototype._l = renderlist; vue.prototype._t = renderslot; vue.prototype._q = looseequal; vue.prototype._i = looseindexof; vue.prototype._m = renderstatic; vue.prototype._f = resolvefilter; vue.prototype._k = checkkeycodes; vue.prototype._b = bindobjectprops; vue.prototype._v = createtextvnode; vue.prototype._e = createemptyvnode; vue.prototype._u = resolvescopedslots; vue.prototype._g = bindobjectlisteners;
正如注释所说,这些方法是为了减少生成的 render 函数的体积。
全局 api 还剩 directive、filter、component、mixin,这几个比较类似,而且都对应着配置项,会在「选项」中再详细介绍。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: 群魔共舞,谈笑风生,搞笑图片也!