魔幻代码之舞:探秘JavaScript原型的奇妙世界

1.前言

我们要探索 JavaScript 中的一个超酷的技术------原型。原型是编程中的一种强大工具,它能让你的代码更加高效和精巧。

想象一下,原型就像是编程的蓝图,可以帮助我们构建出更强大的对象。通过了解原型,我们能够更深入地理解 JavaScript 中的对象结构,让我们的代码变得更为优雅和可维护。

在这个学习之旅中,我们将深入了解原型的概念,探索如何利用原型链创建更灵活的代码结构。这并不是什么神秘的技巧,而是实实在在的编程智慧,能够让你的代码更加强大和高效。

2.原型的概念

2.1 函数的原型(显式原型、原型对象)

在 JavaScript 中,每个函数都有一个特殊的属性,称为 prototype。这个 prototype 是一个对象,它包含了一个指向原型对象的引用。

原型是函数天生就具有的属性。它定义了构造函数制造出的对象的公共祖先。通过该构造函数产生的对象,可以隐式继承到原型上的属性和方法。

js 复制代码
function myFunction() {
  // 函数体
}

console.log(myFunction.prototype); // 输出: {}

这里,myFunction.prototype 就是 myFunction 函数的显示原型。

显示原型的作用

函数的原型对象是一个普通的对象,但它对于实现继承和共享属性和方法至关重要。让我们看看它的作用:

1.继承

当你创建一个对象实例时,它会继承其构造函数的原型。这样,通过原型链,实例可以访问原型对象中定义的属性和方法。

js 复制代码
function Person(name) {
  this.name = name
}

Person.prototype.sayHello = function() {
  console.log(`Hello, I'm ${this.name}`)
}

let person1 = new Person('dante')
person1.sayHello(); // 输出:Hello, I'm dante

2 共享属性和方法

所有通过相同构造函数创建的实例都共享同一个原型对象。这意味着,如果你修改了原型对象,所有实例都会受到影响。

js 复制代码
Person.prototype.age = 18;

console.log(person1.age); // 输出: 18

let person2 = new Person('dante');
console.log(person2.age); // 输出: 18

prototype 与 proto

要注意 prototype 是函数特有的属性,而 __proto__ 则是实例对象特有的属性。__proto__ 指向其构造函数的原型对象。

js 复制代码
console.log(person1.__proto__ === Person.prototype); // 输出: true

虽然 __proto__ 是一种访问原型链的方式,但并不推荐使用,因为它不是标准的 JavaScript API。推荐使用 Object.getPrototypeOf() 方法来获取对象的原型。

js 复制代码
console.log(Object.getPrototypeOf(person1) === Person.prototype); // 输出: true

构造函数的 prototype

构造函数本身也有一个 prototype 属性,它与实例的 __proto__ 指向同一个对象。这个构造函数的 prototype 用于定义实例对象的原型。

js 复制代码
console.log(Person.prototype === myFunction.prototype); // 输出: true

函数原型在 JavaScript 中扮演着重要的角色,它通过原型链实现了继承和共享属性和方法的机制。深入理解函数原型有助于更好地利用 JavaScript 中的面向对象编程特性。

2.2对象的原型(隐式原型)

在 JavaScript 中,每个对象都有一个原型。原型可以看作是对象的父对象,它包含了对象共享的属性和方法。当我们访问一个对象的属性或方法时,JavaScript 引擎会首先在对象本身查找,如果找不到,就会去原型中查找。

现在我们通过具体的代码来了解什么是对象原型,使用构造函数是一种创建对象的常见方式。构造函数可以看作是一种特殊的函数,通过 new 关键字调用时,它会创建一个新的对象,并将该对象的原型指向构造函数的原型。

js 复制代码
Person.prototype.say = function (){
  return this.name + '今年' + this.age + '岁了'
}
function Person(){
  this.name = 'dante'
  this.age = 18
}
const p = new Person()
console.log(p) // Person {name: 'dante', age: 18}
console.log(p.say()) //dante今年18岁了

从上面的代码,我们可以看到,Person构造函数中并没有say()这样一个方法,但是为什么实例p可以访问say这个方法呢?这就是在创建实例p的时候,p继承了构造函数Person的显式原型中的方法,在js引擎查找这个say()方法时,首先在实例p中查找say,没有就会在p的原型上查找,刚好,实例p的原型继承了构造函数Person中的属性和方法。我总结了以下两点:

  1. 当访问对象属性时,先找对象显式具有的属性,没找到再去找对象的隐式原型。

  2. 实例对象的隐式原型 === 构造函数的显式原型

2.3原型链

从上面我们了解到了函数的显式原型和对象的隐式原型吗,明白了这两个概念,原型链就很好解决了,那什么是原型链呢?

当一个对象中的方法被调用执行时,引擎查找的时候首先会查找对象的显式具有的属性,没有,就会在对象原型中去查找,若还没有找到,就会继续向构造该对象的构造函数的显式原型中查找,还未找到,继续向上一层查找,一直到null。

顺着对象的隐式原型不断地向上查找上一级的隐式原型,直到找到目标或者一直到null,这种查找关系叫做原型链

3.所有的对象都有隐式原型吗?

了解了对象原型,那我们来思考这个问题,所有的对象都有隐式原型吗?文章读到这里,好像这句话说的非常的正确。 但是有一个特例,通过Object.create(null)创建的对象是没有隐式原型的。

js 复制代码
 //  Object.create()
  let obj = {
    a:1
  }
  let obj2 = Object.create(null)

  console.log(obj)
  console.log(obj2)

上面是在浏览器上打印obj的结果,是有Prototype的,而下方是打印obj2的结果,没有Prototype

Object.create(null) 创建的对象之所以为空(即没有原型链上的属性和方法),是因为它的原型被显式地设置为 null

在 JavaScript 中,Object.create() 方法接受一个参数,用于指定新创建对象的原型。当你传入 null 作为参数时,创建的对象将没有原型链,也就是说它不继承任何属性或方法。

示例:

js 复制代码
const emptyObject = Object.create(null);

console.log(emptyObject.toString); // 输出: undefined
console.log(emptyObject instanceof Object); // 输出: false

在这个例子中,emptyObject 被创建为一个空对象,它不具备任何继承的属性或方法。toString 属性为 undefined,而且它不是 Object 的实例,因为它没有原型链。

通常,这种操作被用于创建一个"纯净"的、不继承任何属性或方法的对象,以防止不必要的属性干扰。这样的对象可以用于一些特定的用途,比如作为映射(Map)的初始值,因为它不会受到原型链上其他属性的影响。

4. 总结

函数原型和对象原型共同构成了 JavaScript 中强大的原型链系统。它们通过原型链的连接关系,实现了继承、属性共享,为 JavaScript 提供了灵活且强大的面向对象编程能力。深入理解这两个概念,可以帮助你更好地设计和组织代码,使其更具可维护性和可扩展性。

相关推荐
小小小小宇7 小时前
前端并发控制管理
前端
小小小小宇7 小时前
前端SSE笔记
前端
小小小小宇7 小时前
前端 WebSocket 笔记
前端
小小小小宇8 小时前
前端visibilitychange事件
前端
小小小小宇9 小时前
前端Loader笔记
前端
烛阴10 小时前
从0到1掌握盒子模型:精准控制网页布局的秘诀
前端·javascript·css
前端工作日常13 小时前
我理解的`npm pack` 和 `npm install <local-path>`
前端
李剑一13 小时前
说个多年老前端都不知道的标签正确玩法——q标签
前端
嘉小华13 小时前
大白话讲解 Android屏幕适配相关概念(dp、px 和 dpi)
前端
姑苏洛言13 小时前
在开发跑腿小程序集成地图时,遇到的坑,MapContext.includePoints(Object object)接口无效在组件中使用无效?
前端