彻底理解JS原型以及构造函数

本文详细探讨了JavaScript中的原型对象,从何时创建、如何访问到其与构造函数的关系。介绍了[[prototype]]、isPrototypeOf、Object.getPrototypeOf等概念,讨论了原型链的工作原理和存在的问题,如属性设置和屏蔽。同时,文章还分析了NEW操作符在原型中的作用,以及constructor属性的变化和潜在问题。通过对原型的深入理解,有助于更好地掌握JavaScript的面向对象编程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么时候创建了原型对象

我们创建的每一个函数,不光是构造函数,都会有一个prototype属性,当我们创建了一个函数/声明了一个函数,它就会根据一组特定的规则为该函数创建一个prototype属性指向的对象。

原型对象

原型对象就是我们创建了一个新函数的时候,自动创建的一个对象,当我们仅仅是创建了函数的时候,这个原型对象是这样的:

这里写图片描述

原型对象是会自动获得一个constructor属性,这个属性指向我们创建的函数。
原型对象还有很多从Object继承来的属性和方法。

但是当我们创建了一个新实例后,该实例内部将包含一个指针,指向构造函数的原型对象~ [[prototype]]

[[prototype]]

这个是一个指针,这指针指向原型对象。 脚本没有标准的方式访问这个[[prototype]],但FireFox Safari Chrome 都支持一个属性称为__proto__

ppt2.__proto__

真正重要的是:连接存在于实例和原型对象之间,而不是实例与构造函数。

Person–> Person.prototype <–person1
这里写图片描述

构造函数的prototype属性指向原型对象,原型对象的constructor属性又指回了构造函数!

我们访问原型中的方法是通过搜索查找对象属性的过程实现的。

isPrototypeOf

判断某个实例和某个原型的对应关系。

Person.prototype.isPrototypeOf(person1);//true

它就是判断实例的[[prototype]]是否指向调用这个方法的对象。

Object.getPrototypeOf

ES5新方法,用来获取一个实例的原型。

Object.getPrototypeOf(person1)==Person.prototype;

返回的原型,可以访问这个原型中的属性方法值。
我们在使用原型继承的时候会用到这个方法。

原型链

这里写图片描述
原理: 我们每次读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性,从对象的这个当前实例开始,如果找到这个属性就停止。
否则继续搜索它的原型对象,如果搜索到了属性就停止。仍然搜索不到,就搜索这个原型对象的上一级的原型对象,直到Object.prototype,这个是所有对象的根原型对象,它的上一级是null。

弊端

我们不能通过对象实例重写原型中的属性。
只能是覆盖 屏蔽,因为原型链搜索会因此停止。
它搜索到同名的属性就不会继续搜索了。

delete 这个属性,可以恢复原型链的搜索

使用hasOwnProperty

这个方法可以区分 原型链上的属性和 实例的属性
实例属性才返回true

原型与for in 与 in操作符

for in循环返回的是包括原型链上的所有属性!
用hasOwnProperty可以返回所有实例属性

如果要判断所有原型链上的属性呢?

function hasProtoProperty(object,name){
    return !object.hasOwnProperty(name)&&(name in object);
}

还有就是for - in 循环中的所有属性是能过通过对象访问,并且是可枚举的属性!!!也就是说有些浏览器内置的属性无法访问,Object.defineProperty改为不可枚举的属性也是。

但是 in 操作符
判断这个属性是否存在,不管是否是可枚举的,只要是原型链或实例属性中存在!

BUG
但是一般定义的属性都是可枚举的,IE8及更早的除外。

ES5将constructor和prototype属性的 [[Enumerable]]设置为false~!

取得对象上所有可枚举实例属性

Object.keys()方法。
仅仅返回所有可枚举的实例属性

var p1=new Person();
p1.name='sd';
pa.age=23;
var keys=Object.keys(p1);
//name age 
如果返回所有实例属性不管是否可枚举:
var keys=Object.getOwnPropertyNames(Person.prototype);

你可以把此处的Person.prototype理解为一个它上级原型对象的实例属性,它返回//constructor ,name,age
也就是说把不可枚举的属性也包含了

ES5两个新方法 IE9+

原型的动态性

任何对原型对象的修改都会在实例对象上立即反映出来。
JS多态的天然性!!!!
创建了实例之后,再修改原型!!!也是一样反映

实质原因: 原型与实例的连接不过是一个指针,而不是一个副本
关键还是搜索原型链这个机制!!!

function Person(name,age,job){
    this.name=name;
    this.age=age;
    this.job=job;
}
var friend =new Person();;
Person.prototype={
    constuctor:Person,
    name:"Nicolas",
    age:29,
    friend:["SH","WW"],
    job:"Soft Engineer"
    sayName:function(){

    }
};

如果我们修改了能够立即在所有对象实例中反映出来

上面我们让friend的原型对象为Person这个构造函数的prototype,但是后面我们又更新了这个Person的原型对象。重写了原型对象,这样就切断了和friend这个实例的联系。
friend指向的是原来的,旧的那个原型对象!

重点实例中的指针仅指向原型,而不指向构造函数

原型的问题

对于共享的引用类型来说,改变其中一个,就会改变所有实例的这个属性。

属性设置和屏蔽

前面说过给实例属性赋值,会屏蔽同名的原型中的属性。即屏蔽~ 类似的[[GET]]操作总是选择最底层的属性

对于实例中不存在的属性,而在原型链中存在的话
执行这个语句 myobj.foo=”bar”; (添加屏蔽属性)
其实有三种情况
前提都是 如果在原型链上有这个属性~

  • 如果这个属性没有被标记为writable:false,只读的,那么就会在这个对象实例中添加这个属性,它是屏蔽属性
  • 如果在[[prototype]]链上层存在这个属性,但它只是被标记为只读,writable:false,那么无法修改已有属性或在当前对象上创建屏蔽属性。 严格模式下报错
  • 如果它是一个setter,那么就一定会调用这个setter

foo不会被添加到myobj,也不会重新定义foo这个setter
如果你希望第二第三种情况下也能屏蔽这个foo属性,就要使用Object.defineProperty也不是 =


NEW操作与原型

function Foo(){}
var a=new Foo();
Object.getPrototypeOf(a)===Foo.prototype; //true

new操作会产生一个对象a,其中一步就是将a内部的[[prototype]]链接到Foo.prototype所指向的对象。

面向类的语言在那个,类可以被实例化多次,就像模具制作。(类把行为复制到物理对象中,对每个对象来说都是这样)而JS,没有类似的复制机制。你只能创建多个对象,但是它们的[[prototype]]关联的是同一个对象。

我们通过原型把对象关联起来,但是仅仅是关联起来,不会复制,只是创建了一个关联。

本质来说
在JS中,我们没有构造函数,关键是 new调用它,会劫持这个普通函数并用构造对象的形式来调用它~!

function Nothing(){
    console.log('sss');
}

var a=new Nothing();
//a {} 

a是一个空对象,Nothing是一个普通的函数,但是被new调用的时候,它就会构造一个对象并赋值给a。这看起来像是new的一个副作用。

constructor的问题

之前讨论constructor属性时,其实对constructor的理解有点问题,因为我们可能认为这个属性指向的函数是对象的构造函数。 这么理解确实很容易。

constructor会被隔断联系

Foo.prototype的.constructor 属性只是Foo函数在声明时的默认属性。如果你创建了一个新对象并替换了函数默认的.prototype对象引用,那么新对象不会自动获得constructor属性。

function Foo(){}
Foo.prototype={
//?
};//创建一个新的原型对象

var a1=new Foo();
a1.constructor===Foo; //false
a1.constructor===Object; //true!

这个新建的对象a1,看起来是用Foo构造的,但是呢,从上面的例子看出来,a1.constructor却不是Foo。
其实a1并没有constructor属性,这个属性是委托[[prototype]]链上的Foo.prototype来查找的!
但是它上面也没有constructor属性(默认的Foo.prototype有这个属性)

这里写图片描述

这里写图片描述
可以看出来,我们如果在声明了
function Foo(){} 后去重写它的prototype,那么constructor属性就会消失了

如何修复?

Object.defineProperty(Foo.prototype,"constructor",{
    enumerable:false,
    writable:true,
    configurable:true,
    value:Foo //让这个.constructor指向FOO
})

实际上对象的.constructor会默认指向一个函数,这个函数可以通过对象的.prototype引用!!
constructor并不是一个不可变的属性,它是不可枚举的,但是它的值可写。
所以说这个属性是非常随意的,不可靠,不安全的引用。
我们要尽量避免使用这些引用!

它的行为非常难以捕捉,我们可以随时修改,所以一般不要用这个引用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值