JavaScript高级---(2)构造函数和原型、继承

x33g5p2x  于2021-09-24 转载在 JavaScript  
字(4.7k)|赞(0)|评价(0)|浏览(672)

一、构造函数和原型

1、概述

在ES6之前,对象并不是基于类创建的,而是构造函数来定义对象及其特征。

构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与 new 一起使用。我们可以把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面。
而new在执行时会有四件事情:

  1. 在内存中创建一个新的空对象。
  2. 让 this 指向这个新的对象。
  3. 执行构造函数里面的代码,给这个新对象添加属性和方法。
  4. 返回这个新对象(所以构造函数里面不需要 return)。

静态成员及实例成员

  • 静态成员:在构造函数本上添加的成员称为静态成员(利用对象.方法/属性),只能由构造函数本身来访问
  • 实例成员:在构造函数内部创建的对象成员称为实例成员(利用this),只能由实例化的对象来访问

下面这个例子可以帮助你理解:

function Stra(uname, age) {
            //构造函数里面的属性和方法我们叫做成员
            this.uname = uname;
            this.age = age;
            this.sing = function() {
                console.log("sing~~~");
            }
        }

        var ldh = new Stra('ldh', 18);
        //实例成员就是构造函数内部通过this添加的成员 sing uname age...
        //实例成员只能通过实例化对象访问
        console.log(ldh.age);
        // console.log(Stra.age); //undefined

        //静态成员 在构造函数本身添加成员
        Stra.sex = 'male'; //这里就是静态成员
        //只能通过构造函数访问
        console.log(Stra.sex);
        // console.log(ldh.sex); //undefined

2、构造函数存在的浪费内存问题及解决

我们先看一下这个例子:

function Star(uname, age) {
            //构造函数里面的属性和方法我们叫做成员
            this.uname = uname;
            this.age = age;
        }
        Star.prototype.sing = function() {
            console.log("sing~~~");
        }
        var ldh = new Star('ldh', 18);
        var zxy = new Star('zxy', 19);
        
        // 一般公共属性定义到构造函数,方法定义到原型对象
        console.log(ldh.sing === zxy.sing); //false

我们构造两个对象,判断一下两个对象的sing方法是否在同一内存,答案是false。这样就造成了内存资源的浪费。我们希望所有的对象使用同一个函数,这样就比较节省内存,那么我们要怎样做呢?

这时候我们就要提出一个构造函数的属性—prototype(构造函数原型)。
构造函数通过原型分配的函数是所有对象所共享的。
JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象。注意这个 prototype 就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。
我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法,就可以解决掉内存资源浪费的问题。

function Star(uname, age) {
            //构造函数里面的属性和方法我们叫做成员
            this.uname = uname;
            this.age = age;
        }
        Star.prototype.sing = function() {
            console.log("sing~~~");
        }
        var ldh = new Star('ldh', 18);
        var zxy = new Star('zxy', 19);
        console.log(ldh.sing === zxy.sing); //true

这时候再判断,两个对象的sing方法指向就相同了。

下面我们再介绍一个概念:对象原型 __ proto__
每一个对象都会有一个__proto__属性,指向构造函数的prototype原型对象。

  • __proto__对象原型和原型对象 prototype 是等价的
  • __proto__对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype

是不是有些晕,看下面这张图,它能帮助你更直观的理解:

3、constructor构造函数及原型链

constructor构造函数
对象原型( proto)和构造函数(prototype)原型对象里面都有一个属性 constructor 属性 ,constructor 我们称为构造函数,因为它指回构造函数本身。
constructor 主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。
一般情况下,对象的方法都在构造函数的原型对象中设置。

我们通过输出一个构造函数的prototype属性,可以看到里面存在一个constructor属性,它是一个函数,指向该构造函数。

构造函数、实例、原型对象三者的关系

原型链
我们知道只要是一个对象就有__proto__属性,同时我们还知道一个构造函数的prototype指向一个对象,那么它的__proto__属性是什么呢,我们试着打印一下。

function Star(uname, age) {
            this.name = uname;
            this.age = age;
        }
        Star.prototype.sing = function() {
            console.log("sing~~~");
        }

        var ldh = new Star('ldh', 18);
        // 1、只要是对象就有__proto__属性,指向原型对象
        console.log(Star.prototype.__proto__);

        // Object.prototype里面的__proto__指向为null

它指向一个对象Object(),它会像套娃一样无止尽的指下去吗?答案是不会的,Object.prototype里面的__proto__指向为null。
这里有一张图,更加直观:

4、扩展内置对象

了解完了原型对象,接下来我们讲一个实用性的操作,利用原型对象扩展内置对象:
我们知道Array()对象里面有很多使用的方法,比如索引了、排序了之类的,但是我们相对数组求和的时候却发现没有这个方法,那么我们用原型对象来给Array()加一个sum求和方法吧。

我们先打印一下Array的原型对象:

里面有很多方法,但是没有我们想要的sum求和方法。

我们加一个试试:

// 添加array对象的求和功能
        Array.prototype.sum = function() {
            var sum = 0
            for (var i = 0; i < this.length; i++) {
                sum += this[i];
            }
            return sum;
        }

        // var arr = [1, 2, 3, 4];
        console.log(Array.prototype);

可以看到Array()里面已经有了sum方法,能不能用呢?我们实践一下:

// 添加array对象的求和功能
        Array.prototype.sum = function() {
            var sum = 0
            for (var i = 0; i < this.length; i++) {
                sum += this[i];
            }
            return sum;
        }

        // var arr = [1, 2, 3, 4];
        var arr = new Array(1, 2, 3);
        console.log(arr.sum()); // 6
        console.log(Array.prototype);

打印结果是6,看来可以准确地输出结果呢。

这里有一点需要注意
内置对象不能给原型对象覆盖操作 Array.prototype = {} ,只能是 Array.prototype.xxx = function(){} 的方式。

二、继承

ES6之前并没有给我们提供 extends 继承。我们可以通过构造函数+原型对象模拟实现继承,被称为组合继承。

1、call()方法

es6之前是通过构造函数+原型对象实现继承的
call()可以调用这个函数并且修改函数运行时this的指向

方法:fun.call(thisArg, … ,);

实例:

function fun(x, y) {
            console.log("drink water");
            console.log(this);
            console.log(x + y);
        }

        var o = {
            name: "andy"
        }

        // 调用函数
        // fun.call();

        // 修改this的指向
        fun.call(o, 1, 2); // 此时this指向o

这里我们在调用的时候,发现fun()中this的指向已经改变。

2、借用构造函数继承父类型属性

原理: 通过 call() 把父类型的 this 指向子类型的 this ,这样就可以实现子类型继承父类型的属性。

// 父构造函数
        function Father(uname, age) {
            this.name = uname;
            this.age = age;
        }
        // 子构造函数
        function Son(uname, age, score) {
            Father.call(this, uname, age);
            this.score = score;
        }

        var son = new Son('ldh', 18, 100);
        console.log(son);

在子构造函数中,通过调用Father.call()方法可以更改Father的this指向,让其指向Son,这样就可以实现继承Father的属性了。那父亲的方法呢?我们一般使用原型对象。

3、利用原型对象继承父类型的方法

一般情况下,对象的方法都在构造函数的原型对象中设置,通过构造函数无法继承父类方法。
核心原理:

  • 将子类所共享的方法提取出来,让子类的 prototype 原型对象 = new 父类()
  • 本质:子类原型对象等于是实例化父类,因为父类实例化之后另外开辟空间,就不会影响原来父类原型对象
  • 将子类的 constructor 从新指向子类的构造函数

举个例子
父亲有挣钱的方法,儿子想从父亲那里继承该方法,并且还要有一个考试的方法。

先解决问题一:继承父亲的方法
我们可以使用Son的原型对象,指向父亲的实例化对象,这样Son的原型对象里面就有了earn()方法。

可以看到现在Son的prototype里面的prototype有了earn方法。

接下来是问题二:添加儿子的方法
直接在Son的原型对象里面添加exam方法

最后注意将Son原型对象的constructor函数指回Son。

// 父构造函数
        function Father(uname, age) {
            this.name = uname;
            this.age = age;
        }
        Father.prototype.earn = function() {
                console.log(10000);
            }
            // 子构造函数
        function Son(uname, age, score) {
            Father.call(this, uname, age);
            this.score = score;
        }

        Son.prototype = new Father();
        Son.prototype.constructor = Son;
        Son.prototype.exam = function() {
            console.log("考试");
        };

        var son = new Son('ldh', 18, 100);
        console.log(son);

放个关系图,助于理解:

下一篇:JavaScript高级— (3)ES5新方法,数组、字符串、对象

相关文章