Js 面向对象

Posted by Youzi Blog on March 12, 2019

原生 JS 面向对象

创建对象

  • 工厂模式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function constructor(args) {
  // 1.原料
  let obj = {};
  // 2.加工
  obj.args = args;
  obj.methods = function () {
    // do something...
  };
  // 3.出厂
  return obj;
  /*
              4.缺点:创建了两个子对象,不能区别这两个对象的对象类型
              例子:无法判断 实例 instanceof 构造
          */
}
let instance = constructor(args);
  • 构造函数模式:
1
2
3
4
5
6
7
8
9
10
11
function constructor(args) {
  this.args = args;
  this.methods = function () {
    // do something...
  };
  /*
                优点:可以把实例标识为特定的对象类型,比工厂模式好
                缺点:每个方法都得在实例对象上重新创建
                */
}
let instance = new constructor(args);
  • 原型模式:
1
2
3
4
5
6
7
8
9
function constructor() {}
// 优点:可用isPrototypeOf()来判断构造的原型对象是实例的原型;
// 例:true == constructor.prototype.isPrototypeOf(instance)
// 缺点:实例共享所有属性和方法
constructor.prototype.args = '?';
constructor.prototype.methods = function () {
  // ...
};
let instance = new constructor();
  • 混合模式:
1
2
3
4
5
6
7
function constructor(args) {
  this.args = args;
}
constructor.prototype.methods = function () {
  // ...
};
let instance = new constructor();

这种模式是最常见的构造对象的方式了,结合了构造函数的优点和原型模式的有点,属性作为每个实例的私有,方法作为实例的共享方法。

包装对象

7 种基本的数据类型中,除去 Symbol(没有构造函数),Object、null、undefined 分为一类,String、Number、Boolean 分为一类;后面那类如果使用字面量方法构造,它们就是各自对应的基本数据类型;如果用构造函数方法创建,它们就是 Object 类型。

出现上述这种情况,是因为后三种基本类型都有自己对应的包装对象。看下面一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let str = '???'; // 字面量方法创建字符串
typeof str; // string
str.concat('!!!');
/*
    基本的String类型是没有其他属性和方法的,
    所以JS会将基本类型转换为字符串对象,
    看到一种说法是会先执行tmpStr = new String(str),这个临时对象继承了String的属性和方法,
    执行了这些属性和方法后,再将这个临时对象销毁
    */
str.xxx = '?';
console.log(str.xxx); // undefined
/* String类是没有xxx这个属性的,这里给str添加属性实际上是给临时对象添加的,而临时对象随即销毁,所以第二行打印的时候找不到这个属性 */
let str2 = new String('???');
typeof str2; // object
str2.xxx = '?';
console.log(str2.xxx); // "?"
/* 使用构造方法创建了一个字符串对象,自然地,给一个对象添加属性是可以的,而且这个对象也不是临时对象 */

MDN 对 String 基本字符串和字符串对象的比较:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String#基本字符串和字符串对象的区别

面向对象一些属性和方法

hasOwnProperty()

只有对象自己定义的属性和方法会返回true,注意这个方法的参数是一个字符串;在 prototype 下定义的属性和方法是公用的,会返回false

constructor

可以用来检测函数类型

每个原型函数都会自动添加 constructor 属性,一般这个属性会指向对象的构造函数,所以这个属性尽量不要修改。来看一个例子:

1
2
3
4
5
6
7
8
function Person() {
  // ...
}
let person = new Person();
person.constructor == Person; // true

let ary = [];
ary.constructor == Array; // true

另一个需要注意的点,在使用原型模式构造对象时可能会遇到的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Person() {}
Person.prototype.name = '?';
Person.prototype.age = 10;
let p = new Person();
p.constructor == Person; // true

// 上述写法没什么问题,但下面的写法会有问题
Person.prototype = {
  name: '?',
  age: 10
};
let person = new Person();
person.constructor == Person; // false; person.constructor == Object
// 这里将Person.prototype重新赋值给一个新的对象,新对象的constructor会指向Object
// 所以需要手动修改,
// 默认情况下constructor是不可枚举的(Enumerable == false),应该用Object.defineProperty()来修改
Object.defineProperty(person, 'constructor', { value: Person });

instanceof

判断对象和构造在原型链上是否有关系,也可以用来对类型进行判断,但不是最好的方案,因为在两个 iframe 里是无法判断的。

toString()

判断类型,一般用于判断数组等类型,ES6 中判断数组可以用Array.isArray();看一个例子:

1
2
3
4
5
6
7
let ary = [];
Object.prototype.toString.call(ary); // [object Array]
let obj = {};
Object.prototype.toString.call(obj); // [object Object]
let date = new Date();
Object.prototype.toString.call(date); // [object Date]
// ...

继承

用 ES6 的语法是最佳解决方案。