深入学习js之浅谈设计模式(混入)

在继承或者实例化时,JS的对象机智并不会自动执行复制行为。简单地说,JS中只有对象,并不存在可以被实例化的类。一个对象并不会被赋值到其他对象,他们会被关联起来,

由于在其他语言中类表现出来的都是复制行为,因此JS的开发者们想出一种模拟类的行为。这个方法就是混入。混入又分为显式和隐式

1.显式混入

看以下代码

function mixin(sourceObj, targetObj) {
	for (var key in sourceObj) {
		if (!(key in targetObj) && sourceObj.hasOwnProperty(key)) {
			targetObj[key] = sourceObj[key];
		}
	}

	return targetObj;
}
var Suv = {
	engines: 1,

	ignition: function() {
		console.log("ignition");
	},

	drive: function() {
		//this.ignition();
		console.log("Steering and moving forward!");
	}
}

var Car = mixin(
	Suv, {
		wheels: 2,

		drive: function() {
			console.log("drive");
		}
	}
)
console.log(Car);

注意我们处理的不是类而是一个个对象,我们复制Suv对象自身存在的属性(不包含原型链上的)且Car对象没有的属性到Car对象中。

现在Car属性就有了一份Suv属性和函数的副本了,事实上,函数实际上没有被复制,复制的是函数引用。所以Car属性ignition只是从Suv中复制对函数ignition的引用。值类型的engines属性直接复制。然而drive属性以及存在,所以保留了下来,这里模仿了“子类”对“父类”的重写。
1.1显式伪多态

Suv.drive.call(this);
function mixin(sourceObj, targetObj) {
			for (var key in sourceObj) {
				if (!(key in targetObj) && sourceObj.hasOwnProperty(key)) {
					targetObj[key] = sourceObj[key];
				}
			}

			return targetObj;
			}
			var Suv = {
				engines: 1,

				ignition: function() {
					console.log("ignition");
				},

				drive: function() {
					this.ignition();
					console.log("Steering and moving forward!");
				},
				stop: function() {
					this.ignition();
				}
			}

			var Car = mixin(
				Suv, {
					wheels: 2,

					drive: function() {
						Suv.drive.call(this);//显式伪多态
						console.log("drive");
					}
				}
			)
			console.log(Car.drive());

Suv.drive.call(this)这就是我们说的显式多态

js在ES6之前并没有多态机制,所以,由于Car和Suv中都有drive函数,为了指明调用对象,我们必须使用绝对引用。我们通过名称指定Suv对象并调用它的drive函数。

但是如果执行 Suv.drive() ,this会被绑定到Suv对象而不是Car对象,所以用call硬绑定drive在Car对象的上下文中执行

用显式伪多态shi'xi多重继承的例子

var Suv = {
				engines: 1,

				ignition: function() {
					console.log("ignition");
				},

				drive: function() {
					console.log(this.engines);
					console.log("Steering and moving forward!");
				},
				stop: function() {
					this.ignition();
				}
			}
			
			var Bmw = {
				engines:2,
				
				superAccerate:function () {
					console.log("superAccerate");
				},
				accelerate:function () {
					console.log(this.engines);
					console.log("accelerate!");
				}
			}
			var Car = mixin(
				[Suv,Bmw], {
					wheels: 3,

					drive: function() {
						Suv.drive.call(this);
						console.log("drive");
					},
					accelerate:function () {
						Bmw.accelerate.call(this);
						console.log("Car accelerate");
					}
				}
			)
	
Car继承了Suv和Bwm的属性,这里有个加载顺序的问题,如果先复制了Suv中有的属性Bwm也有(Car中无)那么只会复制一次这个属性忽略Bwm中的属性。

1.2寄生复制

//传统的js类Maserati
			function Maserati() {
				this.engines = 1;
			}
			Maserati.prototype.ignition = function () {
				console.log("Turning on my engines.");
			};
			Maserati.prototype.drive = function () {
				this.ignition();
				console.log("Steering and moving forward!");
			}
			//寄生类Car
			function Car() {
				//首先car是一个Maserati,new会将对象.prototype的指向绑定到Maserati.prototype
				var car = new Maserati();

				//接着我们对car进行定制
				car.wheels = 4;

				//保存到MasDrive的Maserati.prototype.drive的特殊引用
				var MasDrive = car.drive;

				//重写drive
				car.drive = function () {
					MasDrive.call(this);
					console.log("wheels left " + this.wheels);
				}
				return car;
			}

			var myCar = new Car();//可以不用new
			myCar.drive();
首先,我们复制一份Maserati父类(对象)的定义,然后混入子类(对象)的定义(如果需要还可以保留父类的特殊引用),然后用这个符合对象构建实例
注意 new Car()会创建一个新对象绑定到Car的this上,但是这里我们已经返回了自己的Car对象所以原来创建的对象会被丢弃,因此可以不使用new来调用Car,结果一样,避免创建并丢弃多余对象。

2.隐式混入 

var something = {
				cool: function () {
					this.greeting = "Hello World";
					this.count = this.count ? this.count + 1 : 1;
				}
			};
			something.cool();
			something.greeting;// "Hello World"
			something.count;// 1

			var another = {
				cool: function () {
					//隐式把something混入another
					something.cool.call(this);
				}
			};

			another.cool();
			console.log(another.greeting);// "Hello World"
			another.count;// 1
通过在构造函数调用或者方法调用中使用something.cool.call(this),我们实际上调用了函数的something.cool()并在another上下文中调用了它(通过this绑定到another),

最终结果是something.cool()中的赋值操作都会应用在Another对象上而不是 Something对象上。

虽然这类技术利用了this的重新绑定功能,但是something.cool.call(this)仍然不太灵活,可维护性不佳。

小结:

类是一种设计模式,其他咽炎提供了面向类的原生语法,JS中也有类似的语法但是和其他语言的类完全不同。

类意味着复制

传统的类被实例化时,它的行为会被复制到实例中。类被继承时,行为也会被赋值到子类中。

多态(在继承链不同层次名称相同但功能不同的函数)看起来似乎是从子类引用父类,但从本质上引用的其实是复制的结果。


以下是js中的类使用模式

js并不会自动创建对象的副本(和其他语言类的本质区别)

混入模式(无论显式还是隐式)可以用来模拟类的复制行为,但是通常会产生丑陋并且脆弱的语法,比如显式伪多态(OtherObj.methodName.call(this,...),这会让代码更加复杂且难维护。

此外,显式混入实际上无法完全模仿 类的复制行为,因为对象(包括函数也是对象)只能复制引用,无法复制被引用对象或者函数的本身。

总的来说在js中模拟类的行为并不是一种优雅的解决方式

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值