一篇文章带你了解清楚JS中的原型

62 阅读4分钟

JavaScript是一门充满魔力的语言,其动态性、灵活性和独特的原型继承机制让许多开发者既爱又恨。在这篇文章中,我们将深入探索JavaScript的原型世界,揭开prototype__proto__和原型链的神秘面纱,让你彻底掌握这一核心概念,为你的JavaScript之旅增添一份从容。

原型初探:构造函数的魔法盒子

在JavaScript中,每个函数都有一个特殊的属性——prototype。这个属性是一个对象,用于存储所有通过该函数构造出来的实例所共享的属性和方法。换句话说,prototype是构造函数的一个蓝图,规定了由该构造函数创建的所有对象的基本结构。例如,当你定义一个构造函数如function Person(name) { this.name = name; }时,其Person.prototype上可以附加诸如sayHello()这样的方法,供所有Person实例共享。 如:

<script>
        // console.log(Person.prototype);
        //Person.prototype//函数内置属性
        Person.prototype.say=function(){
            console.log('hello');
        }
        Person.prototype.age=18
        function Person(){
            this.name ="xie"
        }
        let p=new Person();
        let p2=new Person();
        p.say()
        p2.say()
        p.name="xiexie"
    </script>

image.png

实例对象与原型的不解之缘

每个由构造函数通过new操作符创建的实例对象,都暗含着一条通往其构造函数prototype的隐秘通道。这条通道就是每个实例对象的__proto__属性。这意味着,即使实例本身没有某个属性或方法,它也能通过__proto__链接到构造函数的原型对象上寻找。这就是所谓的原型继承的核心——隐式原型链

原型链:属性查找的奇幻旅程

当访问一个对象的属性或方法时,JavaScript引擎会首先检查该对象自身是否有定义。如果没有,它不会立刻宣布失败,而是继续沿着对象的__proto__(即隐式原型)向上追溯,直到找到匹配的属性或方法,或者达到原型链的终点null。这个逐级查找的过程就是原型链的精髓所在。形象地说,原型链就像一条串起所有对象的链条,让它们能够共享祖先的属性和方法,实现代码的复用和高效的内存管理。

Grand.prototype.lastName='zhang'
    function Grand(){
        this.name='san'
    }
    Father.prototype=new Grand()
    function Father(){
        this.age=40
    }
    Son.prototype=new Father()
    function Son(){
        this.like ='coding'
    }
    let son =new Son()
    // console.log(son.like);
    // console.log(son.age);
    console.log(son.name);

image.png 查找过程:先找自身显性原型,没有再找隐性原型,对象上的隐形原型等于他的构造函数的显性原型,他的构造函数的显性原型没有,就会去找他的构造函数的隐性原型上找,就这样一级一级的往上找,直到找到目标为止。

{
         like:"coding",
         __proto__: Son.prototype==new Father() : {
            __proto__:Father.prototype==new Grand() : {
                 __protp__:Grand.prototype == new Object() :{
                     __proto__:Object.prototype:{
                         __proto__:null
                     }
                }
             }
         }
     }

特例:并非所有对象都有原型

尽管大部分JavaScript对象都有原型链,但也有例外。使用Object.create(null)创建的对象就是一个特例,它没有__proto__指向,意味着它没有原型,因此也无法从原型链中继承任何属性或方法。这种对象常用于构建纯净的哈希表或映射结构,避免了原型链带来的意外污染。

<script>
    let a ={
    num:1
}

let b= Object.create(a);
let c= Object.create(null);
</script>

image.png

原型的实践意义

理解原型机制对于高效地使用JavaScript至关重要。它不仅影响着对象的创建、属性的查找,还与继承、封装等面向对象编程的核心概念紧密相连。通过原型,你可以:

  • 实现继承:无需类的语法,仅通过对象间的链接即可实现继承,使代码更加灵活。
  • 优化性能:将方法定义在原型上,所有实例共享同一份方法定义,节省内存。
  • 动态扩展:即使在实例创建后,也可以向原型添加属性和方法,所有实例即时获得更新。

小心陷阱,拥抱原型

虽然原型机制强大,但也伴随着一些陷阱,如原型污染、循环引用等问题。正确地管理原型链,避免不必要的属性覆盖和内存泄漏,是每位JavaScript开发者必须掌握的技能。

总结

JavaScript的原型系统是一把双刃剑,它赋予了语言无与伦比的灵活性,同时也带来了学习和使用的复杂度。通过本文的梳理,希望能帮你揭开原型的神秘面纱,理解其背后的逻辑与机制,从而在实际编码中更加得心应手。记住,掌握原型,就是掌握了JavaScript的灵魂。在不断实践和探索中,你会发现,原型并不那么可怕,反而能成为你解决问题的强大工具。