〖大前端 - 基础入门三大核心之JS篇(54)〗- 原型和原型链

发布时间:2023年12月18日
  • 说明:该文属于 大前端全栈架构白宝书专栏目前阶段免费如需要项目实战或者是体系化资源,文末名片加V!
  • 作者:哈哥撩编程,十余年工作经验, 从事过全栈研发、产品经理等工作,目前在公司担任研发部门CTO。
  • 荣誉:2022年度博客之星Top4、2023年度超级个体得主、谷歌与亚马逊开发者大会特约speaker全栈领域优质创作者



? 原型和原型链

原型和原型链是实现JavaScript继承的主要机制,许多面向对象的特性,如类的方法继承等,都是基于原型和原型链的。

在JavaScript中,每个对象都有一个特殊的内部属性[[Prototype]],它是对象的原型。原型也就是其他对象的引用,每个对象都从原型“继承”属性。

原型链是通过同样的[[Prototype]]属性链接起来的,形成了一条链状结构。“原型链”就是对象通过[[Prototype]]属性引用其他对象,然后通过这些对象再引用其他对象,如此形成的一条链状结构。

当我们访问对象的某个属性时,JavaScript会首先在该对象自身的属性中查找,如果没有找到,那么JavaScript会在该对象的[[Prototype]]原型对象中查找,如果还是没有找到,那么JavaScript就会继续在原型的原型中查找,即在原型链上向上查找,直到找到属性或者查找到null(原型链的末端)为止。如果在原型链的最顶端也没有找到这个属性,那么就会返回undefined。这就是原型链在JavaScript属性查找中的作用。

prototype,原型。任何函数都有prototype属性。

prototype属性值是个对象,它默认拥有constructor属性指回函数

image-20230619151013992

下面我们来敲一个例子证明prototype这个属性的存在,再看一下它的具体功能:

function sum(a, b) {
    return a + b;
}

console.log(sum.prototype);
console.log(typeof sum.prototype);  // object
console.log(sum.prototype.constructor);
console.log(sum.prototype.constructor === sum);   // true;证明protoype的constructor属性指向原函数

image-20230712151012627

  • 对于普通函数来说,prototype属性没有任何用处,而构造函数的prototype属性非常有用
  • 构造函数的prototype属性是它的实例的原型

下面我们看下一张图来解释什么叫做“构造函数的prototype属性是它的实例的原型”

image-20230712152625260

乍一看这个图并不能理解其中的含义,只是知道构造函数、构造函数的prototype属性和构造函数的实例三者之间存在一种“三角”关系。我们来敲一下代码,从代码维度来理解这三者的关系:

function People(name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
}
// 实例化
var xiaoming = new People('小明', 12, '男');
// 测试三角关系是否存在
console.log(xiaoming.__proto__ === People.prototype);  // true

image-20230712162818967

上面的代码只是证明了构造函数、构造函数的prototype属性和构造函数的实例存在的这种三角关系,但这个关系到底在编写代码中有什么作用呢?答案是,我们可以利用这个三角关系实现“原型链查找”。

JavaScript规定:实例可以打点访问它的原型的属性和方法,这被成为“原型链查找”

示例代码:

function People(name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
}
// 往原型上添加nationality属性
People.prototype.nationality = '中国';
var xiaoming = new People('小明', 12, '男');
console.log(xiaoming.nationality);
console.log(xiaoming);

image-20230713101527969

当实例化对象打点调用某个属性时,系统发现它本身没有这个属性,此时并不会报错,而是回去查找它的“原型”上有没有这个属性,如果有,则会把这个属性输出。

下面把上面的例子延伸一下,我们在People这个构造函数再实例化一个对象,这个对象本身就有nationality属性,那我们用这个对象打点调用nationality属性时,会返回什么呢?示例代码:

function People(name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
}
// 往原型上添加nationality属性
People.prototype.nationality = '中国';

// 实例化
var xiaoming = new People('小明', 12, '男');
var tom = new People('Tom', 11, '男');
tom.nationality = '美国';

console.log(xiaoming.nationality);
console.log(xiaoming);
console.log(tom.nationality);  // 美国

image-20230713102722477

可以看到,如果这个实例化对象本身就有nationality属性时,打点会直接访问到这个属性,而不是去访问原型上的nationality属性,这个就是原型链的遮蔽效应

? hasOwnProperty方法和in运算符

hasOwnProperty方法可以检查对象是否真正“自己拥有”某属性或者方法

和hasOwnProperty方法有些类似的是in运算符:

in运算符只能检查某个属性或方法是否可以被对象访问,不能检查是否是自己的属性或方法

示例代码:

function People(name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
}
// 往原型上添加nationality属性
People.prototype.nationality = '中国';

// 实例化
var xiaoming = new People('小明', 12, '男');

console.log(xiaoming.hasOwnProperty('name'));   // true
console.log(xiaoming.hasOwnProperty('age'));   // true
console.log(xiaoming.hasOwnProperty('sex'));   // true
console.log(xiaoming.hasOwnProperty('nationality'));   // false

console.log('name' in xiaoming);   // true
console.log('age' in xiaoming);   // true
console.log('sex' in xiaoming);   // true
console.log('nationality' in xiaoming);   // true

image-20230713104250772

? 在prototype上添加方法

之前在实例化对象时,是直接往实例化对象上添加方法,实例化多少个对象,就会在内存中创建多少个方法,这些方法虽然名字相同,但在内存中的地址是不同的,敲一段代码来验证一下:

function People(name) {
    this.name = name;
    this.sayHello = function () {

    };
}
// 实例化
var xiaoming = new People('xiaoming');
var xiaobai = new People('xiaobai');
var xiaohei = new People('xiaohei');

console.log(xiaoming.sayHello === xiaobai.sayHello);

image-20230714103610250

把方法直接添加到实例身上的缺点:每个实例和每个实例的方法函数都是内存中不同的函数,造成了内存的浪费

如果把方法添加到prototype身上,就可以避免这个问题,将上面的例子改造后代码如下:

function People(name) {
    this.name = name;

}
//把方法写到原型上
People.prototype.sayHello = function () {
    console.log('您好,我是' + this.name);
}
// 实例化
var xiaoming = new People('xiaoming');
var xiaobai = new People('xiaobai');
var xiaohei = new People('xiaohei');

xiaoming.sayHello();
xiaobai.sayHello();
xiaohei.sayHello();
console.log(xiaoming.sayHello === xiaobai.sayHello);

image-20230714111236033

把方法写在原型上是聪明有效的避免内存重复占用的方式,我们一定要习惯使用这种写法。

文章来源:https://blog.csdn.net/weixin_42250835/article/details/135006758
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。