js原型和原型链,到底咋回事?

js原型和原型链,到底咋回事?

js原型的原理

在 JavaScript 里,每个对象都有一个“隐藏的小伙伴”,这个“小伙伴”就是原型。可以把原型想象成一个模板或者一个仓库,对象能从它这个“小伙伴”那里借用一些属性和方法。当你访问一个对象的某个属性或方法时,JavaScript 会先在这个对象本身找,如果没找到,就会去它的原型里找。

代码示例
// 定义一个构造函数
function Person(name) {
    this.name = name;
}

// 给 Person 的原型添加一个方法
Person.prototype.sayHello = function() {
    console.log('你好,我是'+ this.name);
};

// 创建一个 Person 对象
let person1 = new Person('张三');

// 调用对象本身没有,但原型上有的方法
person1.sayHello(); 

在上面的代码中,Person 是一个构造函数,Person.prototype 就是通过 Person 构造函数创建出来的对象的原型。我们给 Person.prototype 添加了一个 sayHello 方法,那么通过 new Person() 创建的对象(如 person1)就可以调用这个方法,就好像对象从原型这个“仓库”里借到了 sayHello 方法。

js原型链的原理

原型链就像是一个长长的“接力棒链条”。每个对象都有自己的原型,而这个原型本身也是一个对象,它也有自己的原型,以此类推,一直到最顶层的原型(Object.prototype),这就形成了一个链条。当你访问一个对象的属性或方法时,JavaScript 会沿着这个链条一级一级地往上找,直到找到或者到链条的顶端(Object.prototype)还没找到就返回 undefined

代码示例
// 定义一个 Animal 构造函数
function Animal() {
    this.type = '动物';
}

// 给 Animal 的原型添加一个方法
Animal.prototype.getInfo = function() {
    console.log('这是一只'+ this.type);
};

// 定义一个 Dog 构造函数,继承自 Animal
function Dog(name) {
    this.name = name;
}

// 设置 Dog 的原型为 Animal 的一个实例
Dog.prototype = new Animal();

// 给 Dog 的原型添加一个方法
Dog.prototype.bark = function() {
    console.log(this.name + '汪汪叫');
};

// 创建一个 Dog 对象
let dog1 = new Dog('旺财');

// 调用 Dog 本身没有,但原型链上有的方法
dog1.getInfo(); 
dog1.bark(); 

在这个例子中,dog1Dog 的实例,Dog.prototypeAnimal 的实例,Animal.prototype 又有自己的原型,最终会指向 Object.prototype。当我们调用 dog1.getInfo() 时,dog1 本身没有 getInfo 方法,JavaScript 就会去 Dog.prototype(也就是 Animal 的实例)里找,找到了就执行;当调用 dog1.bark() 时,直接在 Dog.prototype 里找到了这个方法。这就是原型链在起作用,它让对象可以在不同层级的原型中查找属性和方法。

原型链的作用是什么?

1. 实现对象间的继承

在 JavaScript 里,继承就是一个对象能够使用另一个对象的属性和方法。原型链就像是一条“传承线”,让对象之间可以传承特性。就好比儿子可以继承爸爸的一些本事和财产。

代码示例
// 定义一个动物构造函数
function Animal() {
    this.eat = function() {
        console.log('动物会吃东西');
    };
}

// 定义一个狗构造函数
function Dog() {
    this.bark = function() {
        console.log('狗会汪汪叫');
    };
}

// 让 Dog 继承 Animal 的特性
Dog.prototype = new Animal();

// 创建一个狗的实例
let myDog = new Dog();

// 调用继承来的方法
myDog.eat(); 
// 调用自己的方法
myDog.bark(); 

在这个例子中,Dog 通过原型链继承了 Animaleat 方法。当我们创建 myDog 这个狗的实例后,它既可以调用自己的 bark 方法,也能调用从 Animal 那里继承来的 eat 方法。

2. 实现代码复用

代码复用就是避免重复写相同的代码。原型链可以让多个对象共享同一个原型上的属性和方法,就好像大家都去一个公共仓库里拿东西用,而不用每个人都自己准备一份。

代码示例
// 定义一个形状构造函数
function Shape() {
    this.draw = function() {
        console.log('绘制形状');
    };
}

// 定义圆形构造函数
function Circle() {}
// 让圆形继承形状的特性
Circle.prototype = new Shape();

// 定义方形构造函数
function Square() {}
// 让方形继承形状的特性
Square.prototype = new Shape();

// 创建圆形和方形的实例
let circle = new Circle();
let square = new Square();

// 圆形和方形都可以使用 draw 方法
circle.draw(); 
square.draw(); 

这里 CircleSquare 都继承了 Shape 原型上的 draw 方法,不用在 CircleSquare 里分别写 draw 方法的代码,实现了代码复用。

3. 提供默认值

原型链可以给对象提供默认的属性和方法。当对象本身没有这些属性和方法时,就会去原型链上找默认的。这就像你去超市买东西,如果自己没带购物袋,超市会给你提供一个默认的购物袋。

代码示例
// 定义一个汽车构造函数
function Car() {}

// 给汽车的原型设置默认属性
Car.prototype.color = '白色';
Car.prototype.wheels = 4;

// 创建一辆汽车实例
let myCar = new Car();

// 访问默认属性
console.log(myCar.color); 
console.log(myCar.wheels); 

myCar 这个实例本身没有定义 colorwheels 属性,但通过原型链可以使用 Car 原型上设置的默认值。

4. 动态修改对象行为

原型链具有动态特性,你可以在程序运行的时候修改原型上的属性和方法,这样所有继承自这个原型的对象都会受到影响。这就好比你修改了公共仓库里的东西,所有从这个仓库拿东西的人都会拿到修改后的东西。

代码示例
// 定义一个人构造函数
function Person(name) {
    this.name = name;
}

// 给人的原型添加一个方法
Person.prototype.sayHello = function() {
    console.log('你好,我是'+ this.name);
};

// 创建一个人的实例
let person = new Person('张三');

// 调用 sayHello 方法
person.sayHello(); 

// 在运行时修改原型上的方法
Person.prototype.sayHello = function() {
    console.log('哈喽呀,我是'+ this.name);
};

// 再次调用 sayHello 方法,行为已改变
person.sayHello(); 

我们先让 person 调用 sayHello 方法,然后在运行时修改了 Person 原型上的 sayHello 方法,再次调用时,person 执行的就是修改后的方法,体现了原型链动态修改对象行为的作用。

你可能感兴趣的:(大白话前端面试题,javascript,前端)