JavaScript高级-原型和原型链

前言:

       在深入探索JavaScript的广阔领域时,原型(Prototype)和原型链(Prototype Chain)无疑是两个核心概念,它们不仅是理解JavaScript对象模型的关键,也是实现高效、可扩展代码的基础。作为JavaScript高级特性的重要组成部分,原型和原型链不仅揭示了JavaScript对象之间复杂而优雅的关系网,还为我们提供了一种强大而灵活的继承机制。

       在JavaScript中,万物皆对象,而对象之间通过原型链相互连接,形成了一个错综复杂的网络。这个网络不仅允许对象之间共享方法和属性,还促进了代码的复用和模块化。掌握原型和原型链,意味着你能够更深入地理解JavaScript的底层机制,从而编写出更加高效、易于维护的代码。

  本文将带你走进JavaScript原型和原型链的世界,从基础概念讲起,逐步深入其内部工作原理。我们将探讨如何利用原型来定义对象的行为,如何通过原型链实现继承,以及如何在实际项目中灵活运用这些高级特性。无论你是JavaScript的初学者还是有一定经验的开发者,相信本文都能为你提供宝贵的见解和实用的技巧。

原型(Prototype)

        在JavaScript中,每个函数都有一个特殊的属性叫做prototype。这个属性是一个对象,当函数被用作构造函数来创建实例时,这个prototype对象将被用作新创建对象的原型。原型对象默认包含一个constructor属性,它指向与之关联的构造函数。

     function f(){}
      console.log(typeof f.prototype) //object

        上述代码中可以看到函数f是具有prototype,通过typeof可以看到它的基本数据类型为object。

 function Person(name) {
        this.name = name;
      }
      Person.prototype.country = "China";
      const userName1 = new Person('张三');
      console.log(userName1.name); //张三
      console.log(userName1.country); //China

      const userName2 = new Person('李四');
      console.log(userName2.name); //李四
      console.log(userName2.country); //China
      console.log(Person.prototype); //{country: 'China'}

        在上述代码中我们首先定义了一个名为Person的构造函数,它接受一个参数name,并将该参数的值赋给新创建对象的name属性:

function Person(name) {  
    this.name = name;  
}

        接下来,我们向Person函数的原型对象,也就是Person.prototype添加了一个名为country的属性,并将其值设置为"China":

Person.prototype.country = "China";

        然后,我们创建了两个Person的实例,分别命名为userName1userName2,并为它们各自传递了不同的name参数:

const userName1 = new Person('张三');  
const userName2 = new Person('李四');

我们在分别去访问userName1userName2的name和country属性,并通过console.log()打印在浏览器上:

        userName1的name属性的值为'张三',这是通过构造函数直接设置的,而country属性的值为'China',因此可以看出userName1是可以访问到Person函数的原型上,并继承该属性值。

  userName2的情况与userName1相同,name属性的值为'李四',而country属性的值同样为'China',这意味着所有通过new Person()创建的实例都将继承这个country属性。

        最后,我们通过打印Person.prototype对象来验证原型链上的属性。如我们所见,输出为{country: 'China'},这确认了country属性确实被添加到了Person的原型对象上。

其中new这个关键字做操作:

        1.它会生成一个新的空对象。

        2.会将构造函数中的this指向生成的新对象。

        3.会执行函数中的代码。

        4.返回这个新对象

其中原型对象上面的所有属性和方法都可以被实例对象共享。

原型(prototype)机制的一些优点和缺点:

优点:

        1,内存效率:由于多个实例可以共享同一个原型上的方法和属性,这可以显著减少内存占用。相比于传统的基于类的继承,每个实例不需要自己单独存储一份方法副本。

        2,灵活性:原型链允许在运行时动态地添加或修改方法和属性。这意味着你可以根据需要扩展对象的功能,而无需修改原始构造函数。

        3,原型继承:原型链提供了一种自然的继承机制,允许对象从其他对象继承属性和方法。这使得实现复杂的继承层次结构成为可能。

缺点:

        1,属性查找效率低:当访问一个对象的属性或方法时,JavaScript引擎需要沿着原型链逐级查找,直到找到为止或达到原型链的末端。这可能导致比直接访问对象自身属性更高的时间复杂度。

        2,引用属性共享问题:如果原型上有一个引用类型的属性(如数组或对象),那么该属性将被所有实例共享。这可能会导致意外的行为,尤其是当某个实例修改了该属性时。

        3,继承的复杂性:虽然原型链提供了一种继承机制,但它也增加了代码的复杂性。特别是当涉及多层继承时,需要仔细管理原型链,以避免意外的行为或性能问题。

        4,缺乏封装性:与基于类的继承相比,原型链在封装方面较弱。因为所有实例都可以直接访问和修改原型上的属性和方法,这可能会破坏对象的封装性。

        5,难以调试:由于原型链的复杂性和动态性,当出现问题时可能难以追踪和调试。特别是在大型应用程序中,原型链可能会变得非常长且难以管理。

尽管存在这些缺点,但原型机制仍然是JavaScript中一个非常重要的特性。通过深入理解其工作原理和优缺点,开发者可以更好地利用这一机制来构建高效、可扩展和可维护的JavaScript应用程序。

constructor 属性

        在JavaScript中,每个函数都有一个特殊的属性叫做prototype。这个prototype对象是一个普通的对象,它包含一个constructor属性,该属性指向函数本身。这个机制是JavaScript原型链(prototype chain)和继承系统的基础。

        当创建一个构造函数(即一个用于创建新对象的函数)时,JavaScript会自动为这个构造函数的prototype对象添加一个constructor属性,这个属性指回构造函数本身。这允许对象通过原型链访问到它们的构造函数。

function Person(){};
var p1 = new Person();
 console.log(Person.prototype.constructor === Person); //true

通过上述例子的打印结果可以看出,Person.prototype.constructor 指向了 Person 函数

console.log(p1.constructor === Person);

Person 的实例对象的constructor属性指向了Person 

constructor属性的作用是,可以得知某个实例对象,到底是哪一个构造函数产生的。

__proto__属性

        在JavaScript中,__proto__ 属性是一个非标准的、但广泛支持的方式来访问一个对象的原型(prototype)。然而,值得注意的是,尽管它在许多JavaScript环境中都可用,但__proto__ 并不是一个官方属性,也不是ECMAScript标准的一部分。

        尽管如此,__proto__ 属性在实践中经常被用来查看或修改对象的原型链。它指向了创建该对象的构造函数的 prototype 属性。

function Person(){};
var p1 = new Person();
console.log(p1.__proto__ === Person.prototype); //true

上述代码中Person的实例对象p1的.__proto__属性指向了Person.prototype原型对象

原型也是一个对象,既然是对象就会有 __proto__ 属性,该属性指向原型对象的原型。

原型链

        原型链是JavaScript中对象继承的一种机制。当一个对象需要访问某个属性或方法时,如果这个对象本身没有这个属性或方法,JavaScript引擎就会去它的原型对象中寻找。这个过程会一直持续进行,直到找到该属性或方法,或者达到原型链的顶端(通常是Object.prototype,它的原型是null,表示原型链的结束)

绘制函数和对象完整的原型链

1,Object 内置对象
        var o = new Object()
        console.log(o.__proto__ === Object.prototype); //true
        console.log(Object === Object.prototype.constructor); //true
        console.log(Object.prototype === Object.prototype); //true
        console.log(o.constructor === Object); //true
        console.log(Object.prototype.__proto__); //null

最后可以看出Object.prototype原型对象它的原型指向了null,表示原型链的结束。

2,Person 自定义对象
        function Person(){}
        var p1 = new Person();
        console.log(p1.__proto__ === Person.prototype); //true
        console.log(Person.prototype.constructor === Person); //true
        console.log(Person.prototype === Person.prototype); //true
        console.log(p1.constructor === Person); //true

3,Function 特殊对象
 var f = new Function()

在JavaScript中,Function 是一个特殊的内置对象,它用于创建新的函数。

        console.dir(Function.prototype) //ƒ anonymous()
        console.dir(Function.__proto__) //ƒ anonymous()
        console.log(Function.prototype === Function.__proto__); //true

上述代码可以看出Function特殊对象,Function对象它可以通过prototype属性指向它的它的原型,也可以通过__proto__属性指向原型,并且他们的返回值全等,这看起来可能有些反直觉,但这是因为 Function 本身也是一个函数对象(是的,函数也是对象)。因此,Function 函数对象也有一个 [[Prototype]] 指向 Function.prototype

console.log(Function.prototype.constructor === Function); //true

这是正确的。在JavaScript中,每个原型对象都有一个constructor属性,它指向用于创建该原型对象的构造函数。因此,Function.prototype.constructor确实指向Function构造函数。

 以上三个对象的小原型链我们已经化完了,但我们需要把这三个都连在一起。

4,完整的原型链
console.log(Person.prototype.__proto__ === Object.prototype); //true
console.log(Function.prototype.__proto__ === Object.prototype); //true

上述代码可以看出Person.prototype原型对象它的__proto__以及Function.prototype原型对象它的__proto__都指向了Object.prototype这个原型对象。因此可以绘制出:

console.log(Object.__proto__ === Function.prototype); //true
console.log(Person.__proto__ === Function.prototype); //true

上述代码可以看出Object内置对象的__proto__属性以及Person内置对象的__proto__属性都指向了Function.prototype原型对象。因此可以绘制出:

其中Function这个对象比较特殊:

1,所有函数都是Function的实例:在JavaScript中,当你定义一个函数时,无论是通过函数声明、函数表达式、箭头函数,还是使用 new Function() 构造函数,创建的这个函数对象都是 Function 类型的实例。这意味着你可以在这些函数对象上访问到 Function.prototype 上定义的任何属性和方法。

2,Function自己是自己的实例:这个特性听起来可能有点反直觉,但它是JavaScript语言设计的一部分。由于 Function 是一个构造函数,它本身也是一个函数。在JavaScript中,函数是一等公民,这意味着函数可以像其他任何值一样被传递、赋值给变量或作为参数。因此,Function 构造函数(作为一个函数)也是由某个构造函数创建的,而这个构造函数恰好就是它自己。

我们就可以绘制出一个基本上完整的原型链了:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值