Vue响应式开发,深入理解Vue.js响应式原理

时间: 2017-11-10阅读: 697标签: vue

引子

本人是Java背景,许多年前刚接触JavaScript时有点怪怪的,因为它没有 getters 和 setters。随着时间的推移,我开始喜欢上这个缺失的特性,因为相比Java大量的 getter 和 setter,它让代码更简洁。例如,我们看看下面的Java代码:

class Person{
    String firstName;
    String lastName;

    // 这个Demo中省略了一些构造器代码 :)

    public void setFirstName(firstName) {
        this.firstName = firstName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setLastName(lastName) {
        this.lastName = lastName;
    }

    public String getLastName() {
        return lastName;
    }
}

// Create instance
Person bradPitt = new Person();
bradPitt.setFirstName("Brad");
bradPitt.setLastName("Pitt");

JavaScript开发人员永远不会这样做,相反他们会这样:

var Person = function () {

};

var bradPitt = new Person();
bradPitt.firstName = 'Brad';
bradPitt.lastName = 'Pitt';

这要简洁的多。通常简洁更好,不是吗?

的确如此,但有时我想获取一些可以被修改的属性,但我不用知道这些属性是什么。例如,我们在Java代码中扩展一个新的方法 getFullName():

class Person{
    private String firstName;
    private String lastName;

    // 这个Demo中省略了一些构造器代码 :)

    public void setFirstName(firstName) {
        this.firstName = firstName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setLastName(lastName) {
        this.lastName = lastName;
    }

    public String getLastName() {
        return lastName;
    }

    public String getFullName() {
        return firstName + " " + lastName;
    }
}

Person bradPitt = new Person();
bradPitt.setFirstName("Brad");
bradPitt.setLastName("Pitt");

// Prints 'Brad Pitt'
System.out.println(bradPitt.getFullName());

在上面例子中, fullName 是一个计算过的属性,它不是私有属性,但总能返回正确的结果。

C# 和隐式的 getter/setters

我们来看看 C# 特性之一:隐式的 getters/setters,我真的很喜欢它。在 C# 中,如果需要,你可以定义 getters/setters,但是并不用这样做,但是如果你决定要这么做,调用者就不必调用函数。调用者只需要直接访问属性,getter/setter 会自动在钩子函数中运行:

public class Foo {
    public string FirstName {get; set;}
    public string LastName {get; set;}
    public string FullName {get { return firstName + " " + lastName }; private set;}
}

我觉得这很酷...

现在,如果我想在JavaScript中实现类似的功能,我会浪费很多时间,比如:

var person0 = {
    firstName: 'Bruce',
    lastName: 'Willis',
    fullName: 'Bruce Willis',
    setFirstName: function (firstName) {
        this.firstName = firstName;
        this.fullName = `${this.firstName} ${this.lastName}`;
    },
    setLastname: function (lastName) {
        this.lastName = lastName;
        this.fullName = `${this.firstName} ${this.lastName}`;
    },
};
console.log(person0.fullName);
person0.setFirstName('Peter');
console.log(person0.fullName);

它会打印出:

"Bruce Willis"
"Peter Willis"

但使用 setXXX(value) 的方式并不够'JavaScripty'(是个玩笑啦)。

下面的方式可以解决这个问题:

var person1 = {
    firstName: 'Brad',
    lastName: 'Pitt',
    getFullName: function () {
        return `${this.firstName} ${this.lastName}`;
    },
};
console.log(person1.getFullName()); // 打印 "Brad Pitt"

现在我们回到被计算过的 getter。你可以设置 first 或 last name,并简单的合并它们的值:

person1.firstName = 'Peter'
person1.getFullName(); // 返回 "Peter Pitt"

这的确更方便,但我还是不喜欢它,因为我们要定义一个叫getxxx()的方法,这也不够'JavaScripty'。许多年来,我一直在思考如何更好的使用 JavaScript。

然后 Vue 出现了

在我的Youtube频道,很多和Vue教程有关的视频都讲到,我习惯响应式开发,在更早的Angular1时代,我们叫它:数据绑定(Data Binding)。它看起来很简单。你只需要在Vue实例的 data() 块中定义一些数据,并绑定到HTML:

var vm = new Vue({
    data() {
        return {
        greeting: 'Hello world!',
        };
    }
})
<div>{greeting}</div>

显然它会在用户界面打印出 “Hello world!”。

现在,如果你改变greeting的值,Vue引擎会对此作出反应并相应地更新视图。

methods: {
    onSomethingClicked() {
        this.greeting = "What's up";
    },
}

很长一段时间我都在想,它是如何工作的?当某个对象的属性发生变化时会触发某个事件?或者Vue不停的调用 setInterval 去检查是否更新?

通过阅读Vue官方文档,我才知道,改变一个对象属性将隐式调用getter/setter,再次通知观察者,然后触发重新渲染,如下图,这个例子来自官方的Vue.js文档:


但我还想知道:

  • 怎么让数据自带getter/setters?
  • 这些隐式调用内部是怎样的?

第一个问题很简单:Vue为我们准备好了一切。当你添加新数据,Vue将会通过其属性为其添加 getter/setters。但是我让 foo.bar = 3? 会发生什么?

这个问题的答案出现在我和SVG & Vue专家Sarah Drasner的Twitter对话中:


Timo: foo.bar=value;是怎么做到实时响应的?

Sarah: 这个问题很难在Twitter说清楚,可以看这篇文章

Timo: 但这篇文章并没有解释上面提到的问题。

Timo: 它们就像:分配一个值->调用setter->通知观察者,不理解为什么在不使用setInterval和Event的情况下,setter/getter就存在了。

Sarah: 我的理解是:你获取的所有数据都在Vue实例data{}中被代理了。

显然,她也是参考的官方文档,之前我也读过,所以我开始阅读Vue源码,以便更好的理解发生了什么。过了一会我想起在官方文档看到一个叫 Object.defineProperty() 的方法,我找到它,如下:

/**
* 给对象定义响应的属性
*/
export function defineReactive (
    obj: Object,
    key: string,
    val: any,
    customSetter?: ?Function,
    shallow?: boolean
) {
    const dep = new Dep()

    const property = Object.getOwnPropertyDescriptor(obj, key)
    if (property && property.configurable === false) {
        return
    }

    // 预定义getter/setters
    const getter = property && property.get
    const setter = property && property.set

    let childOb = !shallow && observe(val)
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter () {
            const value = getter ? getter.call(obj) : val
            if (Dep.target) {
                dep.depend()
                if (childOb) {
                    childOb.dep.depend()
                }
                if (Array.isArray(value)) {
                    dependArray(value)
                }
            }
            return value
        },
        set: function reactiveSetter (newVal) {
            const value = getter ? getter.call(obj) : val
            /* 禁用eslint 不进行自我比较 */
            if (newVal === value || (newVal !== newVal && value !== value)) {
                return
            }
            /* 开启eslint 不进行自己比较 */
            if (process.env.NODE_ENV !== 'production' && customSetter) {
                customSetter()
            }
            if (setter) {
                setter.call(obj, newVal)
            } else {
                val = newVal
            }
            childOb = !shallow && observe(newVal)
            dep.notify()
        }
    })
}

所以答案一直存在于文档中:

把一个普通 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。Object.defineProperty 是仅 ES5 支持,且无法 shim 的特性,这也就是为什么 Vue 不支持 IE8 以及更低版本浏览器的原因。

我只想简单的了解 Object.defineProperty() 做了什么,所以我用一个例子简单的给你讲解一下:

var person2 = {
    firstName: 'George',
    lastName: 'Clooney',
};
Object.defineProperty(person2, 'fullName', {
    get: function () {
        return `${this.firstName} ${this.lastName}`;
    },
});
console.log(person2.fullName); // 打印 "George Clooney"

还记得文章开头C#的隐式 getter 吗?它们看起来很类似,但ES5才开始支持。你需要做的是使用 Object.defineProperty() 定义现有对象,以及何时获取这个属性,这个getter被称为响应式——这实际上就是Vue在你添加新数据时背后所做的事。

Object.defineProperty()能让Vue变的更简化吗?

学完这一切,我一直在想,Object.defineProperty() 是否能让Vue变的更简化?现今越来越多的新术语,是不是真的有必要把事情变得过于复杂,变的让初学者难以理解(Redux也是同样):

  • Mutator:或许你在说(隐式)setter
  • Getters:为什么不用 Object.defineProperty() 替换成(隐式)getter
  • store.commit():为什么不简化成 foo = bar,而是 store.commit("setFoo", bar);?

你是怎么认为的?Vuex必须是复杂的还是可以像 Object.defineProperty() 一样简单?


特别声明,本文转载@余震翻译@Rockjins Blog的《Understanding Vue.js Reactivity in Depth with Object.defineProperty()》
英文地址:https://www.timo-ernst.net/blog/2017/07/26/understanding-vue-js-reactivity-depth-object-defineproperty 
 译文地址:https://juejin.im/post/59a7b01f6fb9a0249975d39f


Vue基础之计算属性

设想一个场景,你需要得到一个复杂运算/逻辑的返回值,利用模板内的表达又过长且难以阅读和维护,这时计算属性就可以很好的解决你的问题。看下面的例子:

vue 自定义指令

接下来我们来看一下钩子函数的参数 (即 el、binding、vnode 和 oldVnode)。但有一些是没有相对应的指令进行操作。在这里以progress(h5的新标签进度条)为例,向大家介绍Vue的一个用于指令扩展的方法:directive(自定义指令)。

对Vue.js的认知

MVVM是Model-View-ViewModel的缩写。MVVM是一种设计思想。Model 层代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑;View 代表UI 组件,它负责将数据模型转化成UI 展现出来,ViewModel 是一个同步View 和 Model的对象。

Vue的href动态拼接绑定

:href前面要加“:”或者v-bind: 字符串要用单引号“ \\\'\\\' ”包住 加上了冒号是为了动态绑定数据,等号后面可以写变量。 如果不使用冒号,等号后面就可以写字符串等原始类型数据。这是就无法进行动态绑定数据了

vue项目中vux的使用

VUX 是基于 WeUI 和 Vue.js 的 移动端 UI 组件库,提供丰富的组件满足移动端(微信)页面常用业务需求。在vue-cli中使用步骤如下:vux2必须配合vux-loader使用,并配置build/webpack.base.conf.js

当使用vue的按键修饰符不起效果的时候怎么办?如@keyup.enter = \'\' ;

但是问题是:如果我们使用第三方组件这个方法并不奏效了 这时我们应该这么写 )注意:这是我们必须在@keyup.enter后面加一个native 来确保这个功能能够得到实现

关于Vue.use()使用详解

相信很多人在用Vue使用别人的组件时,会用到 Vue.use() 。例如:Vue.use(VueRouter)、Vue.use(MintUI)。但是用 axios时,就不需要用 Vue.use(axios),就能直接使用。那这是为什么呐?

Vue 3.0 对 Web 开发意味着什么?

Vue的创建者Evan You向我们展示了Vue 3.0 —— 这是不断上升的Javascript框架的最新版本。这些优化使Vue更高效,更模块化且更易于使用。我将讨论这些变化以及我认为的他们将在Vue 3.0发布后对现有开发产生的影响。

Vue中mixin怎么理解?

mixin是为了让可复用的功能灵活的混入到当前组件中,混合的对象可以包含任意组件选项(生命周期,指令之类等等), mixin翻译过来叫混合,高级的词汇可以叫插件入侵

Vue 中的作用域插槽

什么时候使用作用域插槽呢?当子组件循环或某一部分的dom结构应该由外部传递进来的时候,我们要用作用域插槽,使用作用域插槽,子组件可以向父组件的作用域插槽里传递数据,父组件如果想接收这个数据,必须在外层使用template模版占位符,同时通过slot-scope对应的属性名字,来接收你传递过来的数据

fly63.com版权所有,内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权,请与小编联系!情况属实本人将予以删除!

广告赞助文章投稿关于web前端网站点搜索站长推荐网站地图站长QQ:522607023

小程序专栏: 土味情话心理测试脑筋急转弯幽默笑话段子句子语录