zoukankan      html  css  js  c++  java
  • 红宝书4-第八章对象、类与面向对象编程(1)

    @


    1.理解对象

    这里作为理解对象的开篇,介绍了两种方式来创建对象

    第一种:通过基类 Object 来创建

    let person = new Object(); 
    person.name = "Nicholas"; 
    person.age = 29; 
    person.job = "Software Engineer"; 
    person.sayName = function() { 
     console.log(this.name); 
    };
    

    第二种:通过字面量来创建

    let person = { 
     name: "Nicholas", 
     age: 29, 
     job: "Software Engineer", 
     sayName() { 
     console.log(this.name); 
     } 
    };
    

    2. 属性的类型

    属性的类型在这里是指我们定义的属性,其实内部有一些特性来来描述规范这个属性,把这些特性分成两种:数据属性访问器属性。为了区分和特定表示这些特性,我们用中括号来表示他们,比如 [[Enumerable]]

    1.数据属性

    数据属性有 4个特性描述它们的行为。

    • [[Configurable]] :表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特性,以及是否可以把它改为访问器属性。默认情况下,所有直接定义在对象上的属性的这个特性都是 true,如前面的例子所示。
    • [[Enumerable]]:表示属性是否可以通过 for-in 循环返回。默认情况下,所有直接定义在对象上的属性的这个特性都是 true。
    • [[Writable]]:表示属性的值是否可以被修改。默认情况下,所有直接定义在对象上的属性的这个特性都是 true。
    • [[Value]]:包含属性实际的值。这就是前面提到的那个读取和写入属性值的位置。这个特性的默认值为 undefined。

    像咱们前面那样显式的添加属性,前三个特征都会被设置成true,最后的[[value]]会被设置成特定的值。
    所以数据属性的特性包含三个判断特性,以及一个保存实际数据的储存特性。

    改变数据属性特性的两种方法

    第一种: Object.defineProperty()
    其接受三个参数,要给其添加属性的对象、属性的名称和一个描述符对象。描述符对象上的属性就是上文中的4个特性,一一对应。

    let person = {}; 
    Object.defineProperty(person, "name", { 
     writable: false, 
     value: "Nicholas" 
    }); 
    console.log(person.name); // "Nicholas" 
    person.name = "Greg"; 
    console.log(person.name); // "Nicholas"
    

    非严格模式下,给这个属性赋值会没有效果。严格模式下,则会报错。
    注意:如果我们把[[configurable]]这个特性赋值为false,这样的操作是不可逆的。再次用Object.defineProperty()修改除了writable特性以外的属性都会报错。

    因此,虽然可以对同一个属性多次调用 Object.defineProperty(),但在把 configurable 设置为 false 之后就会受限制了。
    在调用 Object.defineProperty()时,configurable、enumerable 和 writable 的值如果不指定,则都默认为 false。这里也提到了特性默认值的问题,与上面直接给对象添加属性正好是相反的。

    第二种:Object.defineProperties()

    let book = {}; 
    Object.defineProperties(book, { 
     year_: { 
     value: 2017 
     }, 
     edition: { 
     value: 1 
     }, 
     year: { 
     get() { 
     return this.year_; 
     set(newValue) { 
     if (newValue > 2017) { 
     this.year_ = newValue; 
     this.edition += newValue - 2017; 
     } } } });},```
    
    #### 2. 访问器属性
    它们包含一个**获取(getter)函数**和一个**设置(setter)函数**,不过这两个函数不是必需的。在读取访问器属性时,会调用获取函数,这个函数的责任就是返回一个有效的值。在写入访问器属性时,会调用设置函数并传入新值,这个函数必须决定对数据做出什么修改。访问器属性有 4 个特性描述它们的行为。
    
     
    
     - [[Configurable]]:表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特性,以及是否可以把它改为数据属性。默认情况下,所有直接定义在对象上的属性的这个特性都是 true。 
     -  [[Enumerable]]:表示属性是否可以通过 for-in 循环返回。默认情况下,所有直接定义在对象上的属性的这个特性都是 true。 
     - [[Get]]:获取函数,在读取属性时调用。默认值为 undefined。 
     - [[Set]]:设置函数,在写入属性时调用。默认值为 undefined。
    
    **访问器属性是不能直接定义的**,必须使用 **Object.defineProperty()**。下面是一个例子:
    
    ```javascript
    // 定义一个对象,包含伪私有成员 year_和公共成员 edition 
    let book = { 
     year_: 2017, 
     edition: 1
     }; 
    Object.defineProperty(book, "year", { 
     get() { 
     return this.year_; 
     }, 
     set(newValue) { 
     if (newValue > 2017) { 
     this.year_ = newValue; 
     this.edition += newValue - 2017; 
     } 
     } 
    }); 
    book.year = 2018; 
    console.log(book.edition); // 2
    

    上面的例子就是我们定义了一个访问器属性,内部可以定义 get ,set来 自定义函数的获取值,以及设置修改属性的规则。这是访问器属性的典型使用场景,即设置一个属性值会导致一些其他变化发生。

    获取函数和设置函数不一定都要定义。只定义获取函数意味着属性是只读的,尝试修改属性会被忽略。在严格模式下,尝试写入只定义了获取函数的属性会抛出错误。类似地,只有一个设置函数的属性是不能读取的,非严格模式下读取会返回 undefined,严格模式下会抛出错误。

    在不支持 Object.defineProperty()的浏览器中没有办法修改[[Configurable]]或[[Enumerable]]。

    3.读取属性的特性

    Object.getOwnPropertyDescriptor()方法可以取得指定属性的属性描述符。这个方法接收两个参数:属性所在的对象和要取得其描述符的属性名。

    let book = {}; 
    Object.defineProperties(book, { 
     year_: { 
     value: 2017 
     }, 
     edition: { 
     value: 1 
     }, 
     year: { 
     get: function() { 
     return this.year_; 
     }, 
     set: function(newValue){ 
     if (newValue > 2017) { 
     this.year_ = newValue; 
     this.edition += newValue - 2017; 
     } 
     } 
     } 
    }); 
    let descriptor = Object.getOwnPropertyDescriptor(book, "year_"); 
    console.log(descriptor.value); // 2017 
    console.log(descriptor.configurable); // false 
    console.log(typeof descriptor.get); // "undefined" 
    let descriptor = Object.getOwnPropertyDescriptor(book, "year"); 
    console.log(descriptor.value); // undefined 
    console.log(descriptor.enumerable); // false 
    console.log(typeof descriptor.get); // "function"
    

    Object.getOwnPropertyDescriptors()静态方法,来获取所有属性的特征值。

    let book = {}; 
    Object.defineProperties(book, { 
     year_: { 
     value: 2017 
     }, 
     edition: { 
     value: 1 
     }, 
     year: { 
     get: function() { 
     return this.year_; 
     }, 
     set: function(newValue){ 
     if (newValue > 2017) { 
     this.year_ = newValue; 
     this.edition += newValue - 2017; 
     } 
     } 
     } 
    }); 
    console.log(Object.getOwnPropertyDescriptors(book)); 
    // { 
    // edition: { 
    // configurable: false, 
    // enumerable: false, 
    // value: 1, 
    // writable: false 
    // }, 
    // year: { 
    // configurable: false, 
    // enumerable: false, 
    // get: f(), 
    // set: f(newValue), 
    // }, 
    // year_: { 
    // configurable: false, 
    // enumerable: false, 
    // value: 2017, 
    // writable: false 
    // } 
    // }
    

    4.合并对象。

    Object.assign()方法。这个方法接收一个目标对象和一个或多个源对象作为参数,然后将每个源对象中可枚(Object.propertyIsEnumerable()返回 true)自有(Object.hasOwnProperty()返回 true)属性复制到目标对象。以字符串和符号为键的属性会被复制。对每个符合条件的属性,这个方法会使用源对象上的[[Get]]取得属性的值,然后使用目标对象上的[[Set]]设置属性的值

    let dest, src, result; 
    /** 
     * 简单复制
     */ 
    dest = {}; 
    src = { id: 'src' }; 
    result = Object.assign(dest, src); 
    // Object.assign 修改目标对象
    // 也会返回修改后的目标对象
    console.log(dest === result); // true 
    console.log(dest !== src); // true 
    console.log(result); // { id: src } 
    console.log(dest); // { id: src } 
    /** 
     * 多个源对象
     */ 
    dest = {}; 
    result = Object.assign(dest, { a: 'foo' }, { b: 'bar' }); 
    console.log(result); // { a: foo, b: bar } 
    /** 
     * 获取函数与设置函数
     */ 
    dest = { 
     set a(val) { 
     console.log(`Invoked dest setter with param ${val}`); 
     } 
    }; 
    src = { 
     get a() { 
     console.log('Invoked src getter'); 
     return 'foo'; 
     } 
    }; 
    Object.assign(dest, src); 
    // 调用 src 的获取方法
    // 调用 dest 的设置方法并传入参数"foo" 
    // 因为这里的设置函数不执行赋值操作
    // 所以实际上并没有把值转移过来
    console.log(dest); // { set a(val) {...} }
    

    Object.assign()实际上对每个源对象执行的是浅复制。多个对象后面的会覆盖前面的相同的属性。此外,从源对象访问属性取得的值,比如获取函数,会作为一个静态值赋给目标对象。换句话说,不能在两个对象间转移获取函数和设置函数。
    就是如果我们设置了获取或者设置函数,如果需要触发他们就走我们自己设置的方法,没有设置就走默认的获取设置方法。

    let dest, src, result; 
    /** 
     * 覆盖属性
     */ 
    dest = { id: 'dest' }; 
    result = Object.assign(dest, { id: 'src1', a: 'foo' }, { id: 'src2', b: 'bar' }); 
    // Object.assign 会覆盖重复的属性
    console.log(result); // { id: src2, a: foo, b: bar } 
    // 可以通过目标对象上的设置函数观察到覆盖的过程:
    dest = { 
     set id(x) { 
     console.log(x); 
     } 
    }; 
    Object.assign(dest, { id: 'first' }, { id: 'second' }, { id: 'third' }); 
    // first 
    // second 
    // third 
    /** 
     * 对象引用
     */ 
    dest = {}; 
    src = { a: {} }; 
    Object.assign(dest, src); 
    // 浅复制意味着只会复制对象的引用
    console.log(dest); // { a :{} } 
    console.log(dest.a === src.a); // true
    

    如果赋值期间出错,则操作会中止并退出,同时抛出错误。Object.assign()没有“回滚”之前赋值的概念,因此它是一个尽力而为、可能只会完成部分复制的方法。

    let dest, src, result; 
    /** 
     * 错误处理
     */ 
    dest = {}; 
    src = { 
     a: 'foo', 
     get b() { 
     // Object.assign()在调用这个获取函数时会抛出错误
     throw new Error(); 
     },
      c: 'bar' 
    }; 
    try { 
     Object.assign(dest, src); 
    } catch(e) {} 
    // Object.assign()没办法回滚已经完成的修改
    // 因此在抛出错误之前,目标对象上已经完成的修改会继续存在:
    console.log(dest); // { a: foo }
    

        感谢您花时间阅读此篇文章,如果您觉得看了这篇文章之后心情还比较高兴,可以打赏一下,请博主喝上一杯咖啡,让博主继续码字……
        本文版权归作者和博客园共有,来源网址:https://blog.csdn.net/weixin_46498102 欢迎各位转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接
  • 相关阅读:
    递归函数及Java范例
    笔记本的硬盘坏了
    “References to generic type List should be parameterized”
    配置管理软件(configuration management software)介绍
    WinCE文件目录定制及内存调整
    使用Silverlight for Embedded开发绚丽的界面(3)
    wince国际化语言支持
    Eclipse IDE for Java EE Developers 与Eclipse Classic 区别
    WinCE Heartbeat Message的实现
    使用Silverlight for Embedded开发绚丽的界面(2)
  • 原文地址:https://www.cnblogs.com/jackson1/p/13778058.html
Copyright © 2011-2022 走看看