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

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

  • 白宝书系列
    • 启示录 - 攻城狮的自我修养
    • Python全栈白宝书
    • ChatGPT实践指南白宝书
    • 产品思维训练白宝书
    • 全域运营实战白宝书
    • 大前端全栈架构白宝书


文章目录

  • ⭐ 原型和原型链
  • ⭐ hasOwnProperty方法和in运算符
  • ⭐ 在prototype上添加方法

⭐ 原型和原型链

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

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

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

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

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

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

〖大前端 - 基础入门三大核心之JS篇(54)〗- 原型和原型链_第1张图片

下面我们来敲一个例子证明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属性指向原函数

〖大前端 - 基础入门三大核心之JS篇(54)〗- 原型和原型链_第2张图片

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

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

〖大前端 - 基础入门三大核心之JS篇(54)〗- 原型和原型链_第3张图片

乍一看这个图并不能理解其中的含义,只是知道构造函数、构造函数的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

〖大前端 - 基础入门三大核心之JS篇(54)〗- 原型和原型链_第4张图片

上面的代码只是证明了构造函数、构造函数的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);

〖大前端 - 基础入门三大核心之JS篇(54)〗- 原型和原型链_第5张图片

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

下面把上面的例子延伸一下,我们在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);  // 美国

〖大前端 - 基础入门三大核心之JS篇(54)〗- 原型和原型链_第6张图片

可以看到,如果这个实例化对象本身就有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

〖大前端 - 基础入门三大核心之JS篇(54)〗- 原型和原型链_第7张图片

⭐ 在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);

〖大前端 - 基础入门三大核心之JS篇(54)〗- 原型和原型链_第8张图片

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

如果把方法添加到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);

〖大前端 - 基础入门三大核心之JS篇(54)〗- 原型和原型链_第9张图片

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

你可能感兴趣的:(大前端全栈架构白宝书,#,①,-,基础入门三大核心篇,前端,javascript,开发语言)