zoukankan      html  css  js  c++  java
  • 进击JavaScript核心 --- (3)面向对象

    JS中的对象定义为:无序属性的结合,其属性可以包含基本值、对象或者函数
     
    1、定义对象的方式
     
    (1)、Object构造函数
    var student = new Object();
    student.name = 'Jim Green';
    student.gender = 'Male';
    student.age = 8;
    student.say = function() {
      console.log(`My name is ${this.name}, I'm ${this.age} years old`);
    }
    
    student.say();      // My name is Jim Green, I'm 8 years old

    (2)、对象字面量

    var student = {
      name: 'Jim Green',
      gender: 'Male',
      age: 8,
      say: function() {
        console.log(`My name is ${this.name}`)
      }
    }
    
    student.say();    // My name is Jim Green

    2、属性类型

    Object.defineProperty()方法:用于修改对象属性的默认特性
    这个方法接收三个参数:属性所在的对象,属性的名字和一个描述符对象。其中,描述符对象必须是以下两种之一(不能同时是两者)
     
    2-1、数据属性
    数据属性包含一个数据值的位置,在这个位置可以读取和写入值
     
    configurable:默认值为true。表示能否通过delete删除该属性,能否修改属性的特性,或者能否把属性修改为访问器属性
    enumerable:默认值为true。表示能否通过 for-in 循环返回属性
    writable:默认值为true。表示能否修改属性值
    value:默认值为undefined。包含这个属性的属性值,读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置
    var person = {
      age: 8,
      gender: 'male'
    };
    Object.defineProperty(person, "name", {
      enumerable: false,
      value: "Roger"
    });
    for(var key in person) {
      console.log(`key --> ${key}, value --> ${person[key]}`);
    }
    
    // key --> age, value --> 8
    // key --> gender, value --> male
    如果将name属性的configurable值改为false,并尝试删除该属性,非严格模式下无效,delete操作被忽略;严格模式下报错
    // "use strict";
    var person = {};
    Object.defineProperty(person, "name", {
      configurable: false,
      writable: true,
      value: "Roger"
    });
    console.log(person.name);       // Roger
    delete person.name;
    console.log(person.name);       // Roger    (configurable属性为false时,返回undefined)
    
    // Uncaught TypeError: Cannot delete property 'name' of #<Object> (严格模式下报错)
     
    通过修改name属性的writable将其变为只读属性,非严格模式下修改该属性无效,赋值操作被忽略;严格模式下会报错
    // "use strict"
    var person = {};
    Object.defineProperty(person, "name", {
      writable: false,
      value: "Roger"
    })
    
    console.log(person.name);       // Roger
    person.name = "Frank";
    console.log(person.name);       // Roger 
    
    // console.log(person.name);       // Uncaught TypeError: Cannot assign to read only property 'name' of object '#<Object>'
    注意:
    (1)、调用Object.defineProperty()方法时,如果不指定,configurable、enumerable和writable特性的默认值就都是false
    var person = {};
    Object.defineProperty(person, "name", {
      configurable: true,
      value: "Roger"
    });
    person.name = "Frank";
    console.log(person.name);                // Roger
    
    Object.defineProperty(person, "name", {
      writable: true,
      value: "Roger"
    });
    
    person.name = "Kobe";                   
    console.log(person.name);                // Kobe
    (2)、一旦把属性定义为不可配置的(configurable为false),就不能再把它设为可配置了。此时,再修改除 writable之外的特性,都会导致报错
    var person = {};
    Object.defineProperty(person, "name", {
      configurable: false,
      value: "Roger"
    });
    person.name = "Frank";
    console.log(person.name);                // Roger
    
    Object.defineProperty(person, "name", {  // Uncaught TypeError: Cannot redefine property: name
      writable: true,
      value: "Roger"
    });
    
    person.name = "Kobe";                   
    console.log(person.name);

    2-2、访问器属性

    访问器属性不包含数据值,它们包含一对getter和setter函数(非必需)。共包含4个特性:

     
    configurable:默认值为true;表示能否通过delete删除属性,能否修改属性的特性,或者能否把属性修改为数据属性
    enumerable:默认值为true;表示能否通过for-in循环此属性
    get:默认值为undefined;在读取属性时调用函数
    set:默认值为undefined;在写入属性时调用函数
    var person = {
      name: 'Kobe',
      _number: 8
    }
    
    Object.defineProperty(person, 'number', {
      get: function() {
        return this._number;
      },
      set: function(newValue) {
        if(newValue > 8) {
          this._number = 24;
        }
      }
    })
    
    person.number = 9;
    console.log(person._number);          // 24
    Object.defineProperties() 方法:可以通过描述符一次定义多个属性,这个方法接收两个对象参数
     
    第一个对象是要添加和修改其属性的对象
    第二个对象与第一个对象中要添加或修改的属性一一对应
    var book = {};
    Object.defineProperties(book, {
      _year: {
        writable: true,
        value: 8
      },
      year: {
        get: function() {
          return this._year;
        },
        set: function(newValue) {
          if(newValue > 8) {
            alert('111')
            this._year = 24;
          }
        }
      }
    })
    
    console.log(book);    // {_year: 8}
    book.year = 9;
    console.log(book);    // {_year: 24}
    Object.getOwnPropertyDescriptor() 方法用于读取给定属性的描述符,这个方法接收两个参数:属性所在的对象和要读取其描述符的属性名称,返回值是一个对象。
     
    如果是访问器属性,这个对象包含 configurable, enumerable, get, set;
    如果是数据属性,这个对象包含 configurable,enumerable,writeable,value;
    var book = {};
    Object.defineProperties(book, {
      _year: {
        writable: true,
        value: 8
      },
      year: {
        get: function() {
          return this._year;
        },
        set: function(newValue) {
          if(newValue > 8) {
            alert('111')
            this._year = 24;
          }
        }
      }
    });
    
    var obj1 = Object.getOwnPropertyDescriptor(book, '_year');
    var obj2 = Object.getOwnPropertyDescriptor(book, 'year');
    
    console.log(obj1);   // {value: 8, writable: true, enumerable: false, configurable: false}
    console.log(obj2);   // {get: ƒ, set: ƒ, enumerable: false, configurable: false}
    3、创建对象
     
    面向对象的编程语言都有类的概念,通过类可以创建任意多个具有相同属性和方法的对象
    // 例如:Java中定义一个Student类
    public class Student {
    
      private String name; // 姓名
      private int age; // 年龄
    
      public Student(String name, int age) { //构造器
        super();
        this.name = name;
        this.age = age;
      }
      
      // 属性的getter和setter方法
      public String getName() {
        return name;
      }
    
      public void setName(String name) {
        this.name = name;
      }
    
      public int getAge() {
        return age;
      }
    
      public void setAge(int age) {
        this.age = age;
      }
      
      // 自定义方法
      public void say() {
        System.out.println("姓名:" + this.name + "年龄:" + this.age);
      }
    
    }
    
    // 通过Student类来创建实例对象
    Student xiaoMing = new Student("XiaoMing", 12);
    Student xiaoHong = new Student("XiaoHong", 9);
    
    xiaoMing.say();   // 姓名:XiaoMing年龄:12
    xiaoHong.say();   // 姓名:XiaoHong年龄:9
    如果是通过JS来创建两个学生对象,可以用对象字面量或者Object构造函数创建单个的对象:
    var xiaoMing = {
      name: "XiaoMing",
      age: 12
    };
    
    
    var XiaoHong = {
      name: "XiaoHong",
      age: 9
    }
    这么做有两个明显的弊端,一是重复的代码太多,如果一个班级有60名学生,就要重复60次;二是可读性差,只知道是个对象,到底是个什么对象,学生的集合,人的集合还是其它?
     
    3-1、工厂模式
     
    用函数来封装以特定接口创建对象的细节
    function createStudent(name, age) {
      var obj = new Object();
      obj.name = name;
      obj.age = age;
      obj.say = function() {
        console.log(`My name is ${this.name}, I'm ${this.age} years old.`);
      };
      return obj;
    }
    
    var xiaoMing = createStudent('XiaoMing', 12);
    var xiaoHong = createStudent('XiaoHong', 8);
    
    xiaoMing.say();     // My name is XiaoMing, I'm 12 years old.
    xiaoHong.say();     // My name is XiaoHong, I'm 8 years old.
    弊端:这样做虽然很好的解决了代码重复的问题,但还是不知道创建的到底是个什么类型的对象
     
    3-2、构造函数模式
     
    使用自定义构造函数来定义对象类型的属性和方法,通常为了区别于普通函数,会将构造函数的首字母大写
    function Student(name, age) {
      this.name = name;
      this.age = age;
      this.say = function() {
        console.log(`My name is ${this.name}, I'm ${this.age} years old.`);
      };
    }
    
    var xiaoMing = new Student('XiaoMing', 12);
    var xiaoHong = new Student('XiaoHong', 8);
    
    xiaoMing.say();     // My name is XiaoMing, I'm 12 years old.
    xiaoHong.say();     // My name is XiaoHong, I'm 8 years old.
    与工厂模式相比,有以下几个不同的地方:
    -、没有显示的创建对象
    -、直接将属性和方法赋给了this对象
    -、没有return语句

    使用new操作符创建自定义对象,主要经历了4个步骤:
    -、创建一个新对象
    -、将this指向新对象
    -、为新对象添加属性
    -、返回新对象

    对象都有一个constructor属性用于标识对象的类型,该属性指向创建对象的构造函数
    console.log(xiaoMing.constructor)
    
    // ƒ Student(name, age) {
    //   this.name = name;
    //   this.age = age;
    //   this.say = function() {
    //     console.log(`My name is ${this.name}, I'm ${this.age} years old.`);
    //   };
    // }
    
    console.log(xiaoMing.constructor == Student)                  // true
    console.log(xiaoMing.constructor == xiaoHong.constructor)     // true
    console.log(xiaoMing instanceof Student)                      // true
    console.log(xiaoMing instanceof Object)                       // true
    构造函数与普通函数的唯一区别,就在于调用方式。任何函数,只要使用new操作符来调用就可以作为构造函数,如果不通过new操作符来调用,那就是普通函数
    function Student(name, age) {
      this.name = name;
      this.age = age;
      this.say = function() {
        console.log(`My name is ${this.name}, I'm ${this.age} years old.`);
      };
    }
    
    
    // 将Student当作构造函数(this指向实例对象)
    var xiaoMing = new Student('XiaoMing', 12);   
    xiaoMing.say();     // My name is XiaoMing, I'm 12 years old.
    
    
    // 将Student当作普通函数(由于Student函数属于全局作用域,因此实际上是window.Student(),this指向window)
    Student('Bob', 9);
    say();              // My name is Bob, I'm 9 years old.
    
    
    // 在特定的作用域中调用函数(this指向obj)
    var obj = new Object();
    Student.call(obj, 'Ryan', 30);
    console.log(obj);       // {name: "Ryan", age: 30, say: ƒ}
    obj.say();              // My name is Ryan, I'm 30 years old.
     
    弊端:由于方法也是对象(如果对象的属性是一个函数就称之为方法),这就意味着每次实例化一个对象,都重新创建了一个对象
    // 上面的say方法等价于
    function Student(name, age) {
      this.name = name;
      this.age = age;
      this.say = new Function(`My name is ${this.name}, I'm ${this.age} years old.`);
    }
    为了解决这个问题,似乎可以把方法提出来
    function Student(name, age) {
      this.name = name;
      this.age = age;
      this.say = say;
    }
    function say() {
      console.log(`My name is ${this.name}, I'm ${this.age} years old.`);
    }
    
    var xiaoHong = new Student('XiaoHong', 8);
    xiaoHong.say();       // My name is XiaoHong, I'm 8 years old.

    这样做的话同样存在问题,首先是封装性不太好,对象的某些属性必须依赖于全局的属性;其次,我们期望全局作用域内的函数say只能用于构造函数Student,这样就跟js的理念相冲突了

    3-3、原型模式

    每个函数都有一个prototype属性,prototype属性是一个指针,指向函数的原型对象,该对象包含所有实例对象共享的属性和方法
    function Student() {
    
    }
    Student.prototype.name = 'Bob';
    Student.prototype.age = 12;
    Student.prototype.say = function() {
      console.log(`My name is ${this.name}, I'm ${this.age} years old.`);
    }
    
    var xiaoMing = new Student();
    var xiaoHong = new Student();
    
    xiaoMing.say();   // My name is Bob, I'm 12 years old.
    xiaoHong.say();   // My name is Bob, I'm 12 years old.
    与构造模式不同的时,所有实例对象访问的都是相同的属性和同一个say方法

    对比prototype和constructor
    prototype:每个函数都有一个 prototype属性,指向一个原型对象
    constructor:每个对象都有一个 constructor属性,指向创建该对象的构造函数
    通过构造函数创建的实例对象内部都包含一个指针(内部属性),指向构造函数的原型对象,这个指针就是 [[Prototype]]。换句话说,实例对象与构造函数没有直接关系
    但是,脚本中没有标准的方式访问[[Prototype]],一些现代浏览器中可以通过__proto__属性来访问
    console.log(xiaoMing.__proto__);      // {name: "Bob", age: 12, say: ƒ, constructor: ƒ}
    console.log(xiaoMing.__proto__ === Student.prototype);  // true

    __proto__属性是一个访问器属性,其中包含了getter(读取器)和 setter(设置器)
    getter:暴露了一个对象的内部[[Prototype]],这个值就是构造器函数的prototype属性,如:Array.prototype, Object.prototype
    setter:允许对象的[[Prototype]]被更改
    虽然无法直接访问 [[Prototype]] 属性,但可以通过 isPrototypeOf() 方法来测试一个对象是否存在于另一个对象的原型链上

    prototypeObj.isPrototypeOf(object)
    参数:在object对象的原型链上搜寻
    返回值:Boolean
    console.log(Student.prototype.isPrototypeOf(xiaoMing));   // true
    console.log(Student.prototype.isPrototypeOf(xiaoHong));   // true
    // 说明实例对象 xiaoMing和xiaoHong都存在于Student.prototype的原型链上
    尽管部分现代浏览器都实现了__proto__属性,但是该属性从未被包含在ECMA规范中,因此不推荐使用。

    从ES5开始,[[Prototype]] 可以通过 Object.getPrototypeOf() 访问器访问,用于返回指定对象的原型
    console.log(Object.getPrototypeOf(xiaoHong));  // {name: "Bob", age: 12, say: ƒ, constructor: ƒ}
    console.log(Object.getPrototypeOf(xiaoHong) === Student.prototype);   // true
    当为某个实例对象添加同名属性时,这个属性就会屏蔽掉原型对象中保存的同名属性,也就是说,如果实例对象自己有某个属性,就不会去它的原型对象上找
    xiaoMing.name = 'XiaoMing';
    
    console.log(xiaoMing.name);   // XiaoMing
    console.log(xiaoHong.name);   // Bob

    为了进一步对比,可以删除掉实例对象 xiaoMing 的属性name,然后再访问该属性

    delete xiaoMing.name;
    
    console.log(xiaoMing.name);   // Bob
    console.log(xiaoHong.name);   // Bob
    当前测试的是属性值是基本类型的情况,如果属性值是引用类型就不再屏蔽了
    function Student() {
      
    }
    Student.prototype = {
      name: 'Bob',
      age: 12,
      course: ['Chinese', 'Math']
    }
    
    var xiaoMing = new Student();
    var xiaoHong = new Student();
    
    xiaoMing.name = 'XiaoMing';
    console.log(xiaoMing.name);       // XiaoMing
    console.log(xiaoHong.name);       // Bob
    
    xiaoMing.course.push('English');
    console.log(xiaoMing.course);     // ["Chinese", "Math", "English"]
    console.log(xiaoHong.course);     // ["Chinese", "Math", "English"]
    如果需要判断一个属性是存在于实例对象中,还是原型对象中,可以使用 obj.hasOwnProperty(prop) 方法
    xiaoMing.name = 'XiaoMing';
    
    console.log(xiaoMing.hasOwnProperty('name'));   // true     --- 来自实例
    console.log(xiaoHong.hasOwnProperty('name'));   // false    --- 来自原型
    // 实例对象xiaoMing有自己的name属性,xiaoHong则没有 
    in操作符:只要通过对象能够访问到给定的属性(不管是对象自身的属性还是原型对象的属性),都返回true
    hasProperty():只有属性存在于对象时才返回true
    xiaoMing.name = 'XiaoMing';
    
    console.log('name' in xiaoMing);                // true
    console.log(xiaoMing.hasOwnProperty('name'));   // true
    
    console.log('name' in xiaoHong);                // true
    console.log(xiaoHong.hasOwnProperty('name'));   // false
    因此,要判断一个属性是原型中的属性,只需要同时满足in返回true,hasOwnProperty()返回false即可
    xiaoMing.name = 'XiaoMing';
    
    // 判断一个属性仅存在于对象的原型中
    function checkPropertyInPrototype(Object, prop) {
      return (prop in Object) && !Object.hasOwnProperty(prop)
    }
    
    console.log(checkPropertyInPrototype(xiaoMing, 'name'));    // false
    console.log(checkPropertyInPrototype(xiaoHong, 'name'));    // true
    for-in 循环不仅会遍历对象自身的属性,还会遍历对象原型的属性(这里说的属性必须是可枚举的)
    function Student() {
    
    }
    Student.prototype.name = 'Bob';
    Student.prototype.age = 12;
    Student.prototype.say = function() {
      console.log(`My name is ${this.name}, I'm ${this.age} years old.`);
    }
    
    var xiaoHong = new Student();
    xiaoHong.gender = 'female';
    
    for(var prop in xiaoHong) {
      console.log(prop +' --> '+ xiaoHong[prop]);
    }
    
    /*
    gender --> female
    name --> Bob
    age --> 12
    say --> function() {
      console.log(`My name is ${this.name}, I'm ${this.age} years old.`);
    }
    */
    弊端:所有实例只能共享属性,无法通过构造函数初始化参数
     
    3-4、组合构造函数模式和原型模式
     
    这也是创建自定义类型的最常见方式,构造函数模式用于定义实例属性,原型模式用于定义方法和共有的属性
    function Person(name, age) {
      this.name = name;
      this.age = age;
    }
    Person.prototype = {
      say: function() {
        console.log(`My name is ${this.name}, I'm ${this.age} years old.`);
      }
    }
    
    var xiaoMing = new Person('XiaoMing', 12);
    var xiaoHong = new Person('xiaoHong', 9);
    
    xiaoMing.say();     // My name is XiaoMing, I'm 12 years old.
    xiaoHong.say();     // My name is xiaoHong, I'm 9 years old.

    4、继承

    JS中的继承主要是依靠原型链来实现的

    4-1、原型链

    简单回顾下构造函数、原型和实例对象的关系

    (1)、每个构造函数都包含一个prototype属性指向原型对象

    (2)、每个原型对象都包含一个constructor指针指向构造函数

    (3)、每个实例都包含一个[[prototype]]指针指向构造函数的原型对象

    原型链的基本思想就是利用原型,让一个引用类型继承另一个引用类型的属性和方法

    具体说就是让原型对象等于另一个类型的实例,由于该实例包含指向其原型对象的指针,因此,此时的原型对象也包含了另一个原型对象的 指针。层层向上直到一个对象的原型对象是null。根据定义,null没有原型,并作为原型链中的最后一个环节

    function Person() {
        
      }
      Person.prototype = {
        leg: 4,
        ear: 2
      }
    
      function Student() {
        
      }
    
      // 让Student的原型对象等于Person的实例,因此Student的原型对象也包含了指向Person的原型对象的指针
      Student.prototype = new Person();
    
      // 给Student的原型对象添加方法
      Student.prototype.say = function() {
        console.log(`I have ${this.ear} ears and ${this.leg} legs.`);
      }
    
      var student = new Student();
      student.say();   // I have 2 ears and 4 legs. 
      console.log(student.toString());  // [object Object]
    
      console.log(Object.getPrototypeOf(student) === Student.prototype);  // true (student实例的原型对象就是Student.prototype)
      console.log(Person.prototype.isPrototypeOf(student));               // true (student实例存在于Person对象的原型链上)
      console.log(student instanceof Person);                             // true (student就是Person对象的实例)
      console.log(student.hello());                                       // Uncaught TypeError: student.hello is not a function

    图中所示,红色的粗线条代表的就是原型链,绿色细线条代表是构造函数与其原型对象之间的关联。

    执行 student.say(),student实例自己没有say方法,于是沿着原型链在它的原型对象是找到了,但是该原型对象中并没有ear和leg属性,由于该原型对象包含了指向Person原型对象的指针,因此,继续沿着原型链向上查找,在Person的原型对象上找到了ear和leg属性

    执行 student.toString(),student实例没有提供该方法,于是沿着原型链逐级向上查找,由于所有引用类型都继承自Object,最终在Object的原型对象上找到了

    执行 student.hello(),student实例没有提供该方法,沿着原型链逐级向上查找,都没有找到该方法,因此会报错

    注意:

    (1)、通过原型链实现继承时,不能使用对象字面量创建原型方法,否则会重写原型链

    function Person() {
        
    }
    Person.prototype = {
      say: function() {
        console.log('hello world');
      }
    }
    
    function Student() {
      
    }
    
    // 让Student的原型对象等于Person的实例,因此Student的原型对象也包含了指向Person的原型对象的指针
    Student.prototype = new Person();
    
    // 使用对象字面量给Student创建原型方法
    Student.prototype = {
      class: 2,
      grade: 1
    }
    
    var student = new Student();
    student.say();                // Uncaught TypeError: student.say is not a function

    此例中,先是把Person的实例赋值给Student的原型对象,构建出了一条图中红色粗线部分显示的原型链。然后,使用对象字面量的方法使得Student的原型对象指向了一个实例对象,切断了原来的原型链,重新构建出图中蓝色粗线部分的原型链,自然就找不到say方法了

    (2)、给原型添加方法的代码一定要放在替换原型的语句之后。如子类型重写父类型中的某个方法,或在子类型中添加一个父类型中不存在的方法

    function Person() {
        
    }
    Person.prototype = {
      say: function() {
        console.log('hello world');
      }
    }
    
    function Student() {
      
    }
    
    // 让Student继承Person
    Student.prototype = new Person();
    
    // 子类型重写父类型中的方法
    Student.prototype.say = function() {
      console.log(`I'm Iron man`)
    }
    
    // 子类型中添加一个父类型中不存在的方法
    Student.prototype.walk = function() {
      console.log('walk with legs')
    }
    
    var student = new Student();
    
    student.say();                // I'm Iron man
    student.walk();               // walk with legs

    首先确定了Student继承Person这一继承关系,Student原型对象可以读取到Person原型对象中的say方法,然后为Student原型对象添加的重写和新方法,会覆盖掉原来的say方法

    function Person() {
        
    }
    Person.prototype = {
      say: function() {
        console.log('hello world');
      }
    }
    
    function Student() {
      
    }
    
    // 子类型重写父类型中的方法
    Student.prototype.say = function() {
      console.log(`I'm Iron man`)
    }
    
    // 子类型中添加一个父类型中不存在的方法
    Student.prototype.walk = function() {
      console.log('walk with legs')
    }
    
    // 让Student继承Person
    Student.prototype = new Person();
    
    var student = new Student();
    
    
    console.log(Person.prototype.isPrototypeOf(student));  // true
    student.say();                // hello world
    student.walk();               // Uncaught TypeError: student.walk is not a function

    与前面唯一的区别就是继承关系是在Student原型对象添加方法之后确定的,尽管student实例和Person的原型对象依然在同一条原型链上,但是会用Person原型对象中的属性和方法覆盖掉Student原型对象中的属性和方法,导致输出和前面的不一样

    原型链弊端:
    (1)、针对属性值是引用类型的情况,当某一个实例对象改变该共享属性时,其它实例也会随之改变

    function Person() {
      this.course = ['chinese', 'math'];
    }
    function Student() {
    
    }
    Student.prototype = new Person();
    
    var student1 = new Student();
    
    console.log(student1.course);      // ["chinese", "math"]
    
    student1.course.push('english');
    var student2 = new Student();
    console.log(student2.course);      // ["chinese", "math", "english"]

    (2)、创建子类型的实例时,不能向父类型的构造函数中传递参数。实际上就是一旦给父类型的构造函数传递参数,就会影响所有的实例对象

    function Person(name, age) {
      this.name = name;
      this.age = age;
    }
    function Student() {
    
    }
    // 此处调用父类型的构造函数是需要传参的
    Student.prototype = new Person('Bob', 12); 
    
    var s1 = new Student();
    var s2 = new Student();
    
    console.log(s1.name);     // Bob
    console.log(s2.name);     // Bob

    4-2、借用构造函数

    用于解决原型中包含引用类型值所带来的问题

    基本思想是在子类型构造函数中调用父类型的构造 函数,通过call()或apply()方法在(将来)新创建的对象上执行构造函数

    function Person(name, age) {
      this.name = name;
      this.age = age;
      this.say = function() {
        console.log(`My name is ${this.name}, I'm ${this.age} years old`)
      }
    }
    
    function Student(name, age, grade) {
      Person.call(this, name, age);
      this.grade = grade;
    }
    function Teacher(name, age, height) {
      Person.apply(this, [name, age]);
      this.height = height;
    }
    
    var student = new Student("Jim", 9, 4);
    var teacher = new Teacher('Mr Lee', 33, 1.75);
    
    student.say();  // My name is Jim, I'm 9 years old
    teacher.say();  // My name is Mr Lee, I'm 33 years old

    通过这种方式,子类型不仅可以继承父类型,还可以向父类型构造函数传递参数

    如何解决原型中包含引用类型值带来的问题?

    function Person() {
      this.course = ['chinese', 'math'];
    }
    function Student() {
      Person.call(this);
    }
    
    var s1 = new Student();
    
    console.log(s1.course);       // ["chinese", "math"]
    s1.course.push('english');
    console.log(s1.course);       // ["chinese", "math", "english"]
    
    var s2 = new Student();
    console.log(s2.course);       // ["chinese", "math"]

    利用call方法将this绑定到了实例对象,所以即便修改了引用类型的值,也只是在实例对象的作用域范围内,不影响其他实例

    弊端:方法都在构造函数中定义,如果在父类型的原型对象中定义方法,对子类型还是不可见的

    function Person(name, age) {
      this.name = name;
      this.age = age;
    }
    Person.prototype.say = function() {
      console.log(`My name is ${this.name}`);
    }
    
    function Student(name, age) {
      Person.call(this, name, age);
    }
    
    var s = new Student("Jim", 2);
    s.say();     // Uncaught TypeError: s.say is not a function

    4-3、组合继承

    就是将原型链和借用构造函数两种方法组合在一起实现继承,这也是JS中最常用的继承模式

    思路:使用原型链实现对原型属性和方法的继承,通过借用构造函数实现对实例属性的继承

    function Person(name) {
      this.name = name;
      this.course = ['chinese', 'math'];
    }
    Person.prototype.say = function() {
      console.log(`My name is ${this.name}`);
    }
    
    function Student(name, age) {
      Person.call(this, name);     // 继承实例属性
      this.age = age;
    }
    
    // 继承原型属性和方法
    Student.prototype = new Person();
    
    // 添加子类方法
    Student.prototype.sayAge = function() {
      console.log(`I'm ${this.age} years old`);
    }
    
    // 重写父类方法
    Student.prototype.say = function() {
      console.log(`My name is ${this.name}, I'm ${this.age} years old`);
    }
    
    var s1 = new Student('Bob', 8);
    
    // 修改引用类型值的属性
    s1.course.push('english');
    
    s1.sayAge();                  // I'm 8 years old
    s1.say();                     // My name is Bob, I'm 8 years old
    console.log(s1.course);       // ["chinese", "math", "english"]
    
    var s2 = new Student('Jim', 11);
    console.log(s2.course);       // ["chinese", "math"]
  • 相关阅读:
    Postman使用教程
    CAD和ArcGIS转换 矢量配准
    SAP CRM Advanced search和Simple search里Max hit表现行为的差异
    SAP CRM Product simple search的启用步骤
    如何快速定位SAP CRM订单应用(Order Application)错误消息抛出的准确位置
    如何动态修改SAP CRM WebClient UI表格栏的宽度
    如何在SAP CRM WebClient UI里创建web service并使用ABAP消费
    如何处理SAP CRM Web Service错误
    如何使用SAP CRM WebClient UI实现一个类似新浪微博的字数统计器
    如何开启SAP CRM基于WORD模板创建附件的功能
  • 原文地址:https://www.cnblogs.com/rogerwu/p/10970848.html
Copyright © 2011-2022 走看看