Appearance
对象
万物皆对象。
我们需要掌握的
- ES5 创建对象的 7 种方式,及其优缺点。
- ES6 创建对象的方式 class。
1. ES5 创建对象
1、 工厂模式
2、 构造函数模式
3、 原型模式
4、 组合使用构造函数与原型模式
5、 动态原型模式
6、 寄生构造函数模式
7、 稳妥构造函数模式
1.1、 工厂模式
javascript
function createPersonFactory(name, age, job) {
var person = {};
person.name = name;
person.age = age;
person.job = job;
person.getName = function() {
return this.name;
};
return person;
}
var person1 = createPersonFactory('张三', 26, '码农');
var person2 = createPersonFactory('李四', 26, '码畜');
优点
- 好像没有啥优点的,就是简单。
缺点
不知道其原型是什么。
不能够区分静态属性方法与实例属性方法。
每次创建的时候都需要 在内存中多生成一个对象。
1.2、 构造函数创建对象
javascript
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.getName = function() {
return this.name;
};
}
var person1 = new Person('张三', 26, '码农');
var person2 = new Person('李四', 26, '码畜');
<font class="j-font-red j-font-blod" >new 一个对象的 4 个步骤</font>
- 创建一个新的对象;
- 将构造函数的作用域赋给新对象(this 指向新的对象)
- 执行构造函数中的代码(为这个对象添加属性)
- 返回新的对象
优点
- 跟工厂模式比 不需要去多余的创建一个新的对象存储数据。也知道其原型
缺点
- 不能够区分静态属性方法与实例属性方法。
1.3、 原型模式
javascript
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
}
Person.prototype.type = 'person';
Person.prototype.getName = function() {
return this.name;
};
console.dir(Person);
// console.log(Person.getName()) //Person.getName is not a function
var person1 = new Person('张三', 26, '码农');
var person2 = new Person('李四', 26, '码畜');
优点
- 原型对象的原型属性是共有的。
javascript
console.log(person1.getName === person2.getName); //true
console.log(person1.type === person2.type); //true
console.dir(person1.__proto__ === Person.prototype); //true
console.dir(Person.prototype.constructor === Person); //true
console.dir(Person.prototype.__proto__ === Object.prototype); //true
console.dir(Person.prototype.__proto__.__proto__ === null); //true
缺点
- 每多生成一个静态属性或者方法都需要调用 Person.prototype 原型,性能有问题
1.4、 组合使用构造函数和原型模式
这是最广泛的创建对象的方式
javascript
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
}
Person.prototype = {
// 此时不知道其构造函数是什么
type: 'person',
getName: function() {
return this.name;
},
};
// Person.prototype.constructor = Person;
var person1 = new Person('张三', 26, '码农');
var person2 = new Person('李四', 26, '码畜');
console.log(person1 instanceof Person); // true
console.log({}.toString.call(person1)); // [object Object]
console.log(person1.__proto__.constructor); //Object(){}
// 修改实例对象原型链的指向
Person.prototype.constructor = Person;
var person1 = new Person('张三', 26, '码农');
var person2 = new Person('李四', 26, '码畜');
console.log(person1.__proto__.constructor); //Person(){}
缺点
- 每多生成一个静态属性或者方法都需要调用 Person.prototype 原型,性能有问题
1.5 动态原型模式
解决组合使用构造函数与原型模式下多次创建静态属性方法的问题。 组合使用构造函数与原型模式具体区分了静态属性方法与实例属性方法,但是存在一个问题是静态属性方法本来就是公共的,所以只需要创建一次就可以了,但是组合使用构造函数与原型模式确实没创建一个实例就去重写其静态属性与方法。
javascript
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
if (typeof this.getName === 'undefined') {
console.log('创建了this.getName()');
Person.prototype.getName = function() {
return this.name;
};
}
if (typeof this.type === 'undefined') {
console.log('创建了this.type');
Person.prototype.type = 'person';
}
}
var person1 = new Person('张三', 26, '码农'); //只输出了一次
var person2 = new Person('李四', 26, '码畜');
console.log(person1.getName === person2.getName); //true
1.6 寄生构造函数模式
在上述模式不适用的情况下,使用寄生模式
javascript
function createPerson(name, age, job) {
var person = new Object();
person.name = name;
person.age = age;
person.job = job;
person.getName = function() {
return person.name;
};
}
缺点
- 跟工厂模式唯一的区别就是创建对象的方式不一样。
- 工厂模式 : var person = {};
- 寄生模式 : var person = new Object();
- 不能区别其构造函数是什么。
作用
- 不修改一些构造函数本身的情况下为其添加私有的方法。
javascript
function sigalArray() {
var arr = new Array();
arr.push.apply(arr, arguments);
arr.getFirst = function() {
return arr[0];
};
return arr;
}
var arr = new sigalArray('战三', '李四', '王五');
console.log(arr.getFirst()); // '战三'
1.7 稳妥构造函数模式(安全)
新创建的对象的实例方法不通过 this 引用。
不使用 new 操作符调用构造函数
javascript
function Person(name, age, job) {
var person = new Object();
person.getName = function() {
return name;
};
return person;
}
var person1 = Person('张三', 26, '码农');
console.log(person1.name); //undefined
console.log(person1.getName()); // '张三'
ES6 class 创建对象
ES6 提供的新方式,所以需要使用 babel 等转换,其可以看做一个语法糖,绝大部分功能 ES5 都能实现
- constructor
- 静态属性方法与实例属性方法中 this 的指向
- name 属性
- 静态属性方法与实例属性方法的创建方式
- get、set 方法
- new.target
- 私有属性的创建
基本的创建方式
js
const DOG_TYPE = Symbol('dog');
class Animal {
constructor(name, type, age) {
this.name = name;
this.type = type;
this.age = age;
}
getName() {
return this.name;
}
}
class Dog extends Animal {
constructor(name, age) {
super(name, DOG_TYPE, age);
this.age = 5;
}
// ES2020 私有属性 不需要再通过 _a 去约定了
#a = 2;
// 私有的方法
#bite(peopleName) {
console.log(`${this.name} is bite ${peopleName}`);
}
// 实例属性
color = 'red';
// 实例方法
getColor() {}
// 静态属性
static version = '0.0.1';
// 静态方法
static getVersion() {}
}
javascript
'use strict';
function _typeof(obj) {
'@babel/helpers - typeof';
if (typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol') {
_typeof = function _typeof(obj) {
return typeof obj;
};
} else {
_typeof = function _typeof(obj) {
return obj && typeof Symbol === 'function' && obj.constructor === Symbol && obj !== Symbol.prototype
? 'symbol'
: typeof obj;
};
}
return _typeof(obj);
}
function _inherits(subClass, superClass) {
if (typeof superClass !== 'function' && superClass !== null) {
throw new TypeError('Super expression must either be null or a function');
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: { value: subClass, writable: true, configurable: true },
});
if (superClass) _setPrototypeOf(subClass, superClass);
}
function _setPrototypeOf(o, p) {
_setPrototypeOf =
Object.setPrototypeOf ||
function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
function _createSuper(Derived) {
var hasNativeReflectConstruct = _isNativeReflectConstruct();
return function() {
var Super = _getPrototypeOf(Derived),
result;
if (hasNativeReflectConstruct) {
var NewTarget = _getPrototypeOf(this).constructor;
result = Reflect.construct(Super, arguments, NewTarget);
} else {
result = Super.apply(this, arguments);
}
return _possibleConstructorReturn(this, result);
};
}
function _possibleConstructorReturn(self, call) {
if (call && (_typeof(call) === 'object' || typeof call === 'function')) {
return call;
}
return _assertThisInitialized(self);
}
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return self;
}
function _isNativeReflectConstruct() {
if (typeof Reflect === 'undefined' || !Reflect.construct) return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === 'function') return true;
try {
Date.prototype.toString.call(Reflect.construct(Date, [], function() {}));
return true;
} catch (e) {
return false;
}
}
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf
? Object.getPrototypeOf
: function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true });
} else {
obj[key] = value;
}
return obj;
}
function _instanceof(left, right) {
if (right != null && typeof Symbol !== 'undefined' && right[Symbol.hasInstance]) {
return !!right[Symbol.hasInstance](left);
} else {
return left instanceof right;
}
}
function _classCallCheck(instance, Constructor) {
if (!_instanceof(instance, Constructor)) {
throw new TypeError('Cannot call a class as a function');
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ('value' in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
var DOG_TYPE = Symbol('dog');
var Animal = /*#__PURE__*/ (function() {
function Animal(name, type, age) {
_classCallCheck(this, Animal);
this.name = name;
this.type = type;
this.age = age;
}
_createClass(Animal, [
{
key: 'getName',
value: function getName() {
return this.name;
},
},
]);
return Animal;
})();
var Dog = /*#__PURE__*/ (function(_Animal) {
_inherits(Dog, _Animal);
var _super = _createSuper(Dog);
function Dog(name, age) {
var _this;
_classCallCheck(this, Dog);
_this = _super.call(this, name, DOG_TYPE, age);
_bite.add(_assertThisInitialized(_this));
_a.set(_assertThisInitialized(_this), {
writable: true,
value: 2,
});
_defineProperty(_assertThisInitialized(_this), 'color', 'red');
_this.age = 5;
return _this;
} // ES2020 私有属性 不需要再通过 _a 去约定了
_createClass(
Dog,
[
{
key: 'getColor',
// 实例方法
value: function getColor() {}, // 静态属性
},
],
[
{
key: 'getVersion',
// 静态方法
value: function getVersion() {},
},
]
);
return Dog;
})(Animal);
var _a = new WeakMap();
var _bite = new WeakSet();
var _bite2 = function _bite2(peopleName) {
console.log(''.concat(this.name, ' is bite ').concat(peopleName));
};
_defineProperty(Dog, 'version', '0.0.1');
2.1 constructor
constructor 默认返回的为当前实例对象 可以通过 return 来修改类的原型对象
constructor 如果没有重写,默认添加 constructor(){}
class Person{} === class Person{ constructor()
javascript
class Person {
constructor() {}
}
var person1 = new Person();
console.log(person1 instanceof Person); // true
class Person1 {
constructor() {
return new Person();
}
}
var person2 = new Person1();
console.log(person2 instanceof Person1); // false
console.log(person2 instanceof Person); // true
在 Babel 中如何处理 contructor
js
var Dog = /*#__PURE__*/ (function(_Animal) {
_inherits(Dog, _Animal);
var _super = _createSuper(Dog);
function Dog(name, age) {
var _this;
//
_classCallCheck(this, Dog);
_this = _super.call(this, name, DOG_TYPE, age);
return _this;
} // ES2020 私有属性 不需要再通过 _a 去约定了
return Dog;
})(Animal);
- 其是通过 return 一个当前函数本身,然后通过
_classCallCheck(this, Dog);
去检测当前 class 是通过new
还是直接 执行的
js
function _classCallCheck(instance, Constructor) {
// 判断当前实例对象是否 instanceof 构造函数
if (!_instanceof(instance, Constructor)) {
throw new TypeError('Cannot call a class as a function');
}
}
静态属性和静态方法
js
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true });
} else {
obj[key] = value;
}
return obj;
}
/**
* 通过 defineProperty 去在 构造函数的 prototype 或者Constructor本身上添加属性
* @params Constructor 构造函数本身
* @params protoProps 实例属性 定义在 Constructor.prototype
* @params staticProps 静态属性 定义在 Constructor
*/
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
var Dog = /*#__PURE__*/ (function(_Animal) {
_inherits(Dog, _Animal);
var _super = _createSuper(Dog);
function Dog(name, age) {
var _this;
_classCallCheck(this, Dog);
_defineProperty(_assertThisInitialized(_this), 'color', 'red');
return _this;
} // ES2020 私有属性 不需要再通过 _a 去约定了
_createClass(
Dog,
// 详情请看上面的 _createClass 其第二个参数是定义实例属性或者方法的
[
{
key: 'getColor',
// 实例方法
value: function getColor() {}, // 静态属性
},
],
// 详情请看上面的 _createClass 其第三个参数是定义静态属性或者方法的
[
{
key: 'getVersion',
// 静态方法
value: function getVersion() {},
},
]
);
return Dog;
})(Animal);
// 直接在 Dog的构造函数上定义一个 version 属性 初始值为0.0.1
_defineProperty(Dog, 'version', '0.0.1');
从上面可以看出对于 Class 对象在 Babel 中其是通过
js
_createClass(
Dog,
// 详情请看上面的 _createClass 其第二个参数是定义实例属性或者方法的
[
{
key: 'getColor',
// 实例方法
value: function getColor() {}, // 静态属性
},
],
// 详情请看上面的 _createClass 其第三个参数是定义静态属性或者方法的
[
{
key: 'getVersion',
// 静态方法
value: function getVersion() {},
},
]
);
// 直接在 Dog的构造函数上定义一个 version 属性 初始值为0.0.1
_defineProperty(Dog, 'version', '0.0.1');
_createClass
和 _defineProperty
直接在构造函数上定义静态的属性和方法的
实例属性和实例方法
es6 的书写方式
js
class Dog extends Animal {
// 实例属性
color = 'red';
// 实例方法
getColor() {}
}
其 Babel 编译后的结果如下
js
var Dog = /*#__PURE__*/ (function(_Animal) {
_inherits(Dog, _Animal);
var _super = _createSuper(Dog);
function Dog(name, age) {
var _this;
_classCallCheck(this, Dog);
_this = _super.call(this, name, DOG_TYPE, age);
_defineProperty(_assertThisInitialized(_this), 'color', 'red');
return _this;
} // ES2020 私有属性 不需要再通过 _a 去约定了
_createClass(
Dog,
[
{
key: 'getColor',
// 实例方法
value: function getColor() {}, // 静态属性
},
],
[
{
key: 'getVersion',
// 静态方法
value: function getVersion() {},
},
]
);
return Dog;
})(Animal);
可见 对于实例属性 其是通过_defineProperty(_assertThisInitialized(_this), 'color', 'red');
定义在实例_this 上
对于实例方法 其通过 _createClass(Dog, [{key: 'getColor',value: function getColor() {}, // 静态属性 },])
定义在 Dog.prototype 原型链上
私有属性和私有方法
es6 的书写方式
js
class Dog extends Animal {
// ES2020 私有属性 不需要再通过 _a 去约定了
#a = 2;
// 私有的方法
#bite(peopleName) {
console.log(`${this.name} is bite ${peopleName},#a=${this.#a}`);
}
printSum() {
console.log(this.#bite());
}
}
其 Babel 编译后的结果如下
js
function _classPrivateMethodGet(receiver, privateSet, fn) {
if (!privateSet.has(receiver)) {
throw new TypeError('attempted to get private field on non-instance');
}
return fn;
}
var Dog = /*#__PURE__*/ (function(_Animal) {
_inherits(Dog, _Animal);
var _super = _createSuper(Dog);
function Dog(name, age) {
var _this;
_classCallCheck(this, Dog);
_this = _super.call(this, name, DOG_TYPE, age);
_bite.add(_assertThisInitialized(_this));
_a.set(_assertThisInitialized(_this), {
writable: true,
value: 2,
});
return _this;
} // ES2020 私有属性 不需要再通过 _a 去约定了
_createClass(
Dog,
[
{
key: 'getColor',
// 实例方法
value: function getColor() {}, // 静态属性
},
{
key: 'printSum',
value: function printSum() {
console.log(_classPrivateMethodGet(this, _bite, _bite2).call(this));
},
},
],
[
{
key: 'getVersion',
// 静态方法
value: function getVersion() {},
},
]
);
return Dog;
})(Animal);
// 通过 WeakMap 去创建一个 当前实例对象为key 的 key - value
var _a = new WeakMap();
var _bite = new WeakSet();
var _bite2 = function _bite2(peopleName) {
console.log(''.concat(this.name, ' is bite ').concat(peopleName));
};
可见 对于私有属性 其是通过var _a = new WeakMap();
创建一个当前实例对象为 key 的 Map,然后通过 _bite.add(_assertThisInitialized(_this));
添加,最后通过访问的时候
js
_classPrivateMethodGet(this, _bite, _bite2).call(this); // 其实就是 _bite2.call(this),只是需要验证当前是私有的方法,所以通过
js
_a.set(_assertThisInitialized(_this), {
writable: true,
value: 2,
});
去保存私有属性的值
对于私有的方法 其通过 var _bite = new WeakSet();
创建一个当前实例对象为 key 的 Map,然后通过
2.2 静态属性方法与实例属性方法中 this 的指向
在实例方法或者 construtor 中 this 指向实例对象,但是在静态方法中 this 指向构造函数本身
javascript
class Person {
static getType() {
// 此时的this指向 Person 而不是 new后的实例对象
return this;
}
constructor(name, age, job) {
// 此时的thisnew后的实例对象
this.name = name;
this.age = age;
this.job = job;
}
getName() {
// 此时的thisnew后的实例对象
return this.name;
}
}
var person = new Person('张三', 26, '码农');
let { getName } = person;
// getName() // Cannot read property 'name' of undefined
console.log(Person.name); //Person
2.3 name 属性
console.log(Person.name); //Person name 指向其构造函数本身
2.4 静态属性方法与实例属性方法的创建方式
javascript
class Person {
// 新的提案 ES6的方法
// 定义一个静态的属性 相对于实例属性只需要在前面添加一个static
static type = 'person';
// 定义的一个静态方法,其会前后覆盖
static getType() {
return this.type;
}
// 除了在construtor中定义通过this去定义实例属性 还可以直接在外部定义
protoState = '实例属性';
// 这是定义一个实例属性 这个this指向的 实例对象,但是注意 外部的定义的实例属性 其都是在 构造函数最前面定义的 可能存在获取不到this.name的情景
// 此时this.name 为undefined
// protoThis = { name : name } //此时的name为 arguments中的name
protoThis = {
name: this.name,
};
// 对象的构造函数
constructor(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
}
// 实例属性其定义后都是在 constructor的最上部 按顺序 依次定义
protoThis = {
age: this.age,
};
// 实例方法
getName() {
return this.name;
}
}
console.log(Person.type);
babel 转换后的代码
javascript
var _createClass = (function() {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ('value' in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
/**
* 给对象定义实例属性或者静态属性
* @param {[type]} Constructor [对象]
* @param {[type]} protoProps [实例属性]
* @param {[type]} staticProps [静态属性]
* @return {[type]} [description]
*/
return function(Constructor, protoProps, staticProps) {
// 如果存在实力属性
if (protoProps) defineProperties(Constructor.prototype, protoProps);
// 如果存在静态属性
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
})();
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError('Cannot call a class as a function');
}
}
var Person = (function() {
_createClass(Person, null, [
{
key: 'getType',
value: function getType() {
return this.type;
},
// 新的提案 ES6的方法
},
]);
function Person(name, age, job) {
_classCallCheck(this, Person);
this.protoState = '实例属性';
this.protoThis = {
name: this.name,
};
this.protoThis = {
age: this.age,
};
this.name = name;
this.age = age;
this.job = job;
}
_createClass(Person, [
{
key: 'getName',
value: function getName() {
return this.name;
},
},
]);
return Person;
})();
// 定义的静态属性
Person.type = 'person';
2.5 get、set 方法
get set 其实设置的是对象的实例属性 get set 使用的是描述属性对象的 get、set 方法
javascript
class Person {
constructor(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
}
getName() {
return this.name;
}
get type() {
return this.singalType || 'person';
}
set type(type) {
this.singalType = type;
}
}
var person = new Person('张三', 26, '码农');
console.log(person.type); // 'person'
console.log((person.type = 'teacher'));
console.log(person.type); // 'teacher'
console.log(person); // 'teacher'
输出 get、set 属性的属性描述对象
javascript
console.log(Object.getOwnPropertyDescriptor(person.__proto__, 'type')); // { enumerable: false, configurable: true, get: ƒ, set: ƒ}
可见 get、set 方法其实就是在原型上通过 defineProperty 的 get、set 方法定义
javascript
function Person() {
Object.defineProperty(Person.prototype, 'type', {
configurable: true,
enumerable: false,
get() {},
set() {},
});
}
<font class="j-font-red j-font-blod" >重点:</font>
- get、set 方法中不要使用 this.属性 去取值赋值,不然会无限触发 get、set 方法。
2.6 new.target
一般在构造函数中返回 new 命令作用于的那个构造函数 1、 如果不是通过 new 的方式创建的 其值为 undefind 2、 继承后的 new 还是指向子对象
new.target === Person
javascript
class Person {
constructor(name) {
console.log(new.target === Person); //true
}
}
var person = new Person();
如果不通过 new 创建 其为 undefined
javascript
function Person1() {
if (new.target !== undefined) {
this.name = '是通过new 方式创建的';
console.log('是通过new 方式创建的');
} else {
throw new Error('不是通过new 方式创建对象。');
}
}
var person1 = new Person1(); // 输出 '是通过new 方式创建的'
var person2 = Person1.call(Person1); // 输出 '不是通过new 方式创建对象。'
继承后的 new 还是指向子对象
javascript
class Teacher extends Person {
constructor(name) {
super(name);
console.log(new.target === Person); // false
console.log(new.target === Teacher); // true
}
}
new Teacher();