JavaScript设计模式与开发事件阅读总结(一)
文章目录
面向接口编程
思想:
在静态编程语言中,事先约定好项目的各个功能的接口(接口的签名和方法),实际开发时就可以直接实现这个接口就可以继续完成实际逻辑了,并且如果项目在后续进行升级,我们只需要按照接口约定重新实现一下,就可以达到项目升级和扩展的目的。(要通过抽象类或者接口等将对象进行向上转型)ts是静态的JavaScript
在动态编程语言中,一个对象只要有某种方法,并且这些方法提供了正确的实现,它就可以当做某种类型使用。
- 一个对象若有 push 和 pop 方法,并且这些方法提供了正确的实现,它就可以被当作栈来使用。
- 一个对象如果有 length 属性,也可以依照下标来存取属性(最好还要拥有 slice 和 splice 等方法),这个对象就可以被当作数组来使用。
抽象类和 interface 的作用主要都是以下两点:
通过向上转型来隐藏对象的真正类型,以表现对象的多态性。
约定类与类之间的一些契约行为。
我们只需要根据约定类的结构进行编程即可,无需关心实际的实例是哪个类的,以及它有没有这个方法。
多态
多态:对同一事件,不同人或物会给出不同的反馈。
思想: 将“做什么”与“谁去做以及怎么去做”分离开,即将“不变的事物”与“可能改变的事物”分离出来。
js实现多态:定义一个函数对象,将不变的抽取出来放在函数对象中,具体实现在定义对象时给出实现。
在js中多态是与生俱来的,只取决于这个对象上有没有这个方法,而不取决于它是否是某种类型的对象。而在一些静态语言中实现多态需要通过向上转型来实现。
// 动物都会叫,这个是一个不变的
var makeSound = function (animal) {
// 调用这个方法会去到对象原型上找到并调用
animal.sound();
};
// 不同动物叫发出的声音不同,实现这个方法通过挂在在原型上
var Duck = function(){}
Duck.prototype.sound = function(){
console.log( '嘎嘎嘎' );
};
var Chicken = function(){}
Chicken.prototype.sound = function(){
console.log( '咯咯咯' );
};
makeSound( new Duck() ); // 嘎嘎嘎
makeSound( new Chicken() ); // 咯咯咯
ts实现多态:通过抽象类和继承
abstract class Animal {
abstract makeSound():void;
}
class Duck extends Animal {
makeSound(): void {
console.log("嘎嘎嘎")
}
}
class Chicken extends Animal {
makeSound(): void {
console.log("咯咯咯")
}
}
const duck = new Duck()
const chicken = new Chicken()
duck.makeSound()
chicken.makeSound()
多态的最根本好处在于,你不必再向对象询问“你是什么类型”而后根据得到的答案调用对象的某个行为——你只管调用该行为就是了,其他的一切多态机制都会为你安排妥当。
多态最根本的作用就是通过把过程化的条件分支语句转化为对象的多态性,从而消除这些条件分支语句。
封装
封装的目的:将信息隐藏,包括封装数据、封装实现、封装类型和封装变化。
js封装数据:依赖变量的作用域来实现封装特性,只能模拟public和private特性。在js函数中存在作用域,函数的返回值就是一个public变量,函数内部定义的值是一个private变量。
ts封装数据:使用public、protected、private修饰符。
封装实现:封装使得对象内部的变化对其他对象而言是透明的,也就是不可见的。对象对它自己的行为负责。其他对象或者用户都不关心它的内部实现。
封装类型:静态语言(ts)通过抽象类和接口来进行的;但是js是一门类型模糊的语言,不具有该能力。
封装变化:找到变化并封装之。
设计模式———原型模式
JavaScript 就是使用原型模式来搭建整个面向对象系统的。
原型模式是用于创建对象的一种模式,不关心对象的具体类型,找到一个对象,然后通过克隆来创建一个一模一样的对象。
原型模式的实现关键,是语言本身是否提供了clone方法。js在es5提供了Object.create()方法用来克隆对象。
// 若不支持Object.create()方法,解决兼容性:
Object.create = Object.create || function( obj ){
var F = function(){};
F.prototype = obj;
return new F();
}
原型模式的真正目的是提供了一种便捷的方式去创建某个类型的对象,克隆只是创建这个对象的过程和手段。
重点:js中的根对象是Object.prototype 对象。Object.prototype 对象是一个空的对象。在 js 中的每个对象,实际上都是从 Object.prototype 对象克隆而来的,Object.prototype 对象就是它们的原型。
JavaScript中没有类,new关键字操作的是 函数构造器
new关键字运算的过程:
function Person(name) {
this.name = name;
}
Person.prototype.getName = function () {
return this.name;
};
var objectFactory = function () {
var obj = new Object(), // 从 Object.prototype 上克隆一个空的对象
Constructor = [].shift.call(arguments); // 取得外部传入的构造器,此例是 Person
obj.__proto__ = Constructor.prototype; // 指向正确的原型
var ret = Constructor.apply(obj, arguments); // 借用外部传入的构造器给 obj 设置属性
return typeof ret === 'object' ? ret : obj; // 确保构造器总是会返回一个对象
};
var a = objectFactory(Person, 'sven');
JavaScript 给对象提供了一个名为__proto__的隐藏属性,某个对象的__proto__属性默认会指向它的构造器的原型对象,即{Constructor}.prototype。
var a = new Object();
console.log ( a.__proto__=== Object.prototype ); // 输出:true
如果对象无法响应某个请求,它会把这个请求委托给它的构造器的原型。
原型继承方式:
var obj = { name: 'sven' };
var A = function(){};
A.prototype = obj;
var a = new A();
console.log( a.name ); // 输出:sven
上面这段代码的执行过程:
- 声明并赋值了一个对象obj,声明了一个函数构造器A,将obj挂载在A的prototype原型上;
- 实例化A,得到实例a;
- 先遍历对象a中的所有属性,都是没有name这个属性;
- 在a的构造器原型a.proto(A.prototype)上查找;
- 因为A.prototype上有name属性,返回它的值。
一个“类”继承自另外一个“类”的效果:
var A = function(){};
A.prototype = { name: 'sven' };
var B = function(){};
B.prototype = new A();
var b = new B();
console.log( b.name ); // 输出:sven
上面这段代码的执行过程:
- 声明了一个函数构造器A,并在其prototype原型上挂载一个对象{ name: ‘sven’ };
- 声明了一个函数构造器B,将A的实例赋值给B的prototype原型上;
- 实例化B,通过实例b找name属性
- 先遍历对象b中的所有属性,都是没有name这个属性;
- 在b的构造器原型b.proto(B.prototype)上查找,没有name属性;
- B.prototype指向A,再去A上查找,也没有name;
- 在 A.prototype 中找到了 name 属性,并返回它的值。
Object.prototype 的原型是 null,查找会一直找到null结束,没有这个属性返回undefined
this指向
this总是指向一个对象,具体指向那个对象是在运行时基于函数的执行环境动态绑定的。
this指向大致分为四种:
-
对象的方法调用(this指向该对象)
-
普通函数调用(指向全局对象window)
-
构造器调用(指向实例化的对象)
注意:如果构造器显示的返回一个非对象数据类型或者不返回值,那么this指向该对象;如果返回一个对象,this指向将不确定。
var MyClass = function(){ this.name = '123'// 这里this指向MyClass,但是实例化对象后获取不到这个name return { name:'anne' } } var obj = new cla(); console.log(obj.name);// anne
-
Function.prototype.call 和 Function.prototype.apply调用(用于改变传入的函数的this指向)
call和apply的同异
相同点:
- 作用一致,改变传入函数的this指向
- 都会立即执行函数
不同点:
- 接收参数不同:第一个参数都是指定函数体内this对象的指向;apply的第二个参数是一个带有下标的集合(数组、类数组),call从第二个参数往后每个参数依次传入函数。
注意:当第一个参数传入为null时,函数体内的this会指向默认的宿主对象,在浏览器中时window;但是在严格模式下任然是null。
Function.prototype.bind()方法
用于指定函数内部this指向,不会立即执行该函数,传递参数和call一样。
闭包
前置知识两大概念:
- 变量作用域:变量的有效范围,局部变量、全局变量。
- 变量生命周期:全局变量生命周期是永久的,局部变量随着函数调用的结束而结束。
注:在js中变量前没有带上关键字var,这个变量就会变成全局变量。
闭包的概念:能够读取其他函数内部变量的函数。
闭包的作用:1、封装变量 2、延续局部变量的寿命
命令模式
意图:把请求封装为对象,从而分离请求的发起者和请求的接收者之间的耦合关系。在命令执行前,预先往命令对象中植入命令的接收者。
面向对象中的命令模式,预先植入的命令接收者被当作对象的属性保存起来;而在闭包中的命令模式,命令接收者会在被封闭在闭包的环境中。
高阶函数
特点:函数可以作为参数被传递;函数可以作为返回值输出。
实现AOP:将核心业务逻辑模块无关的功能抽离出来,抽离完成后再通过“动态织入”的方式渗入业务逻辑模块中。