• 读Secrets of the JavaScript Ninja(二)对象


    面向对象和原型

    理解原型

    在JavaScript中,可通过原型实现继承。原型的概念很简单。每个对象都含有原型的引用,当查找属性时,若对象本身不具有该属性,则会查找原型上是否有该属性。

    每个对象都可以有一个原型,每个对象的原型也可以拥有一个原型,以此类推,形成一个原型链。查找特定属性将会被委托在整个原型链上,只有当没有更多的原型可以进行查找时,才会停止查找。

    对象构造器与原型

    当用作为函数调用Ninja时,什么都不会做。在用new操作符时返回一个对象,并且设置了它的原型为Ninja,所以ninja2可以调用swingSword方法。但swingSword方法是Ninja的原型属性, 而不是ninja实例的属性

    function Ninja(){}
    Ninja.prototype.swingSword = function(){
      return true;
    };  
    const ninja1 = Ninja();
    assert(ninja1 === undefined,
    "No instance of Ninja created.");  
    const ninja2 = new Ninja();
    assert(ninja2 &&
    ninja2.swingSword &&
    ninja2.swingSword(),
    "Instance exists and method is callable." );
    

    实例属性

    1. 当在原型和实例中有重名属性时,实例属性优先级比原型属性高
    2. 创建了多个实例,每个实例都是独立的拷贝,但是原型引用的是同一个方法
    function Ninja(){
      this.swung = false;
      this.swingSword = function(){
        return !this.swung;  
      };
    }
    Ninja.prototype.swingSword = function(){
      return this.swung;
    };  
    const ninja = new Ninja();
    assert(ninja.swingSword(),
    "Called the instance method, not the prototype method.");
    

    通过构造函数实现对象类型

    constructor

    通过constructor属性来检查类型

    assert(ninja.constructor === Ninja, constructor引用检测ninja的类型,得到的结果为其构造函数的引用
    

    使用constructor的引用创建新对象

    const ninja2 = new ninja.constructor(); ⇽--- 通过第1个实例化对象的constructor方法创建第2个实例化对象
    

    实现继承

    为了实现继承,将Person的实例作为Ninja的原型,所以当Niaja药调用person的方法时,将会沿着原型链进行查找。

    function Person(){}
    Person.prototype.dance = function(){};
    function Ninja(){}
    Ninja.prototype = new Person(); ⇽--- 通过将Ninja的原型赋值为Person的实例,实现Ninja继承Person
    const ninja = new Ninja();
    assert(ninja instanceof Ninja,
    "ninja receives functionality from the Ninja prototype");
    assert(ninja instanceof Person, "... and the Person prototype");
    assert(ninja instanceof Object, "... and the Object prototype");
    assert(typeof ninja.dance === "function", "... and can dance!")
    

    重写constructor属性的问题

    通过设置Person实例对象作为
    Ninja构造器的原型时, 我们已经丢失了Ninja与Ninja初始原型之间的关联。

    //通过defineProperty配置constructor对象
    function Person(){}
      Person.prototype.dance = function(){};
      function Ninja(){}
      Ninja.prototype = new Person();
      Object.defineProperty(Ninja.prototype,"constructor",{
        enumerable: false,
        value: Ninja,
        writable: true
      }
    );
    

    在ES6使用JavaScript的class

    ES6中使用关键字class来实现类,但其底层的实现仍然是基于原型继承!

    使用class关键字

    class Ninja{
      constructor(name){
        this.name = name;
      }s
      wingSword(){
        return true;
      }
      //静态方法
      static compare(ninja1, ninja2){
        return ninja1.level - ninja2.level;
      }
    }
    

    实现继承

    class Person {
      constructor(name){
        this.name = name;
      }
      dance(){
        return true;
      }
    }
    class Ninja extends Person
      constructor(name, weapon){
        super(name); ⇽--- 使用关键字super调用基类构造函数
        this.weapon = weapon;
      }
      wieldWeapon(){
        return true;
      }
    }
    

    控制对象的访问

    使用getter与setter控制属性访问

    //使用字面量get set
    const ninjaCollection = {
      ninjas: ["Yoshi", "Kuma", "Hattori"],
      get firstNinja(){
        report("Getting firstNinja");
        return this.ninjas[0];
      }, ⇽--- 定义firstNinja的getter方法, 返回ninjas列表中第一个值, 并记录一条
      消息
      set firstNinja(value){
        report("Setting firstNinja");
        this.ninjas[0] = value;
      } ⇽--- 定义firstNinja的setter方法, 设置ninjas列表中第一个值, 并记录一条
      消息
    };
    
    //ES6 class
    class NinjaCollection {
      constructor(){
        this.ninjas = ["Yoshi", "Kuma", "Hattori"];
      }
      get firstNinja(){
        report("Getting firstNinja");
        return this.ninjas[0];
      }
      set firstNinja(value){
        report("Setting firstNinja");
        this.ninjas[0] = value;
      } ⇽--- 在ES6的class中使用getter和setter
    }
    const ninjaCollection = new NinjaCollection();
    

    使用getter与setter校验属性值

    function Ninja() {
      let _skillLevel = 0;
      Object.defineProperty(this, 'skillLevel', {
        get: () => _skillLevel,
        set: value => {
        if(!Number.isInteger(value)){
          throw new TypeError("Skill level should be a number");
          } ⇽--- 校验传入的值是否是整型。 如果不是, 则抛出异常
        _skillLevel = value;
        }
      });
    }
    const ninja = new Ninja();
    

    使用代理控制访问

    可以将代理理解为通用化的setter与getter,区别是每个setter与getter仅能控制单个对象属性, 而代理可用于对象交互的通用处理,包括调用对象的方法

    通过Proxy构造器创建代理

    const emperor = { name: "Komei" };  
    const representative = new Proxy(emperor, {  
      get: (target, key) => {
      report("Reading " + key + " through a proxy");
        return key in target ? target[key]
          : "Don't bother the emperor!"
      },
      set: (target, key, value) => {
        report("Writing " + key + " through a proxy");
        target[key] = value;
      }
    });
    

    使用代理记录日志

    function makeLoggable(target){
      return new Proxy(target, {
        get: (target, property) => {
          report("Reading " + property);
          return target[property];},
        set: (target, property, value) => {
          report("Writing value " + value + " to " + =property);
          target[property] = value;  
      }
    });
    }
    let ninja = { name: "Yoshi"};
    ninja = makeLoggable(ninja);
    assert(ninja.name === "Yoshi", "Our ninja Yoshi");
    ninja.weapon = "sword"; ⇽--- 对代理对象进行读写操作时, 均会通过代理方法记录日志
    

    使用代理可以优雅地实现以下内容。

    • 日志记录。
    • 性能测量。
    • 数据校验。
    • 自动填充对象属性(以此避免讨厌的null异常) 。
    • 数组负索引。
  • 相关阅读:
    C++报错undefined reference to vtable处理
    emplace_back无法支持<braceenclosed initializer list>吗?
    使用proxychains代理应用
    EasyExcel快速读写Excel数据
    解决SpringBoot跨域的三种方式
    SQL Server 锁(LOCK)大全
    MySQL预处理语句PREPARE、EXECUTE、DEALLOCATE使用大全
    C# 自定义泛型二维数组
    C# 一维数组与二维数组相互转换
    C#枚举高级应用
  • 原文地址:https://www.cnblogs.com/secoding/p/11161397.html
Copyright © 2020-2023  润新知