明白JavaScript的Object.defineProperty()函数

明白JavaScript的Object.defineProperty()函数

在进入本日的内容之前,我们可以先斟酌这么一个场景,在你的项目中你有这么一个对象以下所示:

var dreamapple = {
    firstName: 'dream',
    lastName: 'apple'
};

我们的请求就是你要给dreamapple增加一个fullName属性,当dreamapplefirstName或许lastName发生变化的时刻,fullName也要随之变化;而且当我们设置了fullName的值的时刻,那末响应的它的firstNamelastName也随之发生变化; 那末我们应当怎样做呢?

假如你运用过Vue.js的话,那末你可以运用它的盘算属性来到达这个目标,也许的代码应当是下面这个模样:

// ...
computed: {
  fullName: {
    // getter
    get: function () {
      return this.firstName + ' ' + this.lastName
    },
    // setter
    set: function (newValue) {
      var names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}
// ...

假如你运用过Angular 1.x的话,那末你可能会运用$watch来到达这个目标,也许的代码应当是下面这个模样:

// 在控制器中运用 var vm = this;
$scope.$watch('vm.firstName', function() {
    vm.fullName = vm.firstName + ' ' + vm.lastName;
});
$scope.$watch('vm.lastName', function() {
    vm.fullName = vm.firstName + ' ' + vm.lastName;
});
$scope.$watch('vm.fullName', function() {
    var names = vm.fullName.trim().split(' ');
    if(2 === names.length) {
        vm.firstName = names[0];
        vm.lastName = names[1];
    }
    else {
        // TODO
    }
});

那我们运用原生的JavaScript可不可以到达这个目标呢?固然可以了;那末我们须要怎样做呢?比较简单的做法就是给这个对象的属性fullName设置一个getter和一个setter,由于这是ES5的特征所以较低版本的浏览器不支撑这类特征,然则基础一切的当代浏览器都已支撑.我们只须要写出下面的代码就可以了:

var dreamapple = {
    firstName: 'dream',
    lastName: 'apple',
    get fullName() {
        return this.firstName + ' ' + this.lastName;
    },
    set fullName(fullName) {
        var names = fullName.trim().split(' ');
        if(2 === names.length) {
            this.firstName = names[0];
            this.lastName = names[1];
        }
    }
};

dreamapple.firstName = 'Dream';
dreamapple.lastName = 'Apple';
console.log(dreamapple.fullName); // Dream Apple

dreamapple.fullName = 'Jams King';
console.log(dreamapple.firstName); // Jams
console.log(dreamapple.lastName); // King

是不是是很轻易呢?我们通过给dreamapple这个对象设置了属性fullNamegettersetter要领,就到达了我们想要的那种结果.

固然更好的一种要领就是运用Object.defineProperty()这个函数了,下面我们就来好好的讨论一下这个函数.这个要领的作用就是直接在一个对象上定义一个新属性,或许修正一个已存在的属性,并返回这个对象;我们先来看一下怎样运用这个要领:

Object.defineProperty(obj, prop, descriptor)

个中参数obj示意的是须要定义属性的谁人对象,参数prop示意须要被定义或许修正的属性名,参数descriptor就是我们定义的谁人属性prop的形貌;我们接下来重要解说这个descriptor.它是一个对象,它有很多的属性,我们接下来来剖析这些属性都是干什么用的:

  • value 该属性对应的值,可以是任何有用的JavaScript值(数值,对象,函数等),默以为undefined.我们可以看下面的一个小例子:

    var dream = {};
    Object.defineProperty(dream, 'name', {
        value: 'dreamapple'
    });
    
    console.log(dream.name); // dreamapple
    dream.name = 'apple'; // 修正name属性
    console.log(dream.name); // 并非apple,依旧是dreamapple

    从上面的代码中我们可以看到,我们给dream定义了一个新的属性name,然后我们打印出这个属性就是我们预期的那样,获得的是dreamapple;然则,当我们尝试转变这个属性的时刻,却发明这个属性并没有转变,还以第一次我们赋给它的值;这是为何呢?本来,只要当我们这个属性的writable润饰为true时,我们这个属性才够被修正.

  • writable 当且仅当仅当该属性的writabletrue时,该属性才被赋值运算符转变;它的默许值为false.我们来修正一下上面的代码,让属性name可以被修正:

    Object.defineProperty(dream, 'name', {
        value: 'dreamapple',
        writable: true
    });
    
    console.log(dream.name); // dreamapple
    dream.name = 'apple'; // 修正name属性
    console.log(dream.name); // apple

    我们可以看到,当我们把writable修正为true时,我们就可以修正name属性了.

  • enumerable 这个特征决议了我们定义的属性是不是是可罗列的范例,默许是false;只要我们把它设置为true的时刻这个属性才够运用for(prop in obj)Object.keys()中罗列出来.就像下面如许:

    Object.defineProperty(dream, 'a', {
        value: 1,
        enumerable: false // 不可罗列
    });
    Object.defineProperty(dream, 'b', {
        value: 2,
        enumerable: true // 可罗列
    });
    
    // 只会输出 b
    for(prop in dream) {
        console.log(prop);
    }
    
    console.log(Object.keys(dream)); // ['b']
    
    console.log(dream.propertyIsEnumerable('a')); // false
    console.log(dream.propertyIsEnumerable('b')); // true

    所以当我们想给你个对象增加一个不可罗列的属性的时刻,就应当把enumerable设置为false.

  • configurable 这个特征决议了对象的属性是不是可以被删除,以及除writable特征外的别的特征是不是可以被修正;而且writable特征值只可以是false我们可以写一个代码示例来演示一下这个特征:

    Object.defineProperty(dream, 'c', {
        value: 3,
        configurable: false
    });
     //throws a TypeError
    Object.defineProperty(dream, 'c', {
        configurable: true
    });
     //throws a TypeError
    Object.defineProperty(dream, 'c', {
        writable: true
    });
     //won't throws a TypeError
    Object.defineProperty(dream, 'c', {
        writable: false
    });
    delete dream.c; // 属性不可以被删除
    console.log(dream.c); // 3 
  • get 一个给属性供应getter的要领,假如没有getter则为undefined;该要领返回值被用作属性值,默以为undefined.

  • set 一个给属性供应setter的要领,假如没有setter则为undefined;该要领将接收唯一参数,并将该参数的新值分配给该属性,默以为undefined.知道了这些以后我们就可以运用更规范的一种体式格局去处理我们在文中开首的问题了:

    Object.defineProperty(dreamapple, 'fullName', {
        enumerable: true,
        get: function () {
            return this.firstName + ' ' + this.lastName;
        },
        set: function (fullName) {
            var names = fullName.trim().split(' ');
            if (2 === names.length) {
                this.firstName = names[0];
                this.lastName = names[1];
            }
        }
    });

    另有一点须要注重的是,valueget,set是不可以共存的,就是说你定义了value后就不可以再定义get,set特征了.

好啦,本日的文章就写到这里了,置信人人关于Object.defineProperty(obj, prop, descriptor)这个要领应当控制了;另有一点须要说起的是实在Vue.js盘算属性也是在这个函数的基础上举行的一些革新,概况可以看这里盘算属性的奥妙.

假如你对这篇文章有什么看法或许发起可以在这里提出来issues

    原文作者:dreamapplehappy
    原文地址: https://segmentfault.com/a/1190000006178220
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞