前面第一篇博文我们了解了js对象的基础,现在我们来学习js的难点
目录
一、原型与原型链
1. 什么是原型
每个 JavaScript 对象都有一个隐藏的 [[Prototype]]
属性(通常通过 __proto__
访问),它指向另一个对象。这个对象就是 原型,对象通过原型继承了属性和方法。所有 JavaScript 对象都继承自 Object
对象,而 Object.prototype
是所有对象的顶级原型。每当我们访问一个对象的属性时,如果这个对象本身没有该属性,JavaScript 会通过原型链查找该属性。 我们如果定义了一个Object的对象,那我们是不是就能使用很多我们未定义过的函数,为什么我们能使用,就是因为我们定义的这个对象都继承了它原型上的函数。
在解释原型的时候我想先解释一下什么叫构造函数
什么是构造函数?
构造函数本质上是一个普通的函数,只是它有一个特殊的用法:当你使用 new
操作符来调用它时,它会创建一个新对象并返回这个对象。在构造函数内部,你通常会用 this
来为对象添加属性和方法。
构造函数的特点:
-
构造函数通常是一个大写字母开头的函数名,以便与普通函数区分。
-
构造函数内部使用
this
来为新创建的对象添加属性和方法。 -
构造函数在使用
new
关键字时,会自动返回一个新的对象。
// 定义一个构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
// 创建一个 Person 的实例
let person1 = new Person("Alice", 30);
console.log(person1.name); // 输出: Alice
console.log(person1.age); // 输出: 30
我们现在了解构造函数以后,我们就会想
为什么需要原型?
1. 实现继承(Inheritance)
JavaScript 中的继承是通过原型链实现的。如果没有原型,JavaScript 就无法在不同对象之间共享方法和属性,也无法实现继承的概念。原型让一个对象能够继承另一个对象的属性和方法,这对于代码的复用和构建类结构非常重要。
当你使用构造函数创建一个对象的时候,该对象的原型是通过构造函数的 prototype
属性来设定的。所有构造函数都有一个 prototype
属性,该属性指向的对象是通过该构造函数创建的实例的原型。
function Animal(name) {
this.name = name;
}
Animal.prototype.sayHello = function() {
console.log("Hello from " + this.name);
};
function Dog(name) {
Animal.call(this, name); // 继承 Animal 的属性
}
// Dog 继承 Animal 的方法
Dog.prototype = Object.create(Animal.prototype); // 设置 Dog.prototype 为 Animal.prototype 的实例
let dog = new Dog("Buddy");
dog.sayHello(); // 输出: Hello from Buddy
2. 共享方法和属性
原型的最大优势之一是可以让所有通过某个构造函数创建的实例共享方法和属性。这意味着不需要在每个实例上都复制相同的代码,从而节省内存和提高效率。
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log("Hello, my name is " + this.name);
};
let person1 = new Person("Alice");
let person2 = new Person("Bob");
person1.sayHello(); // 输出: Hello, my name is Alice
person2.sayHello(); // 输出: Hello, my name is Bob
3. 减少内存消耗
由于通过构造函数创建的所有实例都共享原型上的方法,这意味着方法只在内存中存储一次,而不是为每个实例复制一份。这有效减少了内存消耗,尤其是在大量创建对象实例时。
4. 动态修改原型
原型可以动态修改,允许我们在不修改构造函数的情况下,为构造函数的所有实例添加新的方法或修改现有方法。这为代码的灵活性和扩展性提供了方便。
function Person(name) {
this.name = name;
}
let person1 = new Person("Alice");
// 动态为所有 Person 实例添加新方法
Person.prototype.sayGoodbye = function() {
console.log("Goodbye from " + this.name);
};
person1.sayGoodbye(); // 输出: Goodbye from Alice
在这个例子中,sayGoodbye
方法是在 person1
创建后添加到 Person.prototype
上的,所有 Person
的实例都能访问到这个方法。
5. 模拟类和对象的关系
JavaScript 是基于原型的语言,并不使用传统的类和继承模型(尽管 ES6 引入了 class
语法,但它仍然是基于原型的语法糖)。通过原型,我们可以模拟传统面向对象语言中的类和对象之间的关系。
使用原型链,JavaScript 允许一个对象通过原型继承另一个对象的方法和属性,像传统的面向对象编程中的类继承一样。
6. 多态性(Polymorphism)
由于 JavaScript 的原型链继承机制,我们可以通过修改对象原型上的方法来实现多态性。当子类的实例调用继承自父类的方法时,可以根据需求重写这些方法,甚至在运行时改变它们的行为。
2. 原型链
JavaScript 中的继承是通过原型链实现的。每个对象都有一个内部属性 [[Prototype]]
,指向其构造函数的原型对象。如果一个对象访问某个属性或方法,JavaScript 会首先检查对象本身是否有该属性,如果没有,则会沿着原型链向上查找,直到找到该属性或达到原型链的顶端(Object.prototype
)。
当访问一个对象的属性时,先在对象的本身找,找不到就去对象的原型上找,如果还是找不到,就去对象的原型(原型也是对象,也有它自己的原型)的原型上找,如此继续,直到找到为止,或者查找到最顶层的原型对象中也没有找到,就结束查找,返回undefined
。
这条由对象及其原型组成的链就叫做原型链。
现在我们已经初步理解了原型和原型链,到现在