js的原型和原型链

本文深入探讨JavaScript中的原型和原型链概念,包括对象的__proto__属性、函数的prototype属性、原型链的查找机制、构造函数与实例的关系,以及如何通过原型实现属性和方法的共享。此外,还讲解了实例与原型之间的关系、原型链的构造,并讨论了构造函数内部定义的数据与原型实例上定义数据的区别,强调了性能和代码复用的考量。最后,通过一个实例展示了原型链的查找过程。
摘要由CSDN通过智能技术生成

原型的基础知识点

一个简单的对象,用于实现对象的属性继承。可以简单的理解成对象的父类。在浏览器中,每个 JavaScript 对象中都包含一个 __proto__ (非标准)的属性指向该对象的原型,可 obj.__proto__进行访问。只有函数包含prototupe。

五条原则需要牢记

  • 所有引用类型(函数、对象、数组),都存在对象特性,即可以自由拓展属性。(除了null意外)
  • 所有的引用类型(函数、对象、数组),都有一个_proto_(我们这里称他为隐形原型)属性,属性值是一个普通的对象。
  • 所有函数都有一个prototype属性,属性值也是一个普通的函数
  • 所有的引用类型(函数、对象、数组),_proto_属性值指向它的构造函数的 prototype(显性属性)属性值。
  • 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的_proto_(即他的构造函数的prototype)中寻找。如果没有,则会接着往上找,一直上溯到Object.prototype,也就是说所有对象都继承Object.prototype的属性,Object.prototype的原型是null,null没有任何属性和方法。

对象分为普通对象和函数对象凡是通过 new Function() 创建的对象都是函数对象,其他的都是普通对象。

例如: f1、f2、f3、Object、Function都是函数对象,而o1、o2、o3都是普通对象

function f1(){}; 
var f2 = function(){};
var f3 = new Function('str','console.log(str)');

var o1 = {}; 
var o2 =new Object();
var o3 = new f1();

console.log(typeof Object); //function 
console.log(typeof Function); //function  

console.log(typeof f1); //function 
console.log(typeof f2); //function 
console.log(typeof f3); //function   

console.log(typeof o1); //object 
console.log(typeof o2); //object 
console.log(typeof o3); //object

构造函数创建对象:

function Person() {

}
var person = new Person();
person.name = 'Kevin';
console.log(person.name) // Kevin

Person 就是一个构造函数(函数对象),我们使用 new 创建了一个实例对象 person(普通对象)

prototype

每个函数对象都有一个prototype 属性,这个属性指向函数的原型对象
每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性。

function Person() {

}
// 虽然写在注释里,但是你要注意:
// prototype是函数才会有的属性
Person.prototype.name = 'Kevin';
var person1 = new Person();
var person2 = new Person();
console.log(person1.name) // Kevin
console.log(person2.name) // Kevin

proto

每一个JavaScript对象(除了 null )都具有的一个属性,叫proto,这个属性会指向该对象的原型。每个对象都有 __proto__ 属性,但只有函数对象才有 prototype 属性

function Person() {

}
var person = new Person();

Person.prototype.constructor == Person;
person1.__proto__ == Person.prototype;
person1.constructor == Person;

constructor

在默认情况下,所有的原型对象(Person.prototype)都会自动获得一个 constructor(构造函数)属性,这个属性(是一个指针)指向 prototype 属性所在的函数(Person)。

实例的构造函数属性(constructor)指向构造函数 :person1.constructor == Person。

function Person() {

}

person1.constructor == Person
Person.prototype.constructor == Person

function Person() {

}

var person = new Person();

console.log(person.__proto__ == Person.prototype) // true
console.log(Person.prototype.constructor == Person) // true
// 顺便学习一个ES5的方法,可以获得对象的原型
console.log(Object.getPrototypeOf(person) === Person.prototype) // true

person1 为什么有 constructor 属性?那是因为 person1 是 Person 的实例。

那 Person.prototype 为什么有 constructor 属性??同理, Person.prototype (你把它想象成 A) 也是Person 的实例。也就是在 Person 创建的时候,创建了一个它的实例对象并赋值给它的 prototype

结论:原型对象(Person.prototype)是 构造函数(Person)的一个实例。

实例与原型

function Person() {

}

Person.prototype.name = 'Kevin';

var person = new Person();

person.name = 'Daisy';
console.log(person.name) // Daisy

delete person.name;
console.log(person.name) // Kevin

在这个例子中,我们给实例对象 person 添加了 name 属性,当我们打印 person.name 的时候,结果自然为 Daisy。

但是当我们删除了 person 的 name 属性时,读取 person.name,从 person 对象中找不到 name 属性就会从 person 的原型也就是 person.proto ,也就是 Person.prototype中查找,幸运的是我们找到了 name 属性,结果为 Kevin。

原型与原型

(1)当对象为普通对象时:

var obj = new Object();
obj.name = 'Kevin'
console.log(obj.name) // Kevin


obj.__proto__===Object.prototype;
Object.prototype.constructor===Object;
Object.prototype.__proto__===null;

(2)当对象为函数对象时:

function Person() {

}

var person = new Person();

person.__proto__===Person.prototype;
Person.prototype.constructor===Person;
Person.__proto__===Function.prototype;
Person.prototype.__proto__ === Object.prototype;
Function.prototype.__proto__===Object.prototype;
Function.prototype.constructor===Function;
Object.prototype.__proto__===null;
Object.prototype.constructor===Object;

原型链

console.log(Object.prototype.__proto__ === null) // true

JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些

函数内部定义数据和原型实例上定义数据区别:

1.原型上定义实例可以实现共享(形成内部的全局)

function Animal(type,name) {
    this.name = name;
    this.type = type;
    this.tell = function() {
        console.log('hello')
    }
}
var dog = new Animal('dog','lunky');
var cat = new Animal('cat','miao');
dog.tell === cat.tell//false


function Animal(type,name) {
    this.name = name;
    this.type = type;
}
var dog = new Animal('dog','lunky');
var cat = new Animal('cat','miao');
Animal.prototype.tell = function() {
    console.log('hello')
}
dog.tell === cat.tell//true

在函数内部的参数对于不同的实例对象是不同的,而在原型实例上的参数是可以共用的。

2.消耗性能

构造函数中的方法每当new一个对象的时候,就会创建一个构造函数里的方法,如果多个实例对象就会创建多个方法,占用内存,没有提高代码的复用性

将方法定义到构造函数的原型对象里,创建多个实例对象而共享一个方法,方法创建了一次。

比如第一点dog和cat都拥有tell这个函数,但是他们开辟的地址不同,如果我new了一百个实例,那么将会开辟一百个不同的地址,极大的浪费性能,而原型实例只会有一个。

3.定义优先级不同

function Animal(type,name) {
    this.name = name;
    this.type = type;
    this.tell = function() {
        console.log('hello')
    }
}
Animal.prototype.tell = function() {
    console.log('hello world')
}
var dog = new Animal('dog','lunky');
dog.tell //hello


function Animal(type,name) {
    this.name = name;
    this.type = type;
}
var cat = new Animal('cat','miao');
Animal.prototype.tell = function() {
    console.log('hello world')
}
cat.tell //hello world

构造函数中定义的属性和方法要比原型中定义的属性和方法的优先级高,如果定义了同名称的属性和方法,构造函数中的将会覆盖原型中的。可以通过dog.prototype取到。

练习

function Foo(){
    Foo.a=function(){
        console.log(1);
    }
    this.a=function(){
        console.log(2);
    }
}

Foo.prototype.a=function(){
    console.log(3);
}
Foo.a=function(){
    console.log(4);
}

Foo.a();//4
let obj = new Foo();
obj.a();//2
Foo.a();//1

Foo.a()===>4,Foo.a是构造函数Foo()内部定义的私有方法,不在原型链上。因为function Foo(){}只是定义并没有执行,所以是根据Foo.a=function(){console.log(4);}。

obj.a()===>2,因为在new的时候会执行一次构造函数,并且构造函数的方法和变量会被新的对象继承,所以obj对象继承this.a这个方法,由于Foo.a是赋值给构造函数Foo()的,所以会打印2;关于为何没有打印3,是由于寻值会优先在当前obj对象上寻找,如果没有再往上。

Foo.a()===>1,因为在new的时候会执行一次构造函数,所以Foo.a被新的函数覆盖,打印1。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值