JavaScript
是一种基于原型的语言,它没有类的概念,而是通过原型对象来实现对象之间的继承和共享属性和方法。本文将介绍JS
中的原型和继承的基本概念和用法,以及如何通过构造函数和new
操作符来创建一类具有相同结构和行为的对象,并通过设置一个对象的原型对象来实现子类继承父类的功能。如果你想了解JS
中的原型和继承是如何工作的,以及如何利用它们来编写更优雅和高效的代码,那么本文值得一读。
JavaScript
是一种基于原型的语言,这意味着它没有类的概念,而是通过原型对象来实现对象之间的继承和共享属性。本文将介绍JS
中的原型和继承的基本概念和用法。
原型是一个对象,它可以作为其他对象的模板,提供一些共有的属性和方法。在JS
中,每个对象都有一个内部属性[[Prototype]]
,它指向该对象的原型对象。我们可以通过Object.getPrototypeOf(obj)
或者obj.__proto__
(不推荐)来获取一个对象的原型对象。
例如,我们创建一个普通对象obj
,它的原型对象就是Object.prototype
,这是所有对象的默认原型对象。
jslet obj = {name: "Alice", age: 20};
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true
console.log(obj.__proto__ === Object.prototype); // true
我们也可以通过Object.create(proto)
来创建一个以指定对象为原型的新对象。例如,我们创建一个以obj
为原型的新对象obj2
。
jslet obj2 = Object.create(obj);
console.log(Object.getPrototypeOf(obj2) === obj); // true
console.log(obj2.__proto__ === obj); // true
我们可以通过原型链来访问一个对象的属性和方法。原型链是一系列的原型对象,从当前对象开始,一直到Object.prototype
结束(Object.prototype
的原型是null
)。当我们访问一个对象的某个属性或方法时,JS
会先在当前对象上查找,如果找不到,就会沿着原型链向上查找,直到找到或者到达原型链的末端。
例如,我们访问obj2.name
时,JS
会先在obj2
上查找,发现没有这个属性,就会沿着原型链向上查找,在obj
上找到了这个属性,并返回其值。
jsconsole.log(obj2.name); // Alice
我们也可以通过修改一个对象的原型来改变其继承的属性和方法。例如,我们给obj
添加一个新的属性gender
,那么所有以obj
为原型的对象都会继承这个属性。
jsobj.gender = "female";
console.log(obj2.gender); // female
但是,如果我们给一个对象添加或修改一个与其原型同名的属性或方法,那么就会在该对象上创建一个自有的属性或方法,覆盖掉原型上的同名属性或方法。例如,我们给obj2
添加一个新的属性name
,那么就会在obj2
上创建一个自有的属性name
,覆盖掉原型上的同名属性。
jsobj2.name = "Bob";
console.log(obj2.name); // Bob
console.log(obj.name); // Alice
继承是一种实现代码复用和抽象化的机制,在JS
中,我们可以通过原型来实现继承。我们可以通过构造函数和new
操作符来创建具有相同结构和行为的一类对象,并通过修改构造函数的prototype
属性来指定这类对象共享的原型对象。
例如,我们定义一个构造函数Person
,用来创建具有name
和age
属性和sayHello
方法的人类对象,并给Person.prototype
添加这些属性和方法。
jsfunction Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function() {
console.log("Hello, I'm " + this.name);
};
然后我们用new
操作符来创建两个Person
实例p1
和p2
,并调用它们的sayHello
方法。
jslet p1 = new Person("Alice", 20);
let p2 = new Person("Bob", 21);
p1.sayHello(); // Hello, I'm Alice
p2.sayHello(); // Hello, I'm Bob
我们可以看到,p1
和p2
都有自有的name
和age
属性,但是它们共享同一个sayHello
方法,这个方法是定义在Person.prototype
上的。我们也可以看到,p1
和p2
的原型对象都是Person.prototype
。
jsconsole.log(p1.sayHello === p2.sayHello); // true
console.log(p1.sayHello === Person.prototype.sayHello); // true
console.log(Object.getPrototypeOf(p1) === Person.prototype); // true
console.log(Object.getPrototypeOf(p2) === Person.prototype); // true
我们也可以通过修改Person.prototype
来改变所有Person
实例共享的属性和方法。例如,我们给Person.prototype
添加一个新的属性gender
,并给sayHello
方法添加一句话。
jsPerson.prototype.gender = "unknown";
Person.prototype.sayHello = function() {
console.log("Hello, I'm " + this.name + ", and my gender is " + this.gender);
};
然后我们再次调用p1
和p2
的sayHello
方法,可以看到输出结果发生了变化。
jsp1.sayHello(); // Hello, I'm Alice, and my gender is unknown
p2.sayHello(); // Hello, I'm Bob, and my gender is unknown
我们也可以通过原型来实现子类继承父类的功能。我们可以通过Object.create(proto)
或者Object.setPrototypeOf(obj, proto)
来设置一个对象的原型对象。例如,我们定义一个构造函数Student
,用来创建具有name
,age
和score
属性的学生类对象,并让Student.prototype
继承自Person.prototype
。
jsfunction Student(name, age, score) {
Person.call(this, name, age); // 调用父类构造函数,继承父类属性
this.score = score; // 添加子类属性
}
Student.prototype = Object.create(Person.prototype); // 设置子类原型为父类原型的副本,继承父类方法
Student.prototype.constructor = Student; // 修复子类构造函数指向
然后我们用new
操作符来创建一个Student
实例s
,并调用它的sayHello
方法。
jslet s = new Student("Charlie", 22, 90);
s.sayHello(); // Hello, I'm Charlie, and my gender is unknown
我们可以看到,s
继承了Person
类的name
,age
和gender
属性和sayHello
方法,同时也有自有的score
属性。我们也可以看到,s
的原型对象是Student.prototype
,而Student.prototype
的原型对象是Person.prototype
,形成了一个原型链。
jsconsole.log(s.name); // Charlie
console.log(s.age); // 22
console.log(s.gender); // unknown
console.log(s.score); // 90
console.log(Object.getPrototypeOf(s) === Student.prototype); // true
console.log(Object.getPrototypeOf(Student.prototype) === Person.prototype); // true
我们也可以通过修改Student.prototype
来给子类添加或覆盖父类的属性和方法。例如,我们给Student.prototype
添加一个新的方法study
,并覆盖父类的sayHello
方法。
jsStudent.prototype.study = function() {
console.log("I'm studying hard to get a high score.");
};
Student.prototype.sayHello = function() {
console.log("Hi, I'm " + this.name + ", and my score is " + this.score);
};
然后我们再次调用s
的sayHello
和study
方法,可以看到输出结果发生了变化。
jss.sayHello(); // Hi, I'm Charlie, and my score is 90
s.study(); // I'm studying hard to get a high score.
JS
中的原型和继承是一种基于对象而非类的编程范式,它通过原型对象来实现对象之间的属性和方法的共享和继承。我们可以通过构造函数和new
操作符来创建一类具有相同结构和行为的对象,并通过修改构造函数的prototype
属性来指定这类对象共享的原型对象。
我们也可以通过原型链来访问或修改一个对象继承自其原型对象的属性和方法。我们也可以通过设置一个对象的原型对象来实现子类继承父类的功能。这种方式可以实现代码的复用和抽象化,但也要注意避免原型污染和性能损耗的问题。
本文作者:CreatorRay
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!