创建一个对象的几种方式
- new Object()或者字面量{},或者__proto__;
js
let obj = {
name: 'obj',
sayName: function () {
console.log(this.name)
}
}
// obj ---> Object.prototype ---> null
// 可以通过 __proto__ 字面量属性将新创建对象的[[Prototype]] 指向另一个对象。
const p = { b: 2, __proto__: o };
// p ---> o ---> Object.prototype ---> null
-
优点 被所有的现代引擎所支持。将 proto 属性指向非对象的值只会被忽略,而非抛出异常。与 Object.prototype.proto setter 相反,对象字面量初始化器中的 proto 是标准化,被优化的。甚至可以比 Object.create 更高效。在创建对象时声明额外的自有属性比 Object.create 更符合习惯。
-
缺点 不支持 IE10 及以下的版本。对于不了解其与 Object.prototype.proto 访问器差异的人可能会将两者混淆。
- Object.create(proto,propertiesObject);
js
// 参数1:原型对象,参数2:可枚举的自有属性
// 参数1不是null或对象,则抛出TypeError异常
// 参数2不是指定结构也会报TypeError错
let obj1 = Object.create(obj, {
name: {
value: 'obj1Name', // 默认为undefined
writable: true, // 默认为false
configurable: true, // 默认为false
enumerable: false,
writable:false
}
})
obj1.__proto__.__proto__ === obj.__proto__;
obj1.__proto__.__proto__ === Object.prototype;
// 扩展,查看一个对象的原型上是否有某个属性
Object.prototype.hasOwnProperty.call(obj1,'sayName'); //false name为true
// 可模仿new 构造函数
- 优点 被所有现代引擎所支持。允许在创建时直接设置对象的 [[Prototype]],这允许运行时进一步优化对象。还允许使用 Object.create(null) 创建没有原型的对象。
- 缺点 不支持 IE8 及以下版本。但是,由于微软已经停止了对运行 IE8 及以下版本的系统的扩展支持,这对大多数应用程序而言应该不是问题。此外,如果使用了第二个参数,慢对象的初始化可能会成为性能瓶颈,因为每个对象描述符属性都有自己单独的描述符对象。当处理上万个对象描述符时,这种延时可能会成为一个严重的问题。
- new 构造函数创建;
js
function Person(name) {
// 判断是否用new 创建对象
if (new.target === void 0) {
return {}
}
this.name = name;
this.sayName = function () {
console.log(this.name)
}
}
const xiaoming = new Person('xiaoming');
- 优点 所有引擎都支持------一直到 IE 5.5。此外,其速度很快、非常标准,且极易被 JIT 优化。
- 缺点
1.要使用这个方法,必须初始化该函数。在初始化过程中,构造函数可能会存储每一个对象都必须生成的唯一信息。这些唯一信息只会生成一次,可能会导致问题。
2.构造函数的初始化过程可能会将不需要的方法放到对象上。
- 使用类class
js
class Obj {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
const obj = new Obj(2, 3);
// obj ---> Obj.prototype ---> Object.prototype ---> null
-
优点 被所有现代引擎所支持。非常高的可读性和可维护性。私有属性是原型继承中没有简单替代方案的特性。
-
缺点 类,尤其是带有私有属性的类,比传统的类的性能要差(尽管引擎实现者正在努力改进这一点)。不支持旧环境,通常需要转译器才能在生产中使用类。
new 都干了什么
- 创建了一个空的对象{}
- 为步骤1创建的对象增加属性__proto__,将该属性链接至构造函数的原型对象
- 将步骤1创建的对象作为this的上下文
- 如果该函数没有返回对象,则返回this
如果返回的是一个非对象,则返回this
如果返回的是一个对象,则返回该对象
模拟实现一个new函数
js
function NewFn(Fn) {
if (typeof Fn !== 'function') {
throw new Error('Fn must be a function')
}
const newObj = Object.create(Fn.prototype);
const result = Fn.apply(newObj, Array.prototype.slice.call(arguments, 1))
if (result && typeof result === 'object') {
return result;
} else {
return newObj;
}
}
模拟实现Object.create
js
function inherit(p,properties) {
if (p == null) throw TypeError();
if (Object.create) {
return Object.create(p)
}
const t = typeof p;
if (t !== 'object' && t !== 'function') throw TypeError();
// - 返回了一个对象;
// - 这个对象的原型,指向了这个函数 Function 的 prototype;
function F() {};
F.prototype = p;
if(properties) {
Object.defineProperties(F, properties);
}
return new F();
}
new Object()和直接{}的区别
从创建对象的过程来讲,这两者底层实现基本是没有区别的。但是new Object()本质上是方法(只不过这个方法是内置的)调用, 既然是方法调用,就涉及到在proto链中遍历该方法,当找到该方法后,又会生产方法调用必须的堆栈信息,方法调用结束后,还要释放该堆栈。
所以,相比来说,更推荐直接字面量创建,更简洁,更高效
Object.create(null)和直接{}的区别
Object.create(null)创建了一个空对象,无Object原型对象的任何属性,可以自己写对应的方法等,不会污染全局Object
- Object.create(null)使用
- 在我们使用for...in循环的时候会遍历对象原型链上的属性,使用create(null)就不必再对属性进行检查了
- 你需要一个非常干净且高度可定制的对象当做数据字典的时候
- 减少hasOwnProperty造成的性能损失并且可以偷懒少些一点代码的时候
继承
- 子类的原型对象------类继承
js
// 声明父类
function FatherClass() {
this.children = [];
}
// 为父类添加共有方法
FatherClass.prototype.sayName = function () {
console.log('I am father');
}
// 声明子类
function ChildClass() {
this.hasFather = true;
}
// 继承父类
ChildClass.prototype = new FatherClass();
// 为子类添加共有方法
ChildClass.prototype.sayName = function () {
console.log('I am children');
}
const child1 = new ChildClass();
const child2 = new ChildClass();
child2.children.push(12);
console.log(child1.children, child2.children); // 都为[12]
/**
* 问题
* 1. 传参
* 2. 如果属性是引用属性,一旦某个实例修改了这个属性,那么都会被修改掉
*/
- 构造函数继承
js
// 声明父类
function FatherClass() {
this.children = [];
}
// 为父类添加共有方法
FatherClass.prototype.sayName = function () {
console.log('I am father');
}
// 声明子类
function ChildClass(arg) {
this.hasFather = true;
FatherClass.call(this, Array.prototype.slice.call(arguments, 1))
}
// 为子类添加共有方法
ChildClass.prototype.sayName = function () {
console.log('I am children');
}
const child1 = new ChildClass('');
const child2 = new ChildClass('');
child2.children.push(12);
console.log(child1.children, child2.children); // [],[12]
/**
* 问题
* 1. 这种类型的继承没有涉及原型prototype,所以父类的原型属性或者方法不能被继承,想要被继承的话,只能在构造函数中定义,违背了代码复用原则
* 2. 如果方法在构造函数中定义,每次都会被创建。
*/
- 组合继承
js
// 声明父类
function FatherClass() {
this.children = [];
}
// 为父类添加共有方法
FatherClass.prototype.sayName = function () {
console.log('I am father');
}
// 声明子类
function ChildClass(arg) {
this.hasFather = true;
FatherClass.call(this, Array.prototype.slice.call(arguments, 1))
}
// 只是想要一个原型链,又调用了一遍父类构造函数
ChildClass.prototype = new FatherClass();
// 为子类添加共有方法
ChildClass.prototype.sayName = function () {
console.log('I am children');
}
const child1 = new ChildClass('');
const child2 = new ChildClass('');
child2.children.push(12);
console.log(child1.children, child2.children); // [],[12]
- 原型式继承
js
function inheritObj(obj){
if(p == null) throw TypeError();
if(Object.create) {
return Object.create(p)
}
var t = typeof p;
if(t !== 'object' && t !== 'function') throw TypeError();
// 声明一个过渡函数对象
function Fn(){};
// 过渡对象原型继承父类对象
Fn.prototype = obj;
// 返回该过渡对象实例,该实例的原型链继承了父对象
return new Fn();
}
- 寄生组合式继承
js
function inheritPrototype(parentClass, childClass) {
// 复制一根父类的原型副本保存在变量中
const p = inheritObj(parentClass.prototype);
// 修正因为重写导致子类的constructor属性被修改
p.constructor = childClass;
// 设置子类的原型
childClass.prototype = p;
}
// 声明父类
function FatherClass() {
this.children = [];
}
// 为父类添加共有方法
FatherClass.prototype.sayName = function () {
console.log('I am father');
}
// 声明子类
function ChildClass(arg) {
this.hasFather = true;
FatherClass.call(this, Array.prototype.slice.call(arguments, 1))
}
inheritPrototype(FatherClass, ChildClass);
// 为子类添加共有方法
ChildClass.prototype.sayName = function () {
console.log('I am children');
}
const child1 = new ChildClass('');
const child2 = new ChildClass('');
child2.children.push(12);
console.log(child1.children, child2.children); // [],[12]
js
/// class 继承中做的,而狭义上,组合寄生式继承,没有做的 //
for(var k in Person) {
if(Person.hasOwnProperty(k) && !(k in Teacher)) {
Teacher[k] = Person[k]
}
}
//
// - class 继承,会继承静态属性
// - 子类中,必须在 constructor 调用 super, 因为子类自己的 this 对象,必须先通过 父类的构造函数完成。
POP面向过程编程Procedure-Oriented Programming
分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
OPP面向对象编程Object Oriented Programming
把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描述某个事物在整个解决问题的步骤中的行为。