先复习一些原型链的知识:
[[Prototype]]
机制是一种存在于一个对象上的内部链接,它指向一个其他对象。
在JavaScript中,每个对象都有一个原型对象(prototype
),当访问对象的属性或方法时,如果对象自身没有定义,它会沿着原型链向上查找直到找到对应的属性或方法。这种原型链的机制使得对象之间可以实现继承关系,从而可以共享原型对象上的属性和方法。
1. 行为委托
首先定义一个称为 Parent 的对象 (不是一个类,也不是function),让它拥有具体的行为。定义一个 对象来持有这个特定任务的数据 /行为。
const A = {setName:function(name){this.name = name},getName: function () {return 'my name is ' + this.name;},
}
// 使 B 委托到 A, B的[[protoType]]会指向A对象
const B = Object.create(A);//Object.create作用:以一个现有对象为原型创建一个新对象
B.greeting = function(){console.log('hello, '+this.getName());
}//使 b1 委托到 B, b1的[[protoType]]会指向B对象
const b1 = Object.create(B);
// 通过原型链访问A对象上方法
b1.setName('b1');
//使 b2 委托到 B, b2的[[protoType]]会指向B对象
const b2 = Object.create(B);
b2.setName('b2');// 通过原型链访问B对象上方法
b1.greeting()// hello, my name is b1
b2.greeting()// hello, my name is b2
行为委托意味着:在访问某个对象上不存在的属性或方法时,让这个对象为属性或方法引用提供一个委托。可以多级委托,但是不能相互委托,不能在两个或多个对象间相互地委托(双向地)对方来创建一个 循环 。比如使 B 链接到 A,然后试着让 A 链接到 B。
注意:要尽量避免在 [[Prototype]] 链的不同层级上有相同的命名,因为会出现遮蔽现象。
2. 原型连中的遮蔽
const oldObj = {a: 2
};// newObj的[[protoType]]会指向 oldObj
const newObj = Object.create( oldObj );console.log(oldObj.a);//2
console.log(newObj.a);//2 (查找原型链上的属性)console.log(oldObj.hasOwnProperty( "a" ));// true
console.log(newObj.hasOwnProperty( "a" ));// false
newObj.a++;
console.log(oldObj.a);//2
console.log(newObj.a);//3 console.log(oldObj.hasOwnProperty( "a" ));// true
console.log(newObj.hasOwnProperty( "a" ));// true (是自己的属性噢!竟然创建了一个新属性!!)
虽然看起来 newObj.a++ 应当(通过委托)查询并原地递增 oldObj.a 属性,但是 ++ 操作符相当于 newObj.a = newObj.a + 1。
newObj.a + 1 是在[[Prototype]]
上进行 a 的 [[Get]]
查询,从 oldObj.a 得到当前的值 2,将这个值递增1。然后将这个值 3 用 [[Put]]
赋值给 newObj 上的新遮蔽属性 a上。
如果想递增 oldObj.a,唯一正确的方法是 oldObj.a++。
3. 面向类
实现与行为委托例子一样的行为:
function A(name){this.name = name
}
A.prototype.getName = function (){return 'my name is ' + this.name
}
function B(name){// this的显式绑定:this指向A,并调用A函数初始化nameA.call(this,name)
}
// B继承A
// 将A.prototype对象作为B的原型对象,B.prototype继承了A.prototype的属性和方法
B.prototype = Object.create(A.prototype);
B.prototype.greeting = function(){console.log('hello, '+this.getName());
}
// 调用 B函数,创建一个新对象b1,并将this指向b1
const b1 = new B('b1');
const b2 = new B('b2');b1.greeting();// hello, my name is b1
b2.greeting();// hello, my name is b2
在 JS 中,构造器 仅仅是一个函数,它们偶然地与前置的 new
操作符一起调用。它们不依附于类,它们也不初始化一个类。它们甚至不是一种特殊的函数类型。它们本质上只是一般的函数,在被使用 new 来调用时改变做了附加的行为。(创建一个新对象,并将this指向这个新对象)
4. 行为委托和面向类的区别
- 对象关系:
- 行为委托: 在行为委托中,对象之间是对等的,它们彼此通过委托来共享和重用代码。对象之间的关系是动态的,一个对象可以将请求委托给另一个对象来处理,而不是通过类和继承来建立父子关系。
- 面向类: 在基于类的继承中,通常存在明确的父类和子类之间的关系。子类继承父类的属性和方法,并且可以添加或修改这些属性和方法。对象之间的关系是静态的,由类定义决定,在对象创建时就确定了。
- 代码组织方式:
- 行为委托: 在行为委托中,代码的组织方式更加灵活,因为对象之间的关系是动态的。对象通过委托来共享和重用代码,使得代码更具可组合性和灵活性。
- 面向类: 在基于类的继承中,代码通常是通过类来组织的,对象之间的关系由类的层次结构确定。虽然类继承提供了一种组织和复用代码的方式,但它也可能导致类层次结构的复杂性和深层次的继承链。
- 继承机制:
- 行为委托: 在行为委托中,对象之间通过委托来共享和重用代码,而不是通过继承。
- 面向类: 在基于类的继承中,子类继承父类的属性和方法,并且可以通过继承来共享和重用代码。继承是通过类和类之间的关系来实现的,子类可以扩展或修改父类的行为。