【vue学习】生命周期

马老师的VUE生命周期教程

image.png

Vue实例

虽然没有完全遵循 MVVM 模型,但是 Vue 的设计也受到了它的启发。因此在文档中经常会使用 vm (ViewModel 的缩写) 这个变量名表示 Vue 实例。

一个 Vue 应用由一个通过new Vue() 创建的根 Vue 实例,以及可选的嵌套的、可复用的组件树组成。所有的 Vue 组件也都是 Vue 实例,并且接受相同的选项对象 (一些根实例特有的选项除外)。

每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。

面试中的问题

  1. 请描述下vue的生命周期是什么?
  2. vue生命周期总共有几个阶段?
  3. vue生命周期的作用是什么?
  4. 组件进来请求接口时你是放在哪个生命周期?为什么?
  5. DOM渲染在哪个周期中就已经完成了?
  6. 第一次加载页面时会触发哪几个钩子?
  7. vue在created和mounted这两个生命周期中请求数据有什么区别呢?
  8. watch的属性用箭头函数定义结果会怎么样?
  9. 在vue项目中如果methods的方法用箭头函数定义结果会怎么样?


    image

API中的钩子函数

image

所有的生命周期钩子自动绑定 this 上下文到实例中,因此你可以访问数据,对属性和方法进行运算。这意味着你不能使用箭头函数来定义一个生命周期方法 (例如 created: () => this.fetchTodos())。这是因为箭头函数绑定了父上下文,因此 this 与你期待的 Vue 实例不同,this.fetchTodos 的行为未定义。

大部分钩子在服务器端渲染期间不被调用。

  • beforeCreate:
    在实例初始化(new)之后,数据观测data observer 和 事件配置 event/watcher之前被调用。
  • created:
    实例创建完成后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。
    然而,挂载阶段还没开始,$el 属性目前不可见。
  • beforeMount:
    在挂载开始前被调用:相关的 render 函数首次被调用。
  • mounted:
    el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内。注意: mounted 不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以用 vm.$nextTick 替换掉 mounted。。
  • beforeUpdate:
    数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。 你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。
  • updated:
    由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
    当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态,因为这可能会导致更新无限循环。如果要相应状态改变,通常最好使用计算属性或 watcher 取而代之。。
  • beforeDestroy:
    实例销毁之前调用。在这一步,实例仍然完全可用。
  • destroyed:
    Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
  • activated: keep-alive 组件激活时调用。被调用。
  • deactivated: keep-alive 组件停用时调用。。
  • errorCaptured:当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。

5种处理Vue异常的方法

既然学到了钩子函数errorCaptured,那么我们就来学习下vue的异常处理

错误大全

为了测试各种异常处理技巧,来触发三种类型的错误。

  1. 引用一个不存在的变量:
<div id="app" v-cloak>
    Hello, {{name}}
</div>

上述代码运行后不会抛出错误,但是在控制台会有[Vue warn]消息。

image

你可以在 Codepen 查看例子,页面可以正常显示
image

  1. 将变量绑定到一个被计算出来的属性,计算的时候会抛出异常
<div id="app" v-cloak>
    Hello, {{name2}}
</div>

<script>
    const app = new Vue({
        el: "#app",
        computed: {
            name2() {
                return x;
            }
        }
    });
</script>

运行上述代码会在控制台抛出一个[Vue warn]和一个常规的错误,网页白屏。

image

  1. 执行一个会抛出异常的方法
<div id="app" v-cloak>
    <button @click="doIt">Do It</button>
</div>

<script>
    const app = new Vue({
        el: "#app",
        methods: {
            doIt() {
                return x;
            }
        }
    });
</script>

这个错误在控制台也[Vue warn]和常规报错。和上一个错误的区别在于,只有你点击了按钮才会触发函数调用,才会报错。

上面 3 个例子并不代表所有类型的错误。这 3 种是比较常见的错误。

异常处理技巧

  1. errorHandler[2.2+]
/**
err指代 error 对象,
info是一个 Vue 特有的字符串,
vm指代 Vue 应用本身。
记住在一个页面你可以有多个 Vue 应用。
这个 error handler 作用到所有的应用。
*/
Vue.config.errorHandler = function(err, vm, info) {
    console.log(`Error: ${err.toString()}\nInfo: ${info}`);
};
/**
上面第一种错误不会触发 errorHandler,它只是一个 warning。

第二种错误会抛出错误被 errorHandler 捕获:
Error: ReferenceError: x is not defined
Info: render

第三种错误也会被捕获:
Error: ReferenceError: x is not defined
Info: v-on handler

记住info里面的信息也是非常有用的。
*/
  1. warnHandler[2.4+]
/**
msg和vm都容易理解,trace代表了组件树。
*/
Vue.config.warnHandler = function(msg, vm, trace) {
    console.log(`Warn: ${msg}\nTrace: ${trace}`);
};
/**
第一个错误被warnHandler捕获:
Warn: Property or method 'name' is not defined on the instance but referenced during render. 
Make sure that this property is reactive, either in the data option, 
or for class-based components, by initializing the property. 
See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
Trace:(found in <Root>)
*/
  1. renderError
/**
和前面两个不同,这个技巧不适用于全局,和组件相关。
并且只适用于非生产环境。
*/
const app = new Vue({
    el: "#app",
    renderError(h, err) {
        return h("pre", { style: { color: "red" } }, err.stack);
    }
});

第一个例子是没有效果的,因为只是一个 warning。第二个例子就会在网页上显示具体的错误信息。

image

老实说,我没觉得这个比直接看控制台好多少。但是,如果你们的 QA 团队或则测试对浏览器控制台不熟悉的话,这个还是蛮有用的。

  1. errorCaptured
Vue.component("cat", {
    template: `
<div><h1>Cat: </h1>
  <slot></slot>
</div>`,
    props: {
        name: {
            required: true,
            type: String
        }
    },
    errorCaptured(err, vm, info) {
        console.log(`cat EC: ${err.toString()}\ninfo: ${info}`);
        return false;
    }
});

Vue.component("kitten", {
    template: "<div><h1>Kitten: {{ dontexist() }}</h1></div>",
    props: {
        name: {
            required: true,
            type: String
        }
    }
});
<!--注意 kitten 组件的代码是有 BUG 的。-->
<div id="app" v-cloak>
    <cat name="my cat">
        <kitten></kitten>
    </cat>
</div>

捕获的信息如下:


image
  1. window.onerror (不仅仅针对 Vue)
    最后也是最重要的一个候选项 window.onerror。它是一个全局的异常处理函数,可以抓取所有的 JavaScript 异常。
window.onerror = function(message, source, line, column, error) {
  console.log('ONE ERROR HANDLER TO RULE THEM ALL:', message);
}

Vue.config.productionTip = false;
Vue.config.devtools = false;

Vue.config.errorHandler = function(err, vm, info) {
  //oopsIDidItAgain();
  console.log(`Error: ${err.toString()}\nInfo: ${info}`);
}

const app = new Vue({
  el:'#app',
  methods:{
    doIt() {
      return x;
    }
  }
})

Vue选项/数据

学习vue的生命周期,这里也顺便看下vue选项中的数据:

image

  1. propsData

错误用法:


image

image

正确用法:


image
  1. props 可以是数组或对象,用于接收来自父组件的数据。
// 简单语法
Vue.component('props-demo-simple', {
  props: ['size', 'myMessage']
})

// 对象语法,提供验证
Vue.component('props-demo-advanced', {
  props: {
    // 检测类型
    height: Number,
    // 检测类型 + 其他验证
    age: {
      type: Number,
      default: 0,
      required: true,
      validator: function (value) {
        return value >= 0
      }
    }
  }
})
  1. data
  • 组件的定义data只能是function类型。
  • Vue 实例的数据对象。Vue 将会递归将 data 的属性转换为 getter/setter,从而让 data 的属性能够响应数据变化。
  • 实例创建之后,可以通过 vm.$data 访问原始数据对象
  • _$ 开头的属性 不会被 Vue 实例代理,因为它们可能和 Vue 内置的属性、API 方法冲突。你可以使用例如 vm.$data._property 的方式访问这些属性
  • 如果需要,可以通过将 vm.$data 传入 JSON.parse(JSON.stringify(...)) 得到深拷贝的原始数据对象。
  • 深入响应式原理
  1. computed
computed: {
    // 仅读取
    aDouble: function () {
      return this.a * 2
    },
    // 读取和设置
    aPlus: {
      get: function () {
        return this.a + 1
      },
      set: function (v) {
        this.a = v - 1
      }
    }
  }
  1. methods
methods: {
    plus: function () {
      this.a++
    }
}
  1. watch
    一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。Vue 实例将会在实例化时调用 $watch(),遍历 watch对象的每一个属性。
var vm = new Vue({
  data: {
    a: 1,
    b: 2,
    c: 3,
    d: 4,
    e: {
      f: {
        g: 5
      }
    }
  },
  watch: {
    a: function (val, oldVal) {
      console.log('new: %s, old: %s', val, oldVal)
    },
    // 方法名
    b: 'someMethod',
    // 该回调会在任何被侦听的对象的 property 改变时被调用,不论其被嵌套多深
    c: {
      handler: function (val, oldVal) { /* ... */ },
      deep: true
    },
    // 该回调将会在侦听开始之后被立即调用
    d: {
      handler: 'someMethod',
      immediate: true
    },
    e: [
      'handle1',
      function handle2 (val, oldVal) { /* ... */ },
      {
        handler: function handle3 (val, oldVal) { /* ... */ },
        /* ... */
      }
    ],
    // watch vm.e.f's value: {g: 5}
    'e.f': function (val, oldVal) { /* ... */ }
  }
})

data中为啥不能用-$开头的属性

<template>
 <div class="hello">
    {{test}}
    {{_tttttttttt}}
    {{$tttttt}}
  </div>
</template>
<script>
import h222 from './h2'
export default {
  name: 'HelloWorld',
  data () {
    return {
      _tttttttttt: '__',
      $tttttt: '$$',
      test: 'test'
    }
  },
  mounted () {
    console.log(this)
  }
}
</script>

这么一个简单例子,但是vue却会提示你报错了,错误如下:


image
  • 从错误中我们可以发现报错的原因竟然是$tttttt_tttttttttt的变量是没有定义。这是为什么呢?我们明明在data中定义了。
  • 通过查看官网API说明,data中不要使用$_开头,因为在Vue内部也使用$_作为方法或属性,这是为了防止冲突。那么我们就从源码的角度来分析,我们定义的变量为什么没了呢??
  • 我们都知道vue采用了ES的defineProperty来实现数据驱动视图,如下:
Object.defineProperty(target, key) {
    enumerable: true,
    configurable: true,
    get: function() {
        // 这里获取数据
    },
    set: function() {
        // 这里设置参数,通知更新视图
    }
}
/**
可如果仅仅是这样的话,我们在vue中是没法通过this.xxx来获取变量的,
而必须是通过this.$data.xxx。因此vue的变量都挂在在$data或_data上。
所以vue内部还做了一层代理,如下
*/
/**
target是vue实例,key为_data,
这样就能通过访问this.xxx = this._data.xxx了
*/
function proxy (target, sourceKey, key) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  };
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val;
  };
  Object.defineProperty(target, key, sharedPropertyDefinition);
}
/**
所以那么我们的变量为什么还是不存在呢,
那是因为vue做了一个检测,
检测你的变量的开头是否为_或$,
如果使用了那么就不会使用代理了,
变量只会存在$data上或_data上。我们来看下源码:
*/
function initData (vm) {
  var data = vm.$options.data;
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {};
  if (!isPlainObject(data)) {
    data = {};
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    );
  }
  // proxy data on instance
  var keys = Object.keys(data);
  var props = vm.$options.props;
  var methods = vm.$options.methods;
  var i = keys.length;
  while (i--) {
    var key = keys[i];
    if (process.env.NODE_ENV !== 'production') {
      if (methods && hasOwn(methods, key)) {
        warn(
          ("Method \"" + key + "\" has already been defined as a data property."),
          vm
        );
      }
    }
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== 'production' && warn(
        "The data property \"" + key + "\" is already declared as a prop. " +
        "Use prop default value instead.",
        vm
      );
    } else if (!isReserved(key)) {
        // 这边处理代理,所以isReserved处理了是否要进行代理
      proxy(vm, "_data", key);
    }
  }
  // observe data
  observe(data, true /* asRootData */);

  function isReserved (str) {
      var c = (str + '').charCodeAt(0);
      return c === 0x24 || c === 0x5F // 这边判断chartCode是否为_和$
   }
}
  • 到这里我们就完成的解释为什么无法访问了,所以一般不要使用_和$命名,如果真的要使用的话,那也行。使用如下就不会报错了。
<template>
 <div class="hello">
    {{test}}
    {{_data._tttttttttt}}
    {{_data.$tttttt}}
    {{$data._tttttttttt}}
    {{$data.$tttttt}}
  </div>
</template>
<script>
import h222 from './h2'
export default {
  name: 'HelloWorld',
  data () {
    return {
      _tttttttttt: '__',
      $tttttt: '$$',
      test: 'test'
    }
  },
  mounted () {
    console.log(this.$data._tttttttttt)
    console.log(this.$data.$tttttt)
    console.log(this._data._tttttttttt)
    console.log(this._data.$tttttt)
  }
}
<script>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,635评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,628评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,971评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,986评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,006评论 6 394
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,784评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,475评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,364评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,860评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,008评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,152评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,829评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,490评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,035评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,428评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,127评论 2 356

推荐阅读更多精彩内容