深入理解Vue官方文档梳理之全局API


Posted in Javascript onNovember 22, 2017

Vue.extend

配置项data必须为function,否则配置无效。data的合并规则(可以看《Vue官方文档梳理-全局配置》)源码如下:

深入理解Vue官方文档梳理之全局API

传入非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__ 就会发现:

深入理解Vue官方文档梳理之全局API

// 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,这几个比较类似,而且都对应着配置项,会在「选项」中再详细介绍。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
Document对象内容集合(比较全)
Sep 06 Javascript
jQuery在IE下使用未闭合的xml代码创建元素时的Bug介绍
Jan 10 Javascript
JQuery选择器特辑 详细小结
May 14 Javascript
JS实现在线统计一个页面内鼠标点击次数的方法
Feb 28 Javascript
浅谈JavaScript 浏览器对象
Jun 03 Javascript
AngularJS入门教程之模块化操作用法示例
Nov 02 Javascript
AngularJS获取json数据的方法详解
May 27 Javascript
vue.js的手脚架vue-cli项目搭建的步骤
Aug 30 Javascript
360提示[高危]使用存在漏洞的JQuery版本的解决方法
Oct 27 jQuery
vue 设置proxyTable参数进行代理跨域
Apr 09 Javascript
解决Vue-cli npm run build生产环境打包,本地不能打开的问题
Sep 20 Javascript
VUE脚手架的下载和配置步骤详解
Apr 01 Javascript
初识 Vue.js 中的 *.Vue文件
Nov 22 #Javascript
chorme 浏览器记住密码后input黄色背景处理方法(两种)
Nov 22 #Javascript
基于JavaScript实现表格滚动分页
Nov 22 #Javascript
Vue.js表单标签中的单选按钮、复选按钮和下拉列表的取值问题
Nov 22 #Javascript
Vue官方文档梳理之全局配置
Nov 22 #Javascript
vue.js开发实现全局调用的MessageBox组件实例代码
Nov 22 #Javascript
vue在使用ECharts时的异步更新和数据加载详解
Nov 22 #Javascript
You might like
PHP安全配置
2006/12/06 PHP
使用PHP破解防盗链图片的一个简单方法
2014/06/07 PHP
Chrome Web App开发小结
2014/09/04 PHP
php数组分页实现方法
2016/04/30 PHP
smarty的section嵌套循环用法示例
2016/05/28 PHP
浅谈PHP实现大流量下抢购方案
2017/12/15 PHP
用jquery实现的模拟QQ邮箱里的收件人选取及其他效果(一)
2011/01/06 Javascript
自己动手制作jquery插件之自动添加删除行的实现
2011/10/13 Javascript
javascript判断数组内是否重复的方法
2015/04/21 Javascript
Javascript数组Array方法解读
2016/03/13 Javascript
关于Javascript中defer和async的区别总结
2016/09/20 Javascript
原生JS封装animate运动框架的实例
2017/10/12 Javascript
angularJS实现不同视图同步刷新详解
2018/10/09 Javascript
Vue 实现把表单form数据 转化成json格式的数据
2019/10/29 Javascript
微信小程序实现列表的横向滑动方式
2020/07/15 Javascript
[00:32]2018DOTA2亚洲邀请赛iG出场
2018/04/03 DOTA
安装ElasticSearch搜索工具并配置Python驱动的方法
2015/12/22 Python
Python基于二分查找实现求整数平方根的方法
2016/05/12 Python
python中函数总结之装饰器闭包详解
2016/06/12 Python
Python 正则表达式入门(中级篇)
2016/12/07 Python
Python 找到列表中满足某些条件的元素方法
2018/06/26 Python
Python从文件中读取数据的方法讲解
2019/02/14 Python
利用pyuic5将ui文件转换为py文件的方法
2019/06/19 Python
Python 绘制酷炫的三维图步骤详解
2019/07/12 Python
Python3运算符常见用法分析
2020/02/14 Python
Windows下Anaconda安装、换源与更新的方法
2020/04/17 Python
举例讲解Python装饰器
2020/12/24 Python
如何用用Python将地址标记在地图上
2021/02/07 Python
TIME时代杂志台湾总代理:台时亚洲
2018/10/22 全球购物
优秀导游先进事迹材料
2014/01/25 职场文书
教师工作能力自我评价
2015/03/04 职场文书
HTML页面滚动时部分内容位置固定不滚动的实现
2021/04/14 HTML / CSS
Pytest之测试命名规则的使用
2021/04/16 Python
OpenCV3.3+Python3.6实现图片高斯模糊
2021/05/18 Python
python turtle绘制多边形和跳跃和改变速度特效
2022/03/16 Python
漫画「请问您今天要来点兔子吗?」最新杂志彩页公开
2022/03/24 日漫