JS原型继承工作原理

本文详细探讨了JavaScript中对象属性的查找过程,包括原型链的使用及属性的继承方式。通过实例演示了如何利用原型链实现类与实例的关系,并解释了new运算符的工作原理及其带来的空间浪费问题。进一步介绍了prototype属性的引入及其作用,通过对比不同写法的代码示例,清晰地阐述了如何利用prototype进行属性和方法的共享。最后,通过一道阿里在线前端笔试题,验证了prototype模式的应用及in运算符的使用,强调了原型链在JavaScript编程中的重要性。

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

当查找一个对象的属性时,JS会向上遍历原型链,直到找到给定的属性名称为止,如果没找到就是undefined。

大多数的JS的实现用_proto_属性来表示一个对象的原型链,以下代码展示了JS引擎如何查找属性。

function getProperty(obj, prop) {
    if (obj.hasOwnProperty(prop))
        return obj[prop]

    else if (obj.__proto__ !== null)
        return getProperty(obj.__proto__, prop)

    else
        return undefined}

让我们举一个比较简单的例子,假设有个三维点,坐标x、y、z,同时有print打印方法。

现在我们创建一个对象point,具有x、y、z和print属性,为了能创建一个新的三维坐标点,我们需要创建一个新的对象,使得它的_proto_指向point,继承point。类似C++中的OOP,例如point为基类,创建新的point对象为原先point的子类。

var Point = {
    x: 0,
    y: 0,
    z: 0,
    print: function () { console.log(this.x, this.y, this.z); }};
    var p = {x: 10, y: 20, z: 30,__proto__: Point};p.print(); // 10 20 30

但是js工程师一般都不会这样写原型继承,他们如下写出:

function Point(x, y) {
    this.x = x;
    this.y = y;
    this.z = z;}
    Point.prototype = {
    print: function () { console.log(this.x, this.y, this.z); }};
    var p = new Point(10, 20, 30);
    p.print(); // 10 20 30

这里涉及到了new运算符的工作原理

new后面跟的不是类,而是构造函数 ,用new构造函数生成实例对象,有一个很明显的缺点,就是每个实例无法共享同一个属性和方法。在C++中,如果类中定义了一个static成员,那么所有该类的实例都共享该成员。而JS中每一个实例的对象都有自己属性的副本,这样比较浪费空间,而且无法实现数据的共享。

基于以上new构造函数的缺陷,JS创始人为构造函数添加了prototype属性

prototype属性的引入

JS规定每一个构造函数都有prototype属性,该属性指向另一个对象,另一个对象中所有的属性和方法都会被构造函数的实例引用。

这个属性包含一个对象,所有需要共享的属性和方法放入这个对象中,而不需要共享的属性和方法放入构造函数中。实例对象一旦创建成功,就会自动引用prototype对象中的方法和属性,即实例对象的属性和方法分为两种,一种是本地的,即放入构造函数中的属性和方法,一种是引用的,即放入prototype对象。例如以下代码:

function DOG(name){
    this.name = name;
  }
  DOG.prototype = { species : '犬科' };

  var dogA = new DOG('大毛');
  var dogB = new DOG('二毛');

  alert(dogA.species); // 犬科
  alert(dogB.species); // 犬科

在这个例子中,species属性放入prototype对象中,那么实例dogA和dogB共用species属性。只要其中一个实例的species发生改变,则会影响所有实例的species

Prototype模式的验证方法
isPrototypeOf

这个方法用来判断prototype对象和某个实例之间的关系,例如

alert(DOG.prototype.isPrototypeOf(dogA)); //true
hasOwnProperty

每一个实例对象都有一个该方法,用来判断该实例中的某个属性是来自本地属性,还是继承自原型对象属性。例如

alert(dogA.hasOwnProperty(name))//true
in运算符

in运算符可以判断某个属性是否属于实例对象,不管是本地属性还是继承自原型对象属性。如

alert("name" in dogA); //true
alert("species" in dogA); //true

in运算符还可以遍历某个对象的所有属性,如

for (var prop in dogA) {
alert(prop);
}
测试

有一道阿里的在线前端笔试题,题目如下:

现在有如下的代码:

var foo = 1;
function main(){
    console.log(foo);
    var foo = 2;
    console.log(this.foo)
    this.foo = 3;}

1.请给出以下两种方式调用函数时,输出的结果,并说明原因

var m1 = main();
var m2 = new main();

2.如果需要var m1= main()产生的结果与前面m2产生结果一样,应该如何改造main函数

第一题解答:首先根据JS的变量提升规则,可以知道,全局的foo被main函数屏蔽了,main函数在内部定义了一个foo同名的变量,该变量在第一个console之前只定义而未赋值,故为undefined(undefined有两种情况会出现,已定义未赋值,未定义)。而在第二个console的时候,this指向的是window,故输出为1

转载于:https://my.oschina.net/u/866703/blog/214544

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值