JavaScript原型链和继承如何实现

发布时间:2022-05-23 15:43:15 作者:iii
来源:亿速云 阅读:151

JavaScript原型链和继承如何实现

目录

  1. 引言
  2. 原型链
  3. 继承
  4. ES6中的类与继承
  5. 总结

引言

JavaScript是一种基于原型的面向对象编程语言,与传统的基于类的语言(如Java、C++)不同,JavaScript通过原型链来实现对象的继承。理解原型链和继承机制对于掌握JavaScript的核心概念至关重要。本文将深入探讨JavaScript中的原型链和继承机制,并通过实例详细讲解各种继承方式的实现。

原型链

原型对象

在JavaScript中,每个对象都有一个原型对象(prototype),原型对象也是一个对象,它包含了可以由特定类型的所有实例共享的属性和方法。当我们创建一个新对象时,这个对象会自动继承其原型对象的属性和方法。

function Person(name) {
    this.name = name;
}

Person.prototype.sayHello = function() {
    console.log(`Hello, my name is ${this.name}`);
};

const person1 = new Person('Alice');
person1.sayHello(); // 输出: Hello, my name is Alice

在上面的例子中,Person函数的prototype属性指向了一个对象,这个对象就是Person实例的原型对象。Person实例person1继承了Person.prototype上的sayHello方法。

原型链的形成

原型链是由对象的原型对象组成的链式结构。当我们访问一个对象的属性或方法时,如果该对象本身没有这个属性或方法,JavaScript引擎会沿着原型链向上查找,直到找到该属性或方法,或者到达原型链的顶端(null)。

function Person(name) {
    this.name = name;
}

Person.prototype.sayHello = function() {
    console.log(`Hello, my name is ${this.name}`);
};

function Student(name, grade) {
    Person.call(this, name);
    this.grade = grade;
}

Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

Student.prototype.sayGrade = function() {
    console.log(`I am in grade ${this.grade}`);
};

const student1 = new Student('Bob', 10);
student1.sayHello(); // 输出: Hello, my name is Bob
student1.sayGrade(); // 输出: I am in grade 10

在这个例子中,Student函数的原型对象是Person.prototype的实例,因此Student实例student1可以访问Person.prototype上的sayHello方法。同时,Student.prototype上定义了一个新的方法sayGradestudent1也可以访问这个方法。

原型链的查找机制

当我们访问一个对象的属性或方法时,JavaScript引擎会按照以下步骤进行查找:

  1. 首先查找对象本身是否具有该属性或方法。
  2. 如果对象本身没有该属性或方法,则查找对象的原型对象(__proto__)是否具有该属性或方法。
  3. 如果原型对象也没有该属性或方法,则继续查找原型对象的原型对象,直到找到该属性或方法,或者到达原型链的顶端(null)。
const obj = {
    a: 1,
    b: 2
};

const obj2 = Object.create(obj);
obj2.c = 3;

console.log(obj2.a); // 输出: 1
console.log(obj2.b); // 输出: 2
console.log(obj2.c); // 输出: 3
console.log(obj2.d); // 输出: undefined

在这个例子中,obj2继承了obj的属性ab,并且自身定义了属性c。当我们访问obj2.a时,JavaScript引擎首先查找obj2本身是否有属性a,发现没有,于是继续查找obj2的原型对象obj,发现obj有属性a,于是返回1。当我们访问obj2.d时,由于obj2本身和其原型对象obj都没有属性d,最终返回undefined

继承

在JavaScript中,继承是通过原型链来实现的。JavaScript提供了多种继承方式,每种方式都有其优缺点。下面我们将详细介绍这些继承方式。

原型链继承

原型链继承是最简单的继承方式,它通过将子类的原型对象设置为父类的实例来实现继承。

function Parent() {
    this.name = 'Parent';
}

Parent.prototype.sayHello = function() {
    console.log(`Hello, my name is ${this.name}`);
};

function Child() {
    this.name = 'Child';
}

Child.prototype = new Parent();

const child = new Child();
child.sayHello(); // 输出: Hello, my name is Child

在这个例子中,Child函数的原型对象是Parent的实例,因此Child实例child可以访问Parent.prototype上的sayHello方法。

优点: - 简单易用,代码量少。

缺点: - 所有子类实例共享同一个父类实例,如果父类实例中有引用类型的属性,子类实例之间会相互影响。 - 无法向父类构造函数传递参数。

构造函数继承

构造函数继承通过在子类构造函数中调用父类构造函数来实现继承。

function Parent(name) {
    this.name = name;
}

Parent.prototype.sayHello = function() {
    console.log(`Hello, my name is ${this.name}`);
};

function Child(name, age) {
    Parent.call(this, name);
    this.age = age;
}

const child = new Child('Alice', 10);
console.log(child.name); // 输出: Alice
console.log(child.age); // 输出: 10
child.sayHello(); // 报错: child.sayHello is not a function

在这个例子中,Child构造函数中调用了Parent构造函数,并将this绑定到Child实例上,因此Child实例child具有name属性。但是,Child实例无法访问Parent.prototype上的sayHello方法。

优点: - 可以在子类构造函数中向父类构造函数传递参数。 - 子类实例之间不会共享父类实例的属性。

缺点: - 无法继承父类原型对象上的属性和方法。

组合继承

组合继承结合了原型链继承和构造函数继承的优点,通过在子类构造函数中调用父类构造函数,并将子类的原型对象设置为父类的实例来实现继承。

function Parent(name) {
    this.name = name;
}

Parent.prototype.sayHello = function() {
    console.log(`Hello, my name is ${this.name}`);
};

function Child(name, age) {
    Parent.call(this, name);
    this.age = age;
}

Child.prototype = new Parent();
Child.prototype.constructor = Child;

const child = new Child('Alice', 10);
console.log(child.name); // 输出: Alice
console.log(child.age); // 输出: 10
child.sayHello(); // 输出: Hello, my name is Alice

在这个例子中,Child构造函数中调用了Parent构造函数,并将this绑定到Child实例上,因此Child实例child具有name属性。同时,Child的原型对象是Parent的实例,因此Child实例可以访问Parent.prototype上的sayHello方法。

优点: - 可以在子类构造函数中向父类构造函数传递参数。 - 子类实例之间不会共享父类实例的属性。 - 子类实例可以继承父类原型对象上的属性和方法。

缺点: - 父类构造函数会被调用两次,一次在子类构造函数中,一次在设置子类原型对象时。

原型式继承

原型式继承通过创建一个临时的构造函数,并将其原型对象设置为父类对象,然后返回这个临时构造函数的实例来实现继承。

function createObject(obj) {
    function F() {}
    F.prototype = obj;
    return new F();
}

const parent = {
    name: 'Parent',
    sayHello: function() {
        console.log(`Hello, my name is ${this.name}`);
    }
};

const child = createObject(parent);
child.name = 'Child';
child.sayHello(); // 输出: Hello, my name is Child

在这个例子中,createObject函数创建了一个临时的构造函数F,并将其原型对象设置为parent对象,然后返回F的实例childchild继承了parent对象的属性和方法。

优点: - 简单易用,代码量少。

缺点: - 所有子类实例共享同一个父类对象,如果父类对象中有引用类型的属性,子类实例之间会相互影响。 - 无法向父类构造函数传递参数。

寄生式继承

寄生式继承是在原型式继承的基础上,通过增强子类对象来实现继承。

function createObject(obj) {
    function F() {}
    F.prototype = obj;
    return new F();
}

function createChild(parent) {
    const child = createObject(parent);
    child.sayHello = function() {
        console.log(`Hello, my name is ${this.name}`);
    };
    return child;
}

const parent = {
    name: 'Parent'
};

const child = createChild(parent);
child.name = 'Child';
child.sayHello(); // 输出: Hello, my name is Child

在这个例子中,createChild函数在createObject函数的基础上,为子类对象child添加了一个新的方法sayHello

优点: - 可以在子类对象上添加新的属性和方法。

缺点: - 所有子类实例共享同一个父类对象,如果父类对象中有引用类型的属性,子类实例之间会相互影响。 - 无法向父类构造函数传递参数。

寄生组合式继承

寄生组合式继承是组合继承的改进版,它通过创建一个临时的构造函数,并将其原型对象设置为父类的原型对象,然后将子类的原型对象设置为这个临时构造函数的实例来实现继承。

function inheritPrototype(child, parent) {
    const prototype = Object.create(parent.prototype);
    prototype.constructor = child;
    child.prototype = prototype;
}

function Parent(name) {
    this.name = name;
}

Parent.prototype.sayHello = function() {
    console.log(`Hello, my name is ${this.name}`);
};

function Child(name, age) {
    Parent.call(this, name);
    this.age = age;
}

inheritPrototype(Child, Parent);

const child = new Child('Alice', 10);
console.log(child.name); // 输出: Alice
console.log(child.age); // 输出: 10
child.sayHello(); // 输出: Hello, my name is Alice

在这个例子中,inheritPrototype函数创建了一个临时的构造函数,并将其原型对象设置为Parent.prototype,然后将Child.prototype设置为这个临时构造函数的实例。这样,Child实例可以继承Parent.prototype上的属性和方法,同时避免了组合继承中父类构造函数被调用两次的问题。

优点: - 可以在子类构造函数中向父类构造函数传递参数。 - 子类实例之间不会共享父类实例的属性。 - 子类实例可以继承父类原型对象上的属性和方法。 - 父类构造函数只会被调用一次。

缺点: - 代码相对复杂。

ES6中的类与继承

ES6引入了class关键字,使得JavaScript中的类和继承更加直观和易于理解。下面我们将介绍ES6中的类和继承机制。

class关键字

class关键字用于定义一个类,类中可以定义构造函数、实例方法、静态方法等。

class Person {
    constructor(name) {
        this.name = name;
    }

    sayHello() {
        console.log(`Hello, my name is ${this.name}`);
    }
}

const person = new Person('Alice');
person.sayHello(); // 输出: Hello, my name is Alice

在这个例子中,Person类定义了一个构造函数constructor和一个实例方法sayHelloPerson实例person可以访问sayHello方法。

extends关键字

extends关键字用于实现类的继承,子类可以继承父类的属性和方法。

class Person {
    constructor(name) {
        this.name = name;
    }

    sayHello() {
        console.log(`Hello, my name is ${this.name}`);
    }
}

class Student extends Person {
    constructor(name, grade) {
        super(name);
        this.grade = grade;
    }

    sayGrade() {
        console.log(`I am in grade ${this.grade}`);
    }
}

const student = new Student('Bob', 10);
student.sayHello(); // 输出: Hello, my name is Bob
student.sayGrade(); // 输出: I am in grade 10

在这个例子中,Student类继承了Person类,并定义了一个新的方法sayGradeStudent实例student可以访问Person类的sayHello方法和Student类的sayGrade方法。

super关键字

super关键字用于在子类中调用父类的构造函数或方法。

class Person {
    constructor(name) {
        this.name = name;
    }

    sayHello() {
        console.log(`Hello, my name is ${this.name}`);
    }
}

class Student extends Person {
    constructor(name, grade) {
        super(name);
        this.grade = grade;
    }

    sayHello() {
        super.sayHello();
        console.log(`I am in grade ${this.grade}`);
    }
}

const student = new Student('Bob', 10);
student.sayHello(); // 输出: Hello, my name is Bob
                    // 输出: I am in grade 10

在这个例子中,Student类的sayHello方法中调用了Person类的sayHello方法,并添加了额外的输出。

总结

JavaScript中的原型链和继承机制是其面向对象编程的核心概念。通过原型链,JavaScript实现了对象的继承和属性查找。JavaScript提供了多种继承方式,包括原型链继承、构造函数继承、组合继承、原型式继承、寄生式继承和寄生组合式继承。每种继承方式都有其优缺点,开发者可以根据实际需求选择合适的继承方式。

ES6引入了classextendssuper关键字,使得JavaScript中的类和继承更加直观和易于理解。通过class关键字,开发者可以更方便地定义类和实现继承。

理解JavaScript中的原型链和继承机制对于掌握JavaScript的核心概念至关重要。希望本文能够帮助读者深入理解JavaScript中的原型链和继承机制,并在实际开发中灵活运用。

推荐阅读:
  1. 原型链继承
  2. JavaScript原型继承和原型链原理详解

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

javascript

上一篇:JavaScript的Write和Writeln有哪些区别

下一篇:基于Matlab如何制作一个数独求解器

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》