原型
在JavaScript中,原型(prototype)是极为重要且独特的概念,对对象创建和继承起着关键作用。它可分为
- 函数原型(prototype,显示原型)
- 对象原型(proto,隐式原型)。
理解原型机制有助于深入掌握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');
const person2 = new Person('Bob');
person1.sayHello(); // 输出:Hello, my name is Alice
person2.sayHello(); // 输出:Hello, my name is Bob
函数原型与实例对象的关系
- 属性和方法修改
-
- 实例对象可修改继承自函数原型的属性值,但不能直接修改原型上的属性。若直接给原型属性赋值,实际是给实例对象新增同名属性。例如:
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log('Hello, my name is'+ this.name);
};
const person1 = new Person('Alice');
person1.name = 'Charlie';
person1.sayHello = function() {
console.log('Hi, I am'+ this.name);
};
person1.sayHello(); // 输出:Hi, I am Charlie ,
const person2 = new Person('Bob');
person2.sayHello(); // 输出:Hello, my name is Bob
- 实例对象无法给原型新增属性,尝试添加只会在实例对象上创建新属性。例如:
function Person(name) {
this.name = name;
}
const person1 = new Person('Alice');
const person2 = new Person('Bob');
person1.age = 30;
console.log(person1.age); // 输出:30
console.log(person2.age);//underfined
- 实例对象无法删除原型上的属性,删除操作只影响实例对象自身属性。例如:
function Person(name) {
this.name = name;
}
Person.prototype.age = 30;
const person1 = new Person('Alice');
const person2 = new Person('Bob');
delete person1.name;
console.log(person1.name); // underfined
console.log(person2.name);//Bob
对象原型
每个对象都有内部属性__proto__,指向该对象的原型,原型可为另一个对象或null。例如:
let person1 = new Person('Alice');
console.log(person1.__proto__ ); // Person.prototype
函数原型与对象原型的关系
函数原型和对象原型关系密切,创建新对象时,对象的__proto__属性会被自动设置为构造函数的prototype属性值。例如:
let person2 = new Person('Bob');
console.log(person2.__proto__ === Person.prototype); // true
constructor属性
在原型链中,constructor属性指向创建对象的构造函数引用。每个对象都有此属性。例如:
function Person(name) {
this.name = name;
}
const person1 = new Person('Alice');
console.log(person1.constructor === Person); // 输出: true
原型链
原型链的定义与作用
原型链是JavaScript实现
- 继承
- 属性查找
的机制。每个对象有原型对象,通过它实现属性和方法继承。访问对象属性或方法时,若对象本身没有,引擎会沿原型链向上查找,直到找到或到达原型链顶端(Object.prototype)。
原型链的继承关系
对象间的继承关系通过原型链实现。例如:
function Animal(name) {
this.name = name;
}
Animal.prototype.sleep = function() {
console.log(this.name + " is sleeping");
};
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log("Woof! Woof!");
};
var dog1 = new Dog("Buddy", "Labrador");
dog1.sleep(); // 输出: Buddy is sleeping
dog1.bark(); // 输出: Woof! Woof!
此例中,Dog类通过原型链继承了Animal类的属性和方法。
我们先看看这这几个 api
Call 方法
在 JavaScript 中,call是函数的一个原型方法,它的主要作用是在特定的上下文中调用一个函数,并可以显式地指定函数内部this关键字所指向的对象。
在给定的代码中:
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
这里的Animal.call(this, name)语句的目的是在创建Dog实例时,让Dog构造函数内部的this能够正确地继承Animal构造函数中的属性。
具体来说:
this参数:在call方法中,第一个参数指定了函数内部this关键字要指向的对象。在Dog构造函数里,通过传入this,意味着在调用Animal构造函数时,Animal函数内部的this就会指向正在创建的Dog实例对象。这样就能确保Animal构造函数中对this.name的赋值操作,实际上是给当前正在创建的Dog实例设置了name属性。- 后续参数:除了第一个参数用于指定
this指向外,call方法还可以接收其他参数,这些参数会作为被调用函数(这里是Animal函数)的实际参数传递进去。在上述代码中,name作为参数传递给了Animal构造函数,以便在Animal构造函数内部使用该参数来初始化name属性。
Object.create方法
Object.create()方法用于创建一个新对象,新对象会以传入的对象作为其原型对象。
在代码中:
Dog.prototype = Object.create(Animal.prototype);
这里的作用是让Dog的原型对象(Dog.prototype)继承自Animal的原型对象(Animal.prototype)。这意味着Dog的所有实例都能够继承Animal.prototype上定义的方法,比如sleep方法。通过这种方式,实现了基于原型链的继承机制,使得Dog实例不仅可以访问自身构造函数中定义的属性和方法,还能访问到从Animal原型继承而来的方法。
constructor属性
在 JavaScript 中,每个函数都有一个constructor属性,它指向创建该函数对象的构造函数。
在代码中:
Dog.prototype.constructor = Dog;
当我们使用Object.create(Animal.prototype)来设置Dog.prototype时,新创建的Dog.prototype对象的constructor属性会指向Animal(因为它继承自Animal.prototype,而Animal.prototype.constructor默认指向Animal)。但我们希望Dog.prototype的constructor属性正确地指向Dog构造函数,所以需要显式地将其设置回来。这样做的好处是保持了对象的构造函数引用的正确性,使得在一些需要通过constructor属性来判断对象类型或者进行相关操作的场景下能够正常工作。
prototype属性
- 在 JavaScript 中,函数都有一个
prototype属性,它是一个对象,用于定义通过该函数创建的实例所共享的属性和方法。当使用new关键字创建一个对象实例时,该实例会自动关联到其构造函数的prototype对象上,通过原型链机制来实现对prototype上属性和方法的继承。 - 对于
Animal函数,Animal.prototype.sleep = function() {...}就是在Animal的原型对象上定义了一个sleep方法,这样所有通过Animal构造函数创建的实例(以及继承自Animal的其他构造函数的实例,如Dog的实例)都能够访问到这个sleep方法。 - 同样,对于
Dog函数,我们通过设置Dog.prototype来定义Dog实例所共享的额外属性和方法,比如bark方法就是定义在Dog的原型对象上,只有Dog的实例能够访问到它。
通过上述这些方法和对属性的操作,实现了Dog类对Animal类的继承关系,使得Dog实例既具有自身特有的属性(如breed)和方法(如bark),又能继承Animal类的属性(如name)和方法(如sleep)。
属性查找过程
访问对象属性或方法时,JavaScript引擎沿原型链向上查找,找不到则返回undefined。
关于对象原型的特殊情况
- 除Object本身外的对象原型
-
- Object的实例(通过new Object()或对象字面量创建),原型是Object.prototype。例如:
const obj = {};
console.log(obj.__proto__ === Object.prototype); // 输出: true
- 函数的实例(通过new Function()创建),原型是Function.prototype。例如:
function myFunction() {}
console.log(myFunction.__proto__ === Function.prototype); // 输出: true
- 数组的实例(通过new Array()创建),原型是Array.prototype。例如:
const arr = [];
console.log(arr.__proto__ === Array.prototype); // 输出: true
- 其他内置对象(如Date、RegExp、Error等)的实例,原型分别是对应的Date.prototype、RegExp.prototype、Error.prototype等。例如:
const date = new Date();
console.log(date.__proto__ === Date.prototype); // 输出: true
- Object.prototype是原型链顶端
Object.prototype的原型是null。例如:
console.log(Object.prototype.__proto__); // 输出: null
- 创建无原型的对象
使用Object.create(null)可创建无原型的对象。例如:
const noProtoObj = Object.create(null);
console.log(noProtoObj.__proto__); // 输出: undefined
通过对原型与原型链的深入理解,能更好地运用JavaScript进行面向对象编程,提升代码的效率和可维护性。