javascript 设计模式 ( 读书笔记 )

javascript 设计模式

电子书链接@ 余杭子曰

用对象收编变量,防止变量覆盖和变量污染

let checkObject = {
  checkEmail: function () {
    console.log("邮箱校验");
  },
  checkPhone: function () {
    console.log("手机号校验");
  },
  checkPasswork: function () {
    console.log("密码校验");
  },
};

checkObject.checkEmail();
// 缺点,别人如果想要调用该方法可能会影响原来的代码

let copyObject = checkObject;
copyObject.checkEmail();

对象的另一种形式 函数

let checkObjectFn = function () {};
checkObjectFn.checkEmail = function () {
  console.log("邮箱校验");
};
checkObjectFn.checkPhone = function () {
  console.log("手机校验");
};
checkObjectFn.checkPasswork = function () {
  console.log("密码校验");
};

// 调用
checkObjectFn.checkEmail();

真假对象

可以使用函数 返回一个对象,这样每次声明的时候都会生成一个 新对象,不会影响之前的对象方法

function checkObjectFn() {
  return {
    checkEmail: function () {
      console.log("邮箱校验");
    },
    checkPhone: function () {
      console.log("手机号校验");
    },
    checkPasswork: function () {
      console.log("密码校验");
    },
  };
}

// 调用

checkObjectFn().checkEmail();

类也可以

函数也可以写成类的形式
下面写法缺点: 每次示例对象时,都会复制一边 this 上的属性和方法. 会造成不必要的性能浪费.

function checkObjectFn() {
  this.checkEmail = function () {
    console.log("邮箱检验");
  };
  this.checkPhone = function () {
    console.log("手机检验");
  };
  this.checkPassword = function () {
    console.log("密码检验");
  };
}

let a = new checkObjectFn();
a.checkEmail();

检测类

可以将公共方法同意挂载在 原型对象上. 这样示例对象就可以通过 __proto__ 一直找,直到找到为止

let CheckObjectFn = function () {};
CheckObjectFn.prototype.checkEmail = function () {
  console.log("邮箱验证");
};

CheckObjectFn.prototype.checkPhone = function () {
  console.log("手机验证");
};

CheckObjectFn.prototype.checkPassword = function () {
  console.log("密码验证");
};

let a = new CheckObjectFn();
a.checkEmail();

// 简化写法
let CheckObjectFn = function () {};
CheckObjectFn.prototype = {
  checkEmail: function () {
    console.log("邮箱验证");
  },
  checkPhone: function () {
    console.log("手机验证");
  },
  checkPassword: function () {
    console.log("密码验证");
  },
};

let a = new CheckObjectFn();
a.checkEmail();
a.checkPhone();
a.checkPassword();

注意 :这两种 书写方式不能混合使用,要不可能出现方法覆盖,方法找不到的情况

链式调用

链式调用时 只需要在函数调用的同时,将当前 this返回出去即可

let CheckObjectFn = function () {};
CheckObjectFn.prototype = {
  checkEmail: function () {
    console.log("邮箱验证");
    return this;
  },
  checkPhone: function () {
    console.log("手机验证");
    return this;
  },
  checkPassword: function () {
    console.log("密码验证");
    return this;
  },
};

let a = new CheckObjectFn();

a.checkEmail().checkPassword().checkPhone();

函数高级用法

我们可以在不声明变量的情况下,直接在 Function 函数的原型对象上面挂方法

Function.prototype.checkEmail = function () {
  console.log("邮箱验证");
};

Function.prototype.checkPhone = function () {
  console.log("手机验证");
};

Function.prototype.checkPassword = function () {
  console.log("密码验证");
};

let a = function () {};
a.checkEmail();

注意:虽然 javascript 支持上面写法,但是为了代码的可维护性 最好不要这样书写代码.可以使用下述代码替代

Function.prototype.addMethods = function (funcname, fn) {
  this[funcname] = fn;
};

let a = function () {}; // 等同于 let a  = new Function()
a.addMethods("checkEmail", function () {
  console.log("邮箱验证");
});

a.addMethods("checkPhone", function () {
  console.log("手机验证");
});
a.addMethods("checkPassword", function () {
  console.log("密码验证");
});

a.checkEmail();
a.checkPhone();
a.checkPassword();

// 链式调用
a.addMethods("checkEmail", function () {
  console.log("邮箱验证");
  return this;
});

a.addMethods("checkPhone", function () {
  console.log("手机验证");
  return this;
});
a.addMethods("checkPassword", function () {
  console.log("密码验证");
  return this;
});
a.checkEmail().checkPhone().checkPassword();

创建对象的安全模式

  1. new 关键字的作用是当前对象的 this 不停赋值. 当构造函数没有被 被实例(new)时,就会被当作一个函数执行.
  2. 当函数执行时 函数的作用域是全局,全局的 this 指向 window .
//  这种方法 必须通过 new 关键字进行实例对象.
function Books(price, name, category) {
  this.price = price;
  this.name = name;
  this.category = category;
}
let book1 = Books(199, "javascript百科全书", "社科");
console.log(book1); // undefined
console.log(window.price); // 199

let book2 = new Books(66, "春秋", "历史");

console.log(book2); // Books { price: 66, name: '春秋', category: '历史' }
console.log(book2.name); // 春秋

// 函数改造  --  创建对象安全模式
function Books(price, name, category) {
  if (this instanceof Books) {
    this.price = price;
    this.name = name;
    this.category = category;
  } else {
    return new Books(price, name, category);
  }
}

let book1 = Books(199, "javascript百科全书", "社科");
console.log(book1.name); // javascript百科全书

继承方式

类式继承

  1. 类式继承的原理就是 把一个构造函数的实例对象赋值给另一个构造函数的 prototype (原型)对象.
  • 例子: B 继承 A : B.prototype = new A()
function Father() {
  this.fatherValue = "father";
  this.getFatherValue = function () {
    return this.fatherValue;
  };
}

let fa1 = new Father();
console.log(fa1.fatherValue); // father

function Children() {
  this.childrenValue = "chlidren";
  this.getChildrenValue = function () {
    return this.childrenValue;
  };
}
let ch1 = new Children();
console.log(ch1.childrenValue); // children
console.log(ch1.fatherValue); // undefined

Children.prototype = new Father();
let ch2 = new Children();
console.log(ch2.childrenValue); // children
console.log(ch2.fatherValue); // father

可以使用 instanceof 判断 前面对象是否是后面对象的实例.

  • 例子: 判断 b 是否是 a 的实例 : b instanceof a 返回值是 boolean 类型.
//  instanceof

console.log(ch2 instanceof Father); // true
console.log(ch2 instanceof Children); // true
console.log(Children instanceof Father); // false
console.log(Children.prototype instanceof Father); // tr

注意: 所有对象的实例都是 Object 的实例

console.log(ch2 instanceof Object); // true

类式继承的缺点

  1. 如果子类修改了通过 类式继承的属性, 那么父类中的属性也会受到影响.
function Father() {
  this.fatherArr = [199, "javascript设计模式", "编程"];
}

function Chlidren() {
  this.ChlidrenArr = [66, "春秋", "历史"];
}

Chlidren.prototype = new Father();
let ch1 = new Chlidren();
console.log(ch1.fatherArr); // [ 199, 'javascript设计模式', '编程' ]

ch1.fatherArr.push("新增数据");

console.log(ch1.fatherArr); // [ 199, 'javascript设计模式', '编程', '新增数据' ]

构造函数继承

原理: 在子类中调用一次父类的构造函数,并将子类的 this 传给父类.

  1. 构造函数继承可以解决 类式继承的缺点. 但是 构造函数继承会不断的复制当前对象的 this 违背了代码复用的初心.
function Father(id) {
  this.fatherArr = [199, "javascript设计模式", "编程"];
  this.id = id;
}

function Chlidren(id) {
  Father.call(this, id);
}

let ch1 = new Chlidren(66);
let ch2 = new Chlidren(88);
ch1.fatherArr.push("新增数据");
console.log(ch1.fatherArr); // [ 199, 'javascript设计模式', '编程', '新增数据' ]
console.log(ch1.id); //  66

console.log(ch2.fatherArr); // [ 199, 'javascript设计模式', '编程' ]
console.log(ch2.id); // 88

组合继承

类式继承函数继承的优点结合起来的继承方式 就是组合继承.

缺点: 在使用构造函数继承时,调用了一次父类的构造函数; 在实现子类原型的类式继承时,又调用了一次父类的构造函数.因此父类构造函数调用了两次.

function Father(name) {
  this.fatherArr = [199, "javascript设计模式", "编程"];
  this.name = name;
}

function Chlidren(name) {
  Father.call(this, name);
  this.ChlidrenArr = [66, "春秋", "历史"];
}

let c1 = new Chlidren("蜻蜓队长");
c1.fatherArr.push("三国演义");
console.log(c1.name); // '蜻蜓队长'
console.log(c1.fatherArr); // [ 199, 'javascript设计模式', '编程', '三国演义' ]

let c2 = new Chlidren("铁甲小宝");

console.log(c2.fatherArr); // [ 199, 'javascript设计模式', '编程' ]
console.log(c2.name); // '铁甲小宝'

原型式继承

原型式继承 类似于 类式继承, 缺点也是相同的.

function inheritObject(o) {
  // 声明一个 函数过度对象
  function F() {}
  //   过渡对象的原型 继承父类对象
  F.prototype = o;
  //   返回继承了父类对象的实例
  return new F();
}

寄生式继承

原理: 就是对 原型继承进行了二次封装, 在二次封装的同时对继承的对象进行改造,添加新的属性和方法.

function inheritObject(o) {
  // 声明一个 函数过度对象
  function F() {}
  //   过渡对象的原型 继承父类对象
  F.prototype = o;
  //   返回继承了父类对象的实例
  return new F();
}

function createObject(o) {
  // 通过原型继承 创建对象
  let obj = new inheritObject(o);
  obj.getName = function () {
    return this.name;
  };
  return obj;
}

let sunObj = {
  name: "孙悟空",
  age: 18,
};

let copyobj = createObject(sunObj);
copyobj.name = "copy孙悟空";
console.log(copyobj.name); // copy 孙悟空
console.log(copyobj.getName()); // copy 孙悟空
console.log(sunObj.name); // 孙悟空

寄生组合式继承

原理: 寄生组合继承 和 构造函数继承的 结合,就叫做寄生组合式继承

function inheritObject(o) {
  // 声明一个 函数过度对象
  function F() {}
  //   过渡对象的原型 继承父类对象
  F.prototype = o;
  //   返回继承了父类对象的实例
  return new F();
}

function inheritPrototype(fatherClass, childrenClass) {
  // 复制一份 父类原型 储存在一个变量中
  let p = fatherClass;
  // 修改 因为继承从而导致 子类constructor 属性被修改
  p.constructor = childrenClass;
  childrenClass.prototype = p;
}

function Sun(name) {
  this.name = name;
  this.skills = ["七十二变", "大闹天宫"];
}

function Zhu(name, time) {
  Sun.call(this, name);
  this.time = time;
}

inheritPrototype(Sun, Zhu);

let instance1 = new Zhu("猪八戒", 2010);
let instance2 = new Zhu("孙悟空", 1999);
instance2.skills.push("偷看嫦娥洗澡");
console.log(instance1.skills);
console.log(instance2.skills);

多继承

继承多个对象的属性和方法

Object.prototype.mix = function () {
  let i = 0,
    len = arguments.length, // 获取传入了多少个对象
    arg; // 缓存参数对象

  for (; i < len; i++) {
    // 缓存当前对象
    arg = arguments[i];
    // 遍历被继承的对象属性和方法
    for (let property in arg) {
      this[property] = arg[property];
    }
  }
};
let book1 = {
  name: "三国演义",
  type: "历史",
};

let book2 = {
  people: "刘关张",
  content: "桃园三结义",
};
let otherBook = {};
otherBook.mix(book1, book2);
console.log(otherBook); // { name: '三国演义',type: '历史',mix: [λ],people: '刘关张',content: '桃园三结义' }

多态

原理: 同一个方法 多种调用方式的行为,就叫做 多态

function add() {
  let len = arguments.length;
  switch (len) {
    case 0:
      return 10;
    case 1:
      return 10 + arguments[len - 1];
    case 2:
      return arguments[0] + arguments[1];
  }
}

console.log(add());
console.log(add(6));
console.log(add(6, 6));

// 方式二  : 类形式
// 类形式

function Add() {
  function zero() {
    return 10;
  }

  function one(sum1) {
    return 10 + sum1;
  }
  function two(sum1, sum2) {
    return sum1 + sum2;
  }
  this.add = function () {
    let len = arguments.length;
    switch (len) {
      case 0:
        return zero();
      case 1:
        return one(arguments[0]);
      case 2:
        return two(arguments[0], arguments[1]);
    }
  };
}
let a = new Add();
console.log(a.add());
console.log(a.add(6));
console.log(a.add(6, 6));

设计模式

工厂模式

function Basketball() {
  this.info = "篮球运动";
  this.getSize = function () {
    return "篮球很大";
  };
}
Basketball.prototype.getNumber = function () {
  return "篮球队由五个人组成";
};

function Football() {
  this.info = "足球运动";
  this.getSize = function () {
    return "足球式是正常大小";
  };
}
Football.prototype.getNumber = function () {
  return "足球由是一个人组成";
};

// 运动工厂

function SportsFactory(category) {
  switch (category) {
    case "NBA":
      return new Basketball();
    case "WORLDCUP":
      return new Football();
  }
}

let basketball = SportsFactory("NBA");
console.log(basketball); // Basketball { info: '篮球运动', getSize: [λ] }
console.log(basketball.getNumber()); // 篮球队由五个人组成
console.log(basketball.getSize()); // 篮球很大

工厂模式变式

// 工厂模式变式

function Subject(type, content) {
  if (this instanceof Subject) {
    let s = new this[type](content);
    return s;
  } else {
    return new Subject(type, content);
  }
}

Subject.prototype = {
  Java: function (content) {
    this.content = content;
    console.log("java课程" + this.content);
  },
  Css: function (content) {
    this.content = content;
    console.log("css课程" + this.content);
  },
  Javascript: function (content) {
    this.content = content;
    console.log("js课程" + this.content);
  },
};

let subData = [
  { type: "Java", content: "白菜价" },
  { type: "Css", content: "进阶课程" },
  { type: "Javascript", content: "设计模式:666" },
];

for (let i = 0; i < subData.length; i++) {
  console.log(Subject(subData[i].type, subData[i].content));
}

抽象类

定义: 抽象类是一种声明,但是不能使用的类,使用时会报错.
作用: 定义一个产品簇,并声明一些必备的方法,如果子类中没有重写这些方法时,就报错.

我们可以在 类中手动抛出错误去 模仿抽象类.

function Car() {
  this.getPrice = function () {
    return new Error("抽象方法,不能调用");
  };
}

let c = new Car();
console.log(c.getPrice()); // [Error: 抽象方法,不能调用]

建造者模式

工厂模式: 创建出来的是一个对象,更注重创建的结果.
建造者模式: 可以得到创建的结果,同时也会参与到具体过程. 更类似与一个符合对象.

// 创建一个神仙类
function Immortal(param) {
  // 判断是否传入参数,如果有参数就将 skill赋值给实例对象的skill属性
  this.skill = (param && param.skill) || "保密";
  this.hobby = (param && param.hobby) || "保密";
}

// 实例化姓名类
function Named(name) {
  let that = this;
  //   构造器
  // 构造函数解析姓名类的 姓 和 名
  (function (that, name) {
    that.wholename = name;
    console.log(name);
    if (name && name.indexOf(" ") > -1) {
      that.firstName = that.wholename.slice(0, name.indexOf(" "));
      that.secondName = that.wholename.slice(name.indexOf(" "));
    }
  })(that, name);
}

// 实例化工作类

function Worked(work) {
  let that = this;
  // 构造器
  // 构造函数 解析工作种类
  (function (that, work) {
    switch (work) {
      case "Monkey":
        that.work = "养马的";
        that.describe = "曾在天庭养了五百匹马.";
        break;
      case "Pig":
        that.work = "牵马的";
        that.describe = "曾在天庭偷看嫦娥洗澡.";
        break;
      case "fish":
        that.work = "拉行李的";
        that.describe = "曾在天庭给玉帝倒酒.";
        break;
      case "monk":
        that.work = "超度的";
        that.describe = "曾是如来的最强小弟";
        break;
    }
  })(that, work);
}
Worked.prototype.changWork = function (work) {
  this.work = work;
};
Worked.prototype.changeDescribe = function (describe) {
  this.describe = describe;
};
// 创建一个建造者

let Person = function (data) {
  let { name, work } = data;
  let _person = new Immortal({ skill: "七十二变" });
  _person.work = new Worked(work);
  _person.name = new Named(name);
  return _person;
};

let monkey = new Person({ name: "孙 悟空", work: "Monkey" });
console.log(monkey.skill); // 七十二变
console.log(monkey.name.firstName); // 孙
console.log(monkey.name.secondName); // 悟空
console.log(monkey.work.work); // 养马的
console.log(monkey.work.describe); // 曾在天庭养了五百匹马.
monkey.work.changWork("齐天大圣");
monkey.work.changeDescribe("曾经大闹天宫,调戏七仙女.");
console.log(monkey.work.work); // 齐天大圣
console.log(monkey.work.describe); // 曾经大闹天宫,调戏七仙女.

原型模式

下面代码是 使用构造函数的形式实现的继承.
优点: 不会影响父类原型上的属性和方法
缺点: 每次实例的时候 都会调用两次父类实例,会造成不必要的性能消耗

function Loop(imgs, container) {
  this.imgs = imgs;
  this.container = container;
  this.changeImgs = function () {
    return "父类-changeImgs";
  };
  this.changeContainer = function () {
    return "父类-changeContainer";
  };
}

function Lefttoright(imgs, container) {
  Loop.call(this, imgs, container);
}
let lf = new Loop([1, 2], "coco");
let lr = new Lefttoright([3, 4], "jack");

console.log(lf.imgs); // [1,2]
console.log(lr.imgs); // [3,4]

console.log(lf.changeImgs()); // 父类-changeImgs
console.log(lr.changeImgs()); // 父类-changeImgs
lr.changeImgs = function () {
  return "子类-changeImgs";
};
console.log(lf.changeImgs()); // 父类-changeImgs
console.log(lr.changeImgs()); // 子类-changeImgs

改造

我们可以将比较消耗资源的方法放在 原型中,这样就可以避免一些不必要的性能消耗

function Loop(imgs, container) {
  this.imgs = imgs;
  this.container = container;
}

//  将比较消耗资源的方法 放在原型中
Loop.prototype = {
  changeImgs: function () {
    return "父类-changeImgs";
  },
  changeContainer: function () {
    return "父类-changeContainer";
  },
};

function Lefttoright(imgs, container) {
  Loop.call(this, imgs, container);
}
Lefttoright.prototype = new Loop();
let lf = new Loop([1, 2], "coco");
let lr = new Lefttoright([3, 4], "jack");

console.log(lf.imgs); // [1,2]
console.log(lr.imgs); // [3,4]

console.log(lf.changeImgs());
console.log(lr.changeImgs());
lr.changeImgs = function () {
  return "子类-changeImgs";
};
console.log(lf.changeImgs());
console.log(lr.changeImgs());

单例模式

又称单体模式,是一种只允许被实例一次的对象类
其实就是提供一个命名空间,避免变量冲突的问题.不同的程序员可能会声明相同的变量,从而导致程序出现问题.

// 程序猿1
let monkeyOne = {
  name: "孙悟空",
  age: 18,
};
// 程序员2
let monkeyTwo = {
  name: "六耳猕猴",
  age: 21,
};

结构型设计模式

外观模式

外观模式是对接口方法的外层封装,以供上层代码使用.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>study-practice</title>
  </head>
  <body>
    <div class="box">123</div>
    <script>
      let domLiborary = {
        dom: function (target) {
          // 获取 dom 元素
          document.querySelector(target);
        },
        css: function (target, propertyKey, value) {
          // 设置元素 css样式
          document.querySelector(target).style[propertyKey] = value;
        },
        attr: function (target, propertyKey, value) {
          // 获取元素属性
          document.querySelector(target)[propertyKey] = value;
        },
        on: function (target, type, fn) {
          document.querySelector(target)["on" + type] = fn;
        },
      };
      domLiborary.css(".box", "width", "300px");
      domLiborary.css(".box", "backgroundColor", "tomato");
      domLiborary.on(".box", "click", function () {
        alert("domliborary -- 点击事件");
      });
    </script>
  </body>
</html>

适配器模式

适配器模式: 就是将一个类的接口转化为另一个类的接口(属性和方法),从而解决类(对象)接口不兼容的问题. 就比如 jquery.

装饰者模式

装饰者模式:可以在不了解原有功能的基础上,进行再拓展功能并不影响原有功能.

function decorator(target, fn) {
  // 获取目标元素
  let targetDom = document.querySelector(target);
  // 判断目标元素是否拥有点击事件
  if (typeof targetDom.onclick === "function") {
    // 缓存老的点击事件
    let oldClick = targetDom.onclick;
    // 重写点击事件
    targetDom.onclick = function () {
      oldClick();
      fn();
    };
  } else {
    // 如果之前没有点击事件 直接调用
    targetDom.onclick = fn();
  }
}

链式调用

// 链式调用:调用函数时,我们可以将当前对象返回(return),然后我们就可以继续调用当前对象

function A() {}

A.prototype = {
  length: 2,
  size: function () {
    return this.length;
  },
};
let a = new A();
console.log(a.size());
// console.log(A().size()); // Cannot read properties of undefined (reading 'size') 因为A没有返回值所以会报错

// function B() {
//   return C;
// }

// let C = (B.prototype = {
//   length: 2,
//   size: function () {
//     return this.length;
//   },
// });

// console.log(B().size());

// 减少变量声明的做法 : 将对象赋值给 B的一个属性并返回,这样就可以减少变量的声明
function B() {
  return B.fn;
}

B.fn = B.prototype = {
  length: 2,
  size: function () {
    return this.length;
  },
};

console.log(B().size());


状态模式

参考文章@Javascript 设计模式

  • 情景

    超级玛丽实现单个动作,切换动作,组合动作

  • 思路

    声明一个超级玛丽类(构造函数)
    定义一个状态对象 用于不同动作的映射。
    定义一个动作对象 用于动作的改变和动作的执行。

function MarryState() {
  let _currentState = {};
  let states = {
    // 基础状态动作
    jump() {
      console.log("跳");
      // return "跳";
    },
    shoot() {
      console.log("射");
      // return "射";
    },
    squat() {
      console.log("蹲");
      // return "蹲";
    },
    move() {
      console.log("移动");
      // return "移动";
    },
  };
  let Actions = {
    changeState() {
      let args = arguments;
      _currentState = {};
      if (args.length) {
        for (let i = 0; i < args.length; i++) {
          _currentState[args[i]] = true;
        }
        return this;
      }
    },
    goes() {
      for (key in _currentState) {
        states[key] && states[key]();
      }
      return this;
    },
  };
  return {
    change: Actions.changeState,
    goes: Actions.goes,
  };
}

let marry = new MarryState();

marry
  .change("move")
  .goes()
  .change("jump")
  .goes()
  .goes()
  .change("shoot")
  .goes()
  .change("squat")
  .goes()
  .change("jump")
  .goes();

// 打印结果   移动  跳 跳 射 蹲 跳
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值