zoukankan      html  css  js  c++  java
  • 第7章 面向对象与原型

    1. 理解原型

    const device = {
        powerSource: "battery",
        displayDevice: true
    }
    const phone = { size: "small" }
    const huaweiPhone = { os: "HarmonyOS" }
    
    // Object.setPrototypeOf需要两个对象作为参数,
    // 它将第二个对象设置为第一个对象的原型
    Object.setPrototypeOf(phone, device);
    // 可以直接访问原型的属性
    console.log(phone.powerSource);
    // battery
    
    Object.setPrototypeOf(huaweiPhone, phone);
    // 也可以直接访问原型的原型的属性,即原型是链状的
    // 访问属性时自下而上层层查找
    console.log(huaweiPhone.displayDevice);
    // true
    
    // 用in操作符判断某个对象是否具有某个属性
    console.log("size" in huaweiPhone);
    // true
    

    2. 对象构造器与原型

    • 每一个函数都有一个原型对象,该原型对象将被自动设置为通过该函数创建的对象的原型

    2.1 实例与原型

    function Students(){}
    
    // 在对象的原型对象上添加属性
    Students.prototype.study = function() {
        return true;
    }
    
    // 通过new作为构造函数调用,新建实例
    const stu1 = new Students();
    // 实例拥有了原型对象的属性
    console.log("study" in stu1);
    // true
    
    // 直接调用函数
    const stu2 = Students();
    console.log("study" in stu2);
    // Uncaught TypeError: Cannot use 'in' operator to search for 'study' in undefined
    
    • 每个函数都具有一个原型对象
    • 每个函数的原型都有一个constructor属性,该属性指向函数本身
    graph LR; Students.prototype.constructor-->Students
    • constructor对象的原型设置为新创建的对象的原型
    graph LR; stu1-.new.->Students.prototype.constructor.prototype=Students.prototype

    2.2 实例属性:初始化过程的优先级

    // 在实际代码中不推荐这么做
    function Student() {
        this.gender = "Male";
        // 实例方法与原型方法同名
        this.sayHi = function() {
            return "Hi!";
        }
    }
    
    // 原型方法与实例方法同名
    Student.prototype.sayHi = function() {
        return "I refuse to.";
    }
    
    const stu = new Student();
    
    // 实例会隐藏(并不是替换)原型中与实例方法重名的方法
    // 在构造函数内部,关键字this指向新创建的对象
    // 所以在构造函数内添加的属性直接在新的实例上
    // 查找时是先查找实例内部再查找原型中
    console.log(stu.sayHi());
    // Hi!
    

    在函数的原型上创建对象方法可以使一个方法由所有对象实例共享

    2.3 JS动态特性的副作用:通过原型,一切都可以在运行时修改

    function Student(){
        this.type = "Primary";
    }
    
    const a1 = new Student();
    
    // 在原型上添加方法
    Student.prototype.getInfo = function(){
        return this.type;
    }
    
    // 实例可以正常访问
    console.log(a1.getInfo());
    // Primary
    
    // 将原型指向另一个对象,并在对象里定义方法
    Student.prototype = {
        getName: function() {
            return "Classified";
        }
    }
    
    // 之前实例化的对象依旧可以访问原来的方法
    console.log(a1.getInfo());
    // Primary
    
    // 但不能使用新对象的方法
    console.log(a1.getName());
    // Uncaught TypeError: a1.getName is not a function
    
    // 重新实例化一个对象
    const a2 = new Student();
    
    // 可以使用新方法了
    console.log(a2.getName());
    // Classified
    
    // 但不能使用之前原型上的方法
    console.log(a2.getInfo());
    // Uncaught TypeError: a2.getInfo is not a function
    

    对象与函数原型之间的引用关系是在对象创建时建立的,原型对象指向的改变并不会影响改变前就已经实例化的对象,即函数的原型可以被任意替换,但已经构建的实例引用旧的原型

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

    • 通过constructor属性访问创建该对象所用的函数,这个特性可以用于类型校验
    function Student(){}
    
    const a1 = new Student();
    
    // 通过typeof只能得知a1是个对象
    console.log(typeof a1);
    // object
    
    // 通过instanceof只能得知a1是Student的实例
    console.log(a1 instanceof Student);
    // true
    
    // 通过对象的constructor属性可以得到其构造函数引用
    console.log(a1.constructor === Student);
    // true
    
    // 所以可以用constructor属性来创建对象
    const a2 = new a1.constructor();
    
    console.log(a2 instanceof Student);
    // true
    

    可以用constructor属性创建对象,即使原始构造函数已经不再作用域内。但如果重写了constructor属性,那么原始值就会丢失了

    3. 实现继承

    3.1 子类的原型是父类的实例实现继承

    function People() {}
    
    People.prototype.dance = function() {
        console.log("Dancing");
    }
    
    function Student() {}
    
    // 子类的原型是父类的实例
    Student.prototype = new People();
    
    const a1 = new Student();
    
    // 可以调用
    a1.dance();
    // Dancing        
    console.log(a1 instanceof Student);
    // true        
    console.log(a1 instanceof Object);        
    // true
    console.log(a1 instanceof People);        
    // true
    

    使用People的原型对象作为Student的原型,即
    People.prototype = Student.prototype
    也可以实现继承,但强烈不建议使用。

    缺点:People原型上发生的所有变化都被同步到Student原型上

    优点:所有继承函数的原型将实时更新

    3.2 重写constructor属性的问题

    原型的重写导致constructor指向父类

    function People() {}
    
    People.prototype.dance = function() {
        console.log("Dancing");
    }
    
    function Student() {}
    
    // 子类的原型是父类的实例,但也重写了原型对象,导致后面的问题
    Student.prototype = new People();
    
    const a1 = new Student();
    
    // a1的constructor属性指向了父类
    console.log(a1.constructor === People);
    // true
    console.log(a1.constructor === Student);
    // false
    

    配置对象的属性

    属性描述 作用
    configurable true: 可以删除或修改
    false: 不允许修改
    enumerable true: 可以被for-in遍历到
    value 指定属性的值,默认为undefined
    writable true: 可以通过赋值语句修改属性值
    get 默认为undefined
    定义getter函数,当访问属性时发生调用
    不能与value和writable同时使用
    set 默认为undefined
    定义setter函数,当设置属性时发生调用
    不能与value和writable同时使用
    const book = {}
    book.name = "Ninja";
    book.price = 99.00;
    
    // 内置方法配置属性,接收三个参数:目标对象,属性,配置(对象表示)
    Object.defineProperty(book, "content", {
        configurable: false,
        enumerable: false,
        value: "Classified",
        writable: true
    });
    
    // 属性成功添加,可以访问
    console.log("content" in book);
    // true
    
    // content属性的enumerable设置为false,所以for-in无法遍历
    for(let prop in book) {
        console.log(prop);
    }
    // name 
    // price
    

    解决constructor属性被覆盖的问题

    function People() { }
    
    People.prototype.dance = function () {
        console.log("Dancing");
    }
    
    function Student() { }
    
    // 子类的原型是父类的实例,但也重写了原型对象,导致constructor属性被覆盖
    Student.prototype = new People();
    
    // 注意要配置的constructor属性是在原型上的
    Object.defineProperty(Student.prototype, "constructor", {
        enumerable: false,
        value: Student      // 让constructor重新指向原来的构造器
    });
    
    const a1 = new Student();
    
    console.log(a1.constructor === People);
    // false
    console.log(a1.constructor === Student);
    // true    -> constructor指向正确
    

    3.3 instanceof操作符:基于原型链的检测

    function Student() { }
    
    const a1 = new Student();
    
    // 在原型链中
    console.log(a1 instanceof Student);
    // true
    
    // 修改原型
    Student.prototype = {};
    
    // a1已经不在Student的原型链中
    console.log(a1 instanceof Student);
    // false
    

    4. 在ES6中使用JS的class

    // class关键字定义类
    class People{
        // 定义构造函数
        constructor(name) {
            this.name = name;
        }
        // 定义实例方法
        toString() {
            return this.name;
        }
    }
    
    // extends关键字实现继承
    class Student extends People{
        constructor(name, age) {
            // super关键字用于访问和调用父类上的静态方法
            // 在构造函数中出现时,必须在使用this关键字之前使用
            super(name);    // 调用父类的构造函数
            this.age = age;
        }
    
        sayHi() {
            return "Hi!";
        }
    
        // static关键字定义静态方法
        static compare(stu1, stu2) {
            return stu1.age - stu2.age;
        }
    }
    
    const stu1 = new Student("Wango", 24);
    
    // 实例化对象有效
    console.log(stu1 instanceof Student);
    // true
    
    // 继承有效
    console.log(stu1 instanceof People);
    // true
    
    const stu2 = new Student("Lily", 25);
    
    // 调用静态方法有效
    console.log(Student.compare(stu1, stu2));
    // -1
    
    // 调用父类方法有效
    console.log(stu1.toString());
    // Wango
    console.log(stu2.toString());
    // Lily
    

    ES6引入关键字class,但是底层依然是基于原型实现,以上代码基本等价于一下代码,但要注意下列代码中原型继承的缺陷

    function People(name) {
        this.name = name;
    }
    People.prototype.toString = function() {
        return this.name;
    }
    
    function Student(age) {
        this.age = age;
    }
    
    Student.prototype.sayHi = function() {
        return "Hi!";
    }
    
    Student.compare = function(stu1, stu2) {
        return stu1.age - stu2.age;
    }
    
    // 使用原型继承的一大缺陷:
    // 向父类传递的参数会成为所有子类实例的初始化数据
    Student.prototype = new People("Wango");
    
    Object.defineProperty(Student.prototype, "constructor", {
        enumerable: false,
        value: Student
    });
    
    
    const stu1 = new Student(24);
    
    // 实例化对象有效
    console.log(stu1 instanceof Student);
    // true
    
    // 继承有效
    console.log(stu1 instanceof People);
    // true
    
    const stu2 = new Student(25);
    
    // 调用静态方法有效
    console.log(Student.compare(stu1, stu2));
    // -1
    
    // 调用父类方法
    console.log(stu1.toString());
    // Wango
    console.log(stu2.toString());
    // Wango
    // 给父类的参数成了初始化数据,原型继承有缺陷,还有其他几种继承方式本书暂未介绍
    
  • 相关阅读:
    爬取校园新闻首页的新闻的详情,使用正则表达式,函数抽离
    网络爬虫基础练习
    Hadoop综合大作业
    hive基本操作与应用
    用mapreduce 处理气象数据集
    熟悉常用的HBase操作
    爬虫大作业
    熟悉常用的HDFS操作
    数据结构化与保存
    获取全部校园新闻
  • 原文地址:https://www.cnblogs.com/hycstar/p/14028077.html
Copyright © 2011-2022 走看看