深入理解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 相关文章推荐
两种方法基于jQuery实现IE浏览器兼容placeholder效果
Oct 14 Javascript
基于Bootstrap+jQuery.validate实现Form表单验证
Dec 16 Javascript
jQuery使用CSS()方法给指定元素同时设置多个样式
Mar 26 Javascript
深入解析JavaScript的闭包机制
Oct 20 Javascript
EasyUI在表单提交之前进行验证的实例代码
Jun 24 Javascript
微信小程序动态添加分享数据
Jun 14 Javascript
easyui-datagrid开发实践(总结)
Aug 02 Javascript
浅析Vue实例以及生命周期
Aug 14 Javascript
vue-cli3 DllPlugin 提取公用库的方法
Apr 24 Javascript
vue服务端渲染操作简单入门实例分析
Aug 28 Javascript
解决Antd Table表头加Icon和气泡提示的坑
Nov 17 Javascript
AJAX检测用户名是否存在的方法
Mar 24 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中常用数组处理方法实例分析
2008/08/30 PHP
php在window iis的莫名问题的测试方法
2013/05/14 PHP
PHP写的加密函数,支持私人密钥(详细介绍)
2013/06/09 PHP
PHP函数http_build_query使用详解
2014/08/20 PHP
PHP基于工厂模式实现的计算器实例
2015/07/16 PHP
CI框架实现cookie登陆的方法详解
2016/05/18 PHP
php 7新特性之类型申明详解
2017/06/06 PHP
设定php简写功能的方法
2019/11/28 PHP
文本链接逐个出现的js脚本
2007/12/12 Javascript
ext checkboxgroup 回填数据解决
2009/08/21 Javascript
jquery插件validate验证的小例子
2013/05/08 Javascript
当鼠标移动到图片上时跟随鼠标显示放大的图片效果
2013/06/06 Javascript
js onclick事件传参讲解
2013/11/06 Javascript
Vue.js每天必学之过滤器与自定义过滤器
2016/09/07 Javascript
JS打开摄像头并截图上传示例
2017/02/18 Javascript
vue循环数组改变点击文字的颜色
2019/10/14 Javascript
[01:32]2016国际邀请赛中国区预选赛IG战队首日赛后采访
2016/06/27 DOTA
[56:46]Liquid vs IG 2018国际邀请赛小组赛BO2 第二场 8.17
2018/08/18 DOTA
Python通过websocket与js客户端通信示例分析
2014/06/25 Python
Scrapy的简单使用教程
2017/10/24 Python
Python使用Matplotlib实现雨点图动画效果的方法
2017/12/23 Python
Django 2.0版本的新特性抢先看!
2018/01/05 Python
python 模拟贷款卡号生成规则过程解析
2019/08/30 Python
python编写俄罗斯方块
2020/03/13 Python
python实现秒杀商品的微信自动提醒功能(代码详解)
2020/04/27 Python
css3实现垂直下拉动画菜单示例
2014/04/22 HTML / CSS
Myprotein葡萄牙官方网站:英国优质运动营养品牌
2016/09/12 全球购物
程序员跳槽必看面试题总结
2013/06/28 面试题
平安建设工作方案
2014/06/02 职场文书
军训拉歌口号
2014/06/13 职场文书
2015大学自主招生自荐信范文
2015/03/04 职场文书
2015年世界无烟日演讲稿
2015/03/18 职场文书
欠条范文
2015/07/03 职场文书
2015年秋季开学典礼校长致辞
2015/07/16 职场文书
2016教师廉洁从教心得体会
2016/01/13 职场文书
JavaScript 对象创建的3种方法
2021/11/17 Javascript