likes
comments
collection
share

前端设计模式之原型模式(七)

作者站长头像
站长
· 阅读数 45
  • 定义:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象
  • 传统的原型模式就是克隆,但这在 JS 中并不常用,但 JS 本身是基于原型的,原型和原型链是非常重要的

前端设计模式之原型模式(七)

class CloneDemo {
  name: string = "clone demo";

  clone(): CloneDemo {
    return new CloneDemo();
  }
}

原型和原型链基础知识

函数和显示原型 prototype

JS 中所有函数都有一个 prototype 属性。例如

  • Object.prototype
  • Array.prototype

自定义的函数也有

// 1. 注意第一参数 this ;2. 暂且用 any 表示,实际会用 class
function Foo(this: any, name: string, age: number) {
  this.name = name;
  this.age = age;
}

Foo.prototype.getName = function () {
  return this.name;
};

Foo.prototype.sayHi = function () {
  alert("hi");
};

对象和隐式原型 __proto__

引用类型

  • JS 所有的引用类型对象都是通过函数创建的,都有 __proto__ ,指向其构造函数的 prototype

前端设计模式之原型模式(七)

const obj = {}; // 相当于 new Object()
obj.__proto__ === Object.prototype;

const arr = []; // 相当于 new Array()
arr.__proto__ === Array.prototype;

const f1 = new Foo("张三", 20);
f1.__proto__ === Foo.prototype;

const f2 = new Foo("李四", 21);
f2.__proto__ === Foo.prototype;

访问对象属性或 API 时,首先查找自身属性,然后查找它的 __proto__

f1.name
f1.getName()

值类型

  • 值类型没有 __proto__ ,但它依然可访问对应 API 。因为 JS 会先将它包装为引用类型,然后执行 API
const str = "abc";
str.slice(0, 1); // 调用 String.prototype.string

原型链

  • 一个对象的 __proto__ 指向它构造函数的 prototype
  • prototype 本身也是一个对象,也会指向它构造函数的 prototype ,于是就形成了原型链

前端设计模式之原型模式(七)

class 是函数的语法糖

  • class 和函数一样,也是基于原型实现的
class Foo {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  getName() {
    return this.name;
  }
  sayHi() {
    alert("hi");
  }
}

Foo.prototype;

const f1 = new Foo("张三", 20);
f1.__proto__ = Foo.prototype;

继承

class People {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  eat() {
    alert(`${this.name} eat something`);
  }
  speak() {
    alert(`My name is ${this.name}, age ${this.age}`);
  }
}

class Student extends People {
  school: string;
  constructor(name: string, age: number, school: string) {
    super(name, age);
    this.school = school;
  }
  study() {
    alert(`${this.name} study`);
  }
}

const s1 = new Student("aa", 20, "xx");
s1.study();
s1.eat();

前端设计模式之原型模式(七)

场景

  • 最符合原型模式的应用场景就是 Object.create ,它可以指定原型
const obj1 = {};
obj1.__proto__; // [Object: null prototype] {}

const obj2 = Object.create({ x: 100 });
obj2.__proto__; // { x: 100 }

对象属性描述符

  • 用于描述对象属性的一些特性

获取属性描述符

const obj = { x: 100 };
Object.getOwnPropertyDescriptor(obj, "x"); // { value: 100, writable: true, enumerable: true, configurable: true }
Object.getOwnPropertyDescriptors(obj) // { x: { value: 100, writable: true, enumerable: true, configurable: true } }

设置属性描述符

Object.defineProperty(obj, "y", {
  value: 200,
  writable: false,
  // 其他...

  // PS: 还可以定义 get set
});
  • 使用 Object.defineProperty 定义新属性,属性描述符会默认为 false { configurable: false, enumerable: false, writable: false }
  • 而用 { x: 100 } 字面量形式定义属性,属性描述符默认为 true

解释各个描述符

value

  • 属性值:值类型、引用类型、函数等
const obj = { x: 100 };
Object.defineProperty(obj, "x", {
  value: 101,
});

如果没有 value ,则打印 obj 就看不到属性

const obj = {};
let x = 100;
Object.defineProperty(obj, "x", {
  get() {
    return x;
  },
  set(newValue) {
    x = newValue;
  },
});
console.log(obj); // {}
console.log(obj.x); // 100

configurable

  • 是否可以通过 delete 删除并重新定义
  • 是否可以修改其他属性描述符配置
  • 是否可以修改 get set
const obj = { x: 100 };
Object.defineProperty(obj, "y", {
  value: 200,
  configurable: false, // false
});
Object.defineProperty(obj, "z", {
  value: 300,
  configurable: true,
});

delete obj.y; // 不成功

// 重修修改 y 报错(而修改 z 就不报错)
Object.defineProperty(obj, "y", {
  value: 210,
});

writable

  • 属性是否可以被修改
const obj = { x: 100 };
Object.defineProperty(obj, "x", {
  writable: false,
});
obj.x = 101;
obj.x; // 依然是 10

enumerable

  • 是否可以通过 for...in 遍历
const obj = { x: 100 };
Object.defineProperty(obj, "y", {
  value: 200,
  enumerable: false, // false
});
Object.defineProperty(obj, "z", {
  value: 300,
  enumerable: true,
});

for (const key in obj) {
  console.log(key); // 'x' 'z'
}

// console.log('y' in obj) // true —— 只能限制 for...in 无法限制 in

原型的属性描述符

  • 在 N 年之前,使用 for...in 遍历对象时,需要用 hasOwnProperty 剔出原型属性,否则会把原型属性过滤出来
const obj = { x: 100 };
for (const key in obj) {
  if (obj.hasOwnProperty(key)) {
    console.log(key);
  }
}

现在不用了,都是通过 enumerable 来判断

Object.getOwnPropertyDescriptor(obj.__proto__, "toString");

如果修改原型属性的 enumerable ,也是可以通过 for...in 遍历出来的

const obj = { x: 100 };
Object.defineProperty(obj.__proto__, "toString", {
  // 实际项目最好不要修改原型属性的描述符!
  enumerable: true,
});
for (const key in obj) {
  console.log(key);
}

// obj.hasOwnProperty('toString') // 依然是 false ,和 enumerable 没关系

还有,有些地方会修改函数的 prototype ,但却忽略了 constructor 的属性描述符

function Foo() {}
Foo.prototype = {
  constructor: Foo, // 需要设置 { enumerable: false } ,否则它的实例 for...in 会有 constructor
  fn1() {},
  fn2() {},
};

Symbol 类型

  • Object 的 symbol 属性,即便 enumerable: true 也无法通过 for...in 遍历
const b = Symbol("b");
const obj = { a: 100, [b]: 200 };
for (const key in obj) {
  console.log(key);
}

// Object.getOwnPropertyDescriptor(obj, b) // enumerable: true

获取 Symbol 属性,可使用 getOwnPropertySymbolsReflect.ownKeys

Object.keys(obj); // ['a']
Object.getOwnPropertySymbols(obj); // [ b ]
Reflect.ownKeys(obj); // ['a', b]
转载自:https://juejin.cn/post/7197740114252185637
评论
请登录