关于原型、原型链的理解

    技术2024-04-13  61

    创建对象的几种基本方式

    1.对象字面量

    var person = { name:"Jack", age:15 say:function(){ alert(this.name+"今年"+this.age); } };

    2.使用 Object 构造

    var person = new Object(); person.name = "Jack"; person.age = 15; person.say = function() { alert(this.name+"今年"+this.age); }

    以上两种方式是最基本的创建方式,但是在创建多个同类型对象的时候就显得很笨拙,这时会产生大量重复代码。 因此当需要创建多个同类型对象的时候可以使用下面的方法 ——

    3.构造函数的方式

    function Person(name,age) { this.name = name; this.age = age; this.say = function() { alert(this.name+"今年"+this.age); } } var my = new Person1("Jack",15); var my = new Person2("Lily",13);

    但是这种方式也不够优秀,因为this在对象实例化的时候发生改变,新实例的方法也要重新创建。 也就是每次实例化都要重新创建新的方法。我们可以更优雅一些 —— 让实例化对象们共享属性和方法,共有的方法只创建一次。这时候就需要用到原型

    4.构造函数+原型

    function Person(name,age) { this.name = name; this.age = age; } Person.prototype.say = function() { alert(this.name+"今年"+this.age); } var my = new Person1("Jack",15); var my = new Person2("Lily",13);

    把公共的属性和方法放在原型中,实例就可以实现“共享”,从而达到只需创建一次的目的。 下面我们再来仔细看看,原型相关的——

    构造函数、原型、实例三者之间的关系

    每一个构造函数都拥有一个 prototype 属性,这个属性指向一个对象,也就是原型对象原型对象默认拥有一个 constructor 属性,指向指向它的那个构造函数通过构造函数 new 就可以生成一个实例对象每个实例对象都拥有一个隐藏的属性 _ _ proto _ _,指向它的原型对象 function Person() {} var p = new Person() Person.prototype === p.__proto__ //true Person.prototype.constructor === Person //true

    使用构造函数创建的实例对象 的特点

    实例可以共享原型上的属性和方法实例自身的属性(自有属性)会覆盖原型上面的同名属性,实例自身没有的属性会去原型找 function Person(name,age){ this.name = name this.age = age } Person.prototype.say = function() { console.log("Hi, I'm " + this.name + ","+ this.age + "years old.") } var person1 = new Person("Lily",15); var person2 = new Person("Jack",16);

    原型链

    所有原型链的终点都是 Object 函数的 prototype 属性Objec.prototype 指向的原型对象同样拥有原型,不过它的原型是 null ,而 null 则没有原型

    修改原型

    原型实际上也是个对象,当然也是可以修改的。我们可以直接在原型上增加属性和方法:

    function Person(name,age){ this.name = name this.age = age } Person.prototype.say = function() { console.log("Hi, I'm " + this.name + ","+ this.age + "years old.") } var person1 = new Person("Lily",15); var person2 = new Person("Jack",16); Person.prototype.country = "China" Person.prototype.intro = function() { console.log("Hi, I'm " + this.name + ","+ this.age + "years old.") } person1.country //China person1.intro() //Hi, I'm Lily,15years old.

    但是 在修改原型的时候需要注意:尽量避免在已创建实例之后重写原型。 在已创建实例的情况下重写原型,会切断已创建实例跟原型的关系,造成‘原型链混乱’。

    我们可以看看,正常情况下的原型关系是这样的:

    function Person(name,age){ this.name = name![在这里插入图片描述](https://img-blog.csdnimg.cn/2020070319234850.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2RsZl8wNjEw,size_16,color_FFFFFF,t_70#pic_center) this.age = age } Person.prototype.say = function() { console.log("Hi, I'm " + this.name + ","+ this.age + "years old.") } var person1 = new Person("Lily",15); var person2 = new Person("Jack",16); Person.prototype === person1.__proto__ //true Person.prototype.constructor === Person //true person1.__proto__.constructor === Person //true

    而重写原型对象之后,我们可以看到,新原型对象仍是构造函数的原型,但已经不是实例对象的原型;且新原型对象的构造函数并不指向Person,而是指向Object;但构造函数又还是实例的原型的构造函数…… 场面十分混乱。我们还是看图:

    //- 基于上面的代码重写原型 Person.prototype = { name:"Hello", age:18, say:function(){console.log("Hi!")} } Person.prototype === person1.__proto__ //false Person.prototype.constructor === Person //false person1.__proto__.constructor === Person //true Person.prototype.constructor === Object //true

    如果非要重写原型,那就 在重写原型对象的时候指定 constructor。但是这种方式也并非完美,即便构造函数与原型的关系被保留了,但已创建的实例对象与原型的关系仍然是切断的。此外,以这种方式重设 constructor 属性会导致它的 Enumerable 特性被设置成 true(默认为 false)

    关于ES6中的class

    ES6中的class,实际上就是构造函数的另一种写法(语法糖)。 本质上都是通过原型、原型链去定义方法、实现共享

    class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; } } // 可以这么改写 function Point(x, y) { this.x = x; this.y = y; } Point.prototype.toString = function () { return '(' + this.x + ', ' + this.y + ')'; };

    附上参考: 原型与原型链 js创建对象的多种方式

    Processed: 0.015, SQL: 9