yizihan / blog

Record
1 stars 0 forks source link

设计模式 - 下 #14

Open yizihan opened 6 years ago

yizihan commented 6 years ago

代理模式 - Proxy Pattern

当一个昂贵的对象应被实例化时,Proxy模式可以帮助我们对其进行控制,提供高级的方法来引用对象,或修改对象,让它在特定的上下文中以一种特殊方式发挥作用。

jQuery.proxy() 接受函数作为参数,并返回一个始终具有特定上下文的新对象。这确保了函数中this的值是我们所需要的值。

实例需求:按钮点击之后延迟添加类名

// 失败范例
$('#btn').on('click', function() {
    console.log(this);      // <button class="btn" id="btn">按钮</button>
    setTimeout(functioin() {
        console.log(this);  // Window  延迟执行函数中的this都指向Window
        $(this).addClass('btn-success');    // 失败,因为this指向了Window
    }, 1000);
});

// 成功范例
$('#btn').on('click', function(){
    setTimeout($.proxy(function() {
        $(this).addClass('btn-success');
    }, this), 1000);    // 调用环境中的this指向本身,利用proxy特性,将此this作为$.proxy内执行上下文
});

ES6 - Proxy

Proxy用于修改某些操作的默认行为,等同于在语言层面做出修改,属于一种“元编程”,即对编程语言进行编程。

Proxy是在目标对象架设一层“拦截”,外界对该对象的访问,都必须先通过这层“拦截”,而这层“拦截”可以对外界的访问进行过滤和改写。

var proxy = new Proxy(target, handler);
// new Proxy() 生成一个Proxy实例
// target 表示所要拦截的目标对象
// handler 一个对象,表示用来定制拦截行为
var person = {
    name: 'yizihan'
}
var proxy = new Proxy(person, {
    // 重写 (.)方法
    get: function (target, property) {
        if(property in target) {
            return target[property];
        } else {
            return new ReferenceError("Property '"+ property + "' does not exist.")
        }
    }
})
var obj = Object.create(proxy);
obj.name;   // yizihan
obj.age;    // ReferenceError: Property 'age' does not exist.

通过new Proxy()代理目标对象,目标对象内部的this会指向Proxy代理。


混入 - Mixin Pattern

在一个对象之中混入另外一个对象的方法。

首先设定Mixin包含的方法属性,然后利用原型链继承,从Mixin中继承到子类。

传统继承方式

function Person (firstname, lastname) {
    this.firstname = firstname;
    this.lastname = lastname;
}
function SuperHero (firstname, lastname, skill) {
    // 继承Person自身属性
    Person.call(this, firstname, lastname);
    // => this.firstname = firstname;
    // => this.lastname = lastname;

    this.skill = skill;
}

// 继承Person原型上的属性
SuperHero.prototype = Object.create(Person.prototype);
SuperHero.prototype.constructor = SuperHero;

var superman = new SuperHero('Clark', 'Kent', ['flight'])
console.log(superman)

Mixin方式

var Car = function(setting) {
  this.model = setting.model || 'no model provided';
  this.color = setting.color || 'no color provided';
};

var Mixin = function() {};
// 测试发现将所有方法都放在一个对象中是不可行的,必须单独列出来 ???
// Mixin.prorotype = {
//   driveForward: function () {
//     console.log('drive forward');
//   },
//   driveBackward: function () {
//     console.log('drive backward');
//   }
// };
Mixin.prototype.driveForward = function () {
    console.log('drive forward');
}
Mixin.prototype.driveBackward = function () {
    console.log('drive backward');
}

// 设定继承方法函数
function inherit(receivingClass, givingClass) {
  // 如果有第三个参数,即指定需要继承的方法
  if (arguments[2]) {
    for (var i = 2; i < arguments.length; i++) {
      receivingClass.prototype[arguments[i]] = givingClass.prototype[arguments[i]];
    }
  } else {
    for (var methodName in givingClass.prototype) {
      // 判断本身是否存在该方法 
      if (!receivingClass.prototype[methodName]) {
        receivingClass.prototype[methodName] = givingClass.prototype[methodName];
      }
    }
  }
}
// 实现原型链上的方法继承
inherit(Car, Mixin, 'driveForward');
var myCar = new Car({
  model: 'Civic',
  color: 'black'
});
console.log(myCar.__proto__);   // {driveForward: ƒ, constructor: ƒ}
myCar.driveForward();                       // drive forward

Mixin 有助于减少系统中的重复功能代码及增加函数的复用。

ES6方式

export function mixins(...list) {
    return function(target) {
        Object.assign(target.prototype, ...list);
    }
}

import { mixins } from './mixins';
const Foo = {
    foo() { console.log('foo'); }
}
// 使用ES6装饰器
@mixins(Foo)
class MyClass {}

let obj = new Myclass();
obj.foo();      // foo

装饰者模式 - Decorator Pattern

Decorator 提供了将行为动态添加至系统的现有类的能力

var MacbookPro = function() {};
MacbookPro.prototype.getPrice = function () {
    return 900.00;
}
// 实例化
var myMacbookPro = new MacbookPro();

// 装饰者构造函数 接受一个实例对象
var MacbookProDecorator = function (macbook) {
    this.macbook = macbook; 
}
// 重写此实例对象的getPrice(),但不会破环原来的方法
MacbookProDecorator.prototype.getPrice = function() {
    return this.macbook.getPrice() + 100.00;
}

// 装饰实例对象
myMacbookPro = new MacbookProDecorator(myMacbookPro);
var price = myMacbookPro.getPrice();
console.log(price);         // 1000

优点:对象可以被新行为包装,然后可以继续被使用,而不必担心被修改的基本对象。这种模式使我们不必依靠大量的子类来获得同样的实现。

ES6 - Decorator

修饰器是一个对类进行处理的函数,修饰器函数的第一个参数,就是所要修饰的目标类。

// 定义修饰器
function testable(target) {
    target.isTestable = true;
}
// 调用修饰器
@testabel
class MyTestableClass { ... }
// 修饰器函数生效
MyTestableClass.isTestable;         // true

带参数的修饰器

// 传递参数
function testable(isTestable) {
    // 处理目标类
    return function(target) {
        target.isTestable = isTestable;
    }
}

@testable(true)
class MyTestableClass() {}
MyTestableClass.isTestable;     // true

@testable(false)
class MyClass() {};
MyClass.isTestable;             // false

修饰器对类的行为的改变,是在代码编译时发生的,而不是代码运行时。修饰器本质就是编译时执行的函数。

方法的修饰

修饰器不仅可以修饰类,还可以修饰类的属性。

class Person {
    // 修饰器作用于方法
    @readonly
    name() { return `${this.first} ${this.last}` }
}
// 定义修饰器
function readonly(target, name, descriptor) {
    descriptor.writable = false;
    return descriptor;
}
// @readonly 发生的调用关系等同于下面
readonly(Person.prototype, 'name', descriptor)
Object.defineProperty(Person.prototype, 'name', descriptor);