深入理解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 相关文章推荐
简单的无缝滚动程序-仅几行代码
May 08 Javascript
跨域表单提交状态的变相判断代码
Nov 12 Javascript
Jquery Ajax 学习实例2 向页面发出请求 返回JSon格式数据
Mar 15 Javascript
jquery显示和隐藏div特效实例
Feb 27 Javascript
jQuery的ready方法详解
Nov 27 Javascript
在父页面得到zTree已选中的节点的方法
Feb 12 Javascript
js如何准确获取当前页面url网址信息
Sep 13 Javascript
Vue安装浏览器开发工具的步骤详解
May 12 Javascript
判断“命令按钮”是否被鼠标单击详解
Jul 31 Javascript
node 版本切换的实现
Feb 02 Javascript
解决微信小程序scroll-view组件无横向滚动的问题
Feb 04 Javascript
JS前端监控采集用户行为的N种姿势
Jul 23 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/10/09 PHP
IIS下PHP连接数据库提示mysql undefined function mysql_connect()
2010/06/04 PHP
php用数组返回无限分类的列表数据的代码
2010/08/08 PHP
PHP制作百度词典查词采集器
2015/01/29 PHP
1亿条数据如何分表100张到Mysql数据库中(PHP)
2015/07/29 PHP
Zend Framework动作助手Json用法实例分析
2016/03/05 PHP
PHP版微信第三方实现一键登录及获取用户信息的方法
2016/10/14 PHP
jQuery 打造动态下滑菜单实现说明
2010/04/15 Javascript
轻松创建nodejs服务器(4):路由
2014/12/18 NodeJs
jquery分割字符串的方法
2015/06/24 Javascript
jQuery简单实现上下,左右滑动的方法
2016/06/01 Javascript
使用Web Uploader实现多文件上传
2016/06/08 Javascript
Angularjs结合Bootstrap制作的一个TODO List
2016/08/18 Javascript
微信小程序实战之登录页面制作(5)
2020/03/30 Javascript
详解如何使用webpack+es6开发angular1.x
2017/08/16 Javascript
Vue使用vue-area-linkage实现地址三级联动效果的示例
2018/06/27 Javascript
微信小程序使用npm包的方法步骤
2019/08/13 Javascript
vue 实现路由跳转时更改页面title
2019/11/05 Javascript
vue中音频wavesurfer.js的使用方法
2020/02/20 Vue.js
python使用正则表达式替换匹配成功的组
2017/11/17 Python
pygame游戏之旅 添加游戏界面按键图形
2018/11/20 Python
python hash每次调用结果不同的原因
2019/11/21 Python
Python CSV文件模块的使用案例分析
2019/12/21 Python
python GUI库图形界面开发之PyQt5结合Qt Designer创建信号与槽的详细方法与实例
2020/03/08 Python
Python接口测试结果集实现封装比较
2020/05/01 Python
Python 如何对文件目录操作
2020/07/10 Python
携程英文网站:Trip.com
2017/02/07 全球购物
业务员薪酬管理制度
2014/01/15 职场文书
服装促销活动方案
2014/02/23 职场文书
文明工地标语
2014/06/16 职场文书
行政主管岗位职责范本
2015/04/09 职场文书
2016年11月份红领巾广播稿
2015/12/21 职场文书
详解mysql三值逻辑与NULL
2021/05/19 MySQL
Python基础之元编程知识总结
2021/05/23 Python
vue ref如何获取子组件属性值
2022/03/31 Vue.js
Go语言编译原理之源码调试
2022/08/05 Golang