2025-01-17 日报 Day69

2025-01-17 日报 Day69

Yuyang 前端小白🥬

今日的鸡汤

能坚持别人无法坚持的,才会拥有别人无法拥有的,一点点改变,好过一成不变。

今日学习内容

1、JS 红皮书 P236-239 第八章:对象、类与面向对象编程

今日笔记

1、原型的动态性: 因为从原型上搜索值的过程是动态的,所以即使实例在修改原型之前已经存在,任何时候对原型对象所做的修改也会在实例上反映出来。下面是一个例子:

1
2
3
4
5
let friend = new Person(); 
Person.prototype.sayHi = function() {
console.log("hi");
};
friend.sayHi(); // "hi",没问题!

虽然随时能给原型添加属性和方法,并能够立即反映在所有对象实例上,但这跟重写整个原型是两回事。实例的[[Prototype]]指针是在调用构造函数时自动赋值的,这个指针即使把原型修改为不同的对象也不会变。重写整个原型会切断最初原型与构造函数的联系,但实例引用的仍然是最初的原型。记住,实例只有指向原型的指针,没有指向构造函数的指针。来看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
function Person() {} 
let friend = new Person();
Person.prototype = {
constructor: Person,
name: "Nicholas",
age: 29,
job: "Software Engineer",
sayName() {
console.log(this.name);
}
};
friend.sayName(); // 错误!

在这个例子中,Person 的新实例是在重写原型对象之前创建的。在调用 friend.sayName()的时候,会导致错误。这是因为 firend 指向的原型还是最初的原型,而这个原型上并没有 sayName 属性。
重写构造函数上的原型之后再创建的实例才会引用新的原型。而在此之前创建的实例仍然会引用最初的原型。
2、原生对象原型: 原型模式之所以重要,不仅体现在自定义类型上,而且还因为它也是实现所有原生引用类型的模式。所有原生引用类型的构造函数(包括 Object、Array、String 等)都在原型上定义了实例方法。比如,数组实例的 sort()方法就是 Array.prototype 上定义的,而字符串包装对象的 substring()方法也是在 String.prototype 上定义的,如下所示:

1
2
console.log(typeof Array.prototype.sort); // "function" 
console.log(typeof String.prototype.substring); // "function"

通过原生对象的原型可以取得所有默认方法的引用,也可以给原生类型的实例定义新的方法。可以像修改自定义对象原型一样修改原生对象原型,因此随时可以添加方法。比如,下面的代码就给 String原始值包装类型的实例添加了一个 startsWith()方法:

1
2
3
4
5
String.prototype.startsWith = function (text) { 
return this.indexOf(text) === 0;
};
let msg = "Hello world!";
console.log(msg.startsWith("Hello")); // true

3、原型问题: 原型模式弱化了向构造函数传递初始化参数的能力,会导致所有实例默认都取得相同的属性值。虽然这会带来不便,但还不是原型的最大问题。原型的最主要问题源自它的共享特性。
真正的问题来自包含引用值的属性。来看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Person() {} 
Person.prototype = {
constructor: Person,
name: "Nicholas",
age: 29,
job: "Software Engineer",
friends: ["Shelby", "Court"],
sayName() {
console.log(this.name);
}
};
let person1 = new Person();
let person2 = new Person();
person1.friends.push("Van");
console.log(person1.friends); // "Shelby,Court,Van"
console.log(person2.friends); // "Shelby,Court,Van"
console.log(person1.friends === person2.friends); // true

4、继承: 很多面向对象语言都支持两种继承:接口继承和实现继承。前者只继承方法签名,后者继承实际的方法。接口继承在 ECMAScript 中是不可能的,因为函数没有签名。实现继承是 ECMAScript 唯一支持的继承方式,而这主要是通过原型链实现的。

  • 原型链: 通过原型继承多个引用类型的属性和方法。重温一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型有一个属性指回构造函数,而实例有一个内部指针指向原型。如果原型是另一个类型的实例呢?那就意味着这个原型本身有一个内部指针指向另一个原型,相应地另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链。这就是原型链的基本构想。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function SuperType() { 
    this.property = true;
    }
    SuperType.prototype.getSuperValue = function() {
    return this.property;
    };
    function SubType() {
    this.subproperty = false;
    }
    // 继承 SuperType
    SubType.prototype = new SuperType();
    SubType.prototype.getSubValue = function () {
    return this.subproperty;
    };
    let instance = new SubType();
    console.log(instance.getSuperValue()); // true
此页目录
2025-01-17 日报 Day69