重学前端|重学前端-面向对象

对象 属性
数据属性 1.value 值
2.writable 能否被赋值
3.enumerable 能否被枚举 for in Object.keys
4.configurable 能否被删除或者改变属性值

var a ={a:1} Object.getOwnPropertyDescriptor(a,'a') //{configurable: true,enumerable: true,value: 1,writable: true}Object.defineProperty(a,'a',{value:5,configurable: false,enumerable: false,writable: false})

访问器属性 1.Getter 取值
2.setter 赋值
3.enumerable 能否被枚举 for in
4.configurable 能否被删除或者改变属性值
let o = { b: 1, get a() { return this.b; }, set a(i) { this.b = i; }, }; console.log(o.a); // 1 o.a = 5; console.log(o.a); // 5var o = { b: 1 }; Object.defineProperty(o, 'a', { set: function (value) { this.b = value + 100; }, get: function () { return this.b; }, }); console.warn(o.a); // 6 o.a = 6; console.warn(o.a); // 106

创建对象
对象的key只能是字符串 和 symbol
对象字面量
var id = Symbol(); var o = { name: 'a', age: 18, [id]: 1 }; // 对象的key 键名是数值console.warn(o[id]); // 1 // 获取key string console.log(Object.getOwnPropertyNames(o)); //[ 'name', 'age' ] console.log(Object.keys(o)); //[ 'name', 'age' ] // 获取key Symbol console.log(Object.getOwnPropertySymbols(o)); // [ Symbol() ]// 获取所以属性 console.log(Object.getOwnPropertyNames(o).concat(Object.getOwnPropertySymbols(o))); // [ 'name', 'age', Symbol() ] console.log(Reflect.ownKeys(o)); // [ 'name', 'age', Symbol() ]// 获取属性 console.log(Object.getOwnPropertyDescriptor(o, 'name')); // { value: 'a', writable: true, enumerable: true, configurable: true } console.log(Object.getOwnPropertyDescriptors(o)); // { //name: { value: 'a', writable: true, enumerable: true, configurable: true }, //age: { value: 18, writable: true, enumerable: true, configurable: true }, //[Symbol()]: { value: 1, writable: true, enumerable: true, configurable: true } // }for (let i in o) { console.log(i); // name age }console.log(o.hasOwnProperty('name')); // true console.log(o.hasOwnProperty(id)); // true

缺点:产生大量重复代码
Object构造函数
var id = Symbol(); var o = new Object({ name: 'a', age: 18, [id]: 1 }); // 对象的key 键名是数值console.warn(o[id]); // 1 // 获取key string console.log(Object.getOwnPropertyNames(o)); //[ 'name', 'age' ] console.log(Object.keys(o)); //[ 'name', 'age' ] // 获取key Symbol console.log(Object.getOwnPropertySymbols(o)); // [ Symbol() ]// 获取所以属性 console.log(Object.getOwnPropertyNames(o).concat(Object.getOwnPropertySymbols(o))); // [ 'name', 'age', Symbol() ] console.log(Reflect.ownKeys(o)); // [ 'name', 'age', Symbol() ]// 获取属性 console.log(Object.getOwnPropertyDescriptor(o, 'name')); // { value: 'a', writable: true, enumerable: true, configurable: true } console.log(Object.getOwnPropertyDescriptors(o)); // { //name: { value: 'a', writable: true, enumerable: true, configurable: true }, //age: { value: 18, writable: true, enumerable: true, configurable: true }, //[Symbol()]: { value: 1, writable: true, enumerable: true, configurable: true } // }for (let i in o) { console.log(i); // name age }console.log(o.hasOwnProperty('name')); // true console.log(o.hasOwnProperty(id)); // true

缺点:产生大量重复代码
Object.create() 使用现有的原型来创建对象
var id = Symbol(); var o = Object.create({ name: 'a', age: 18, [id]: 1 }); // 对象的key 键名是数值console.warn(o[id]); // 1 // 获取key string console.log(Object.getOwnPropertyNames(o)); //[ ] console.log(Object.keys(o)); //[] // 获取key Symbol console.log(Object.getOwnPropertySymbols(o)); // []// 获取所以属性 console.log(Object.getOwnPropertyNames(o).concat(Object.getOwnPropertySymbols(o))); // [ ] console.log(Reflect.ownKeys(o)); // [ ]// 获取属性 console.log(Object.getOwnPropertyDescriptor(o, 'name')); // undefined console.log(Object.getOwnPropertyDescriptors(o)); // {}for (let i in o) { console.log(i); // name age }console.log(o.hasOwnProperty('name')); // false console.log(o.hasOwnProperty(id)); // false

工厂模式
function createPerson(name,age) { var o = new Object() o.name = name; o.age = age; o.sayName=function() { console.log(this.name) } return o }var p1 = createPerson('a',18) var p2 = createPerson('b',18)console.log(p1 instanceof Object) // true console.log(p2instanceof Object) // trueconsole.log(p2.__proto__ === p1.__proto__)

优点:能一次创建多个对象
缺点:创建的对象没法区别 所有实例的原型都指向同一个对象
构造函数模式
function Person(name,age) { this.name = name; this.age = age; this.sayName = function() { console.log(this.name) } } var p3 = new Person('a',18) p3.sayName() // q p3.name = 'aa' p3.sayName() // qq var p4 = new Person('b',18) p4.sayName() // b console.log(p3 instanceof Person) // true console.log(p4 instanceof Person) // trueconsole.log(p4.__proto__ === p3.__proto__)

优点:显式的创建了对象 可以识别为一种特点类型
缺点:每次创建对象时方法都要被创建一次
原型模式
function Person1() {} Person1.prototype.name = 'a' Person1.age = '18' Person1.sayName = function() { console.log(this.name) } var p5 = new Person('a',18) p5.sayName() // a p5.name = 'aa' p5.sayName() // aa var p6 = new Person('b',18) p6.sayName() // b console.log(p5 instanceof Person) // true console.log(p5 instanceof Person) // trueconsole.log(p6.__proto__ === p6.__proto__)

优点:方法不会重建
缺点:所有属性和方法都共享 不能初始化参数
  • 原型模式优化
    优点:方法不会重建 封装更好 更简单
    缺点:所有属性和方法都共享 不能初始化参数 完全重写了原型 ,导致constructor指向不对,要明确指向constructor
/** * 原型模式优化 * 优点:方法不会重建 更简单 * 缺点:所有属性和方法都共享 不能初始化参数 完全重写了原型 ,导致constructor指向不对,要明确指向constructor * */ function Person11() {} Person11.prototype = { constructor: Person11,// 完全重写了原型 ,导致constructor指向不对,要明确指向constructor name : 'a', age: 18, sayName:function() { console.log(this.name) } } var p55 = new Person11() p55.sayName() // a p55.name = 'aa' p55.sayName() // aa var p66 = new Person1() p66.sayName() // a console.log(p55 instanceof Person11) // true console.log(p55 instanceof Person11) // trueconsole.log(p66.__proto__ === p55.__proto__) // false console.log(Person11.prototype.constructor === Person11) // true

组合模式 就是把构造模式和原型模式组合起来,私有变量放在构造函数里面,公用方法放在原型链里面
function Person2(name,age) { this.name = name; this.age = age } Person2.prototype.sayName = function() { console.log(this.name) } var p7 = new Person2('a',18) p7.sayName() // a p7.name = 'aa' p7.sayName() // aa var p8 = new Person2('b',18) p8.sayName() // b console.log(p7 instanceof Person2) // true console.log(p7 instanceof Person2) // trueconsole.log(p8.__proto__ === p7.__proto__)

优点:有私有变量 和 共有 方面
动态原型模式 就是把组合模式改变下 原型链定义的方法放在构造函数里面,如果实例没有这个方法 就定义这把这个方法放在原型里,
相比组合模式 封装更多
function Person3(name,age) { this.name = name; this.age = age if(typeof this.sayName !== 'function') { Person3.prototype.sayName = function() { console.log(this.name) } // 或者 // Person3.prototype = { //constructor: Person3, // 对象字面量会导致原型被覆盖 实例的原型指向错误 需要明确指向constructor //sayName: function() { //console.log(this.name) //} // } } } var p7 = new Person3('a',18) p7.sayName() // a p7.name = 'aa' p7.sayName() // aa var p8 = new Person3('b',18) p8.sayName() // b console.log(p7 instanceof Person3) // true 改为对象字面量 变为 false console.log(p7 instanceof Person3) // true 改为对象字面量 变为 false console.log(p7.__proto__)console.log(p8.__proto__ === p7.__proto__) // true 改为对象字面量 变为 false

寄生构造函数 就是把工厂方法写在构造函数里面 和工厂方法的区别是实例通过new
function Person4(name,age) { var o = new Object() o.name = name; o.age = age; o.sayName=function() { console.log(this.name) } return o } var p7 = new Person4('a',18) p7.sayName() // a p7.name = 'aa' p7.sayName() // aa var p8 = new Person4('b',18) p8.sayName() // b console.log(p7 instanceof Person4) //false console.log(p7 instanceof Object) //trueconsole.log(p8.__proto__ === p7.__proto__) // true

用途:对某个类型进行属性增强时 出于安全考虑不能直接修改原型,可以考虑这种方式
function SpecialArray() { var values = new Array(); values.push.apply(values, arguments); values.toPipedString = function() { return this.join("|"); }; return values; } var colors = new SpecialArray("red", "blue", "green"); console.log(colors.toPipedString()); //"red|blue|green"

稳妥构造函数 不用this 没有公共属性 不用new
优点:安全
function person9(name){ var o = new Object(); o.sayName = function(){ console.log(name); }; return o; }var person1 = person9('kevin'); person1.sayName(); // kevinperson1.name = "daisy"; person1.sayName(); // kevinconsole.log(person1.name); // daisy

区别
工厂模式 构造函数模式 原型模式 组合模式 动态模式 寄生构造函数 稳妥构造函数
描述 一个function 里面返回一个对象 一个构造函数 this new()声明 Prototype 构造函数(属性)+原型模式(方法) 组合模式改进 把方法定义放在构造函数里面 就是工厂函数 只是用new声明 就是工厂函数 只是不用this 没有共有属性 只有方法 返回传入的值
优点 简单 能一次创建多个对象实例 显式的创建一种类型 方法只创建一次 既有共有方法也有私有属性 封装更好
缺点 创建的对象没法区分类型 都指向object 方法每次创建都要创建一次 属性和方法公用 不能初始化属性
用途 类型属性增强 安全场景
方法
  • 获取属性
    • Object.getOwnPropertyDescriptor(o, ‘name’) 获取o.name属性 不能获取原型链上的
    • Object.getOwnPropertyDescriptors(o) 获取o全部属性 不能获取原型链上的
  • 枚举
    • Object.getOwnPropertyNames(o) 获取string 的键名 不能获取原型链上的
    • Object.keys(o) 获取string 的键名 不能获取原型链上的
    • Object.getOwnPropertySymbols(o) 获取symbol键名 不能获取原型链上的
    • For in能获取原型链上的键名 拿不到symbol
    • Reflect.ownKeys(o) 不包括原型链上的 包括Symbol
  • o.hasOwnProperty(‘name’) 判断是否是原生属性 不能获取到原型链上的
var id = Symbol(); var o = { name: 'a', age: 18, [id]: 1 }; // 对象的key 键名是数值 co[id]; // 1 // 获取key string Object.getOwnPropertyNames(o); //[ 'name', 'age' ] Object.keys(o); //[ 'name', 'age' ] // 获取key Symbol Object.getOwnPropertySymbols(o); // [ Symbol() ]// 获取所以属性 Object.getOwnPropertyNames(o).concat(Object.getOwnPropertySymbols(o)); // [ 'name', 'age', Symbol() ] Reflect.ownKeys(o); // [ 'name', 'age', Symbol() ]// 获取属性 Object.getOwnPropertyDescriptor(o, 'name'); // { value: 'a', writable: true, enumerable: true, configurable: true } Object.getOwnPropertyDescriptors(o); // { //name: { value: 'a', writable: true, enumerable: true, configurable: true }, //age: { value: 18, writable: true, enumerable: true, configurable: true }, //[Symbol()]: { value: 1, writable: true, enumerable: true, configurable: true } // }var b = Symbol('b'); function person() { this.name = 'a'; this.age = 18; this[b] = 2; }person.prototype[id] = 1; person.prototype.sex = 'girl'; var p = new person(); p[id]; // 1 // 获取key string Object.getOwnPropertyNames(p); //[ 'name', 'age' ] Object.keys(p); //[ 'name', 'age' ] // 获取key Symbol Object.getOwnPropertySymbols(p); // [ Symbol(b) ]// 获取所以属性 Object.getOwnPropertyNames(p).concat(Object.getOwnPropertySymbols(p)); // [ 'name', 'age', Symbol(b) ] Reflect.ownKeys(p); // [ 'name', 'age', Symbol(b) ]// 获取属性 Object.getOwnPropertyDescriptor(p, 'name'); // { value: 'a', writable: true, enumerable: true, configurable: true } Object.getOwnPropertyDescriptors(p); // { //name: { value: 'a', writable: true, enumerable: true, configurable: true }, //age: { value: 18, writable: true, enumerable: true, configurable: true } //[Symbol(b)]: { value: 2, writable: true, enumerable: true, configurable: true } // }for (let i in o) { console.log(i); // name age }for (let i in p) { console.log(i); // name age sex }console.log(o.hasOwnProperty('name')); // true console.log(p.hasOwnProperty('name')); // trueconsole.log(o.hasOwnProperty(id)); // true console.log(p.hasOwnProperty(id)); // false console.log(p.hasOwnProperty(b)); // true

原型 vs 原型链 原型
定义:每个对象创建时都会与之关联一个原型对象,每个对象都会从原型继承属性。
对象是通过构造函数创建的 这个构造函数有一个属性prototype是一个对象,这个对象就是原型。原型包括了一些公用的属性和方法。构造函数创建的对象会从这个原型中继承这个属性。当找一个属性或者方法时在对象找不到会沿着原型链找。
function Person(){} Person.prototype.name = 'a'// 这里的protype就是原型 var p = new Person() console.log(p.name) // a// p对象会从Person.protype原型那里继承name属性console.log(Person.prototype) // {name:'a'} prototype指向原型对象 console.log(Object.getPrototypeOf(p) ) // {name:'a'} Object.getPrototypeOf 获取原型对象 console.log(p.__proto__ === Person.prototype) // true 对象的___proto__指向对象的原型 console.log(Object.getPrototypeOf(p)=== Person.prototype ) //true console.log(Person === Person.prototype.constructor) // true 原型的constructor指向构造函数console.log(Person.prototype.__proto__) // [Object: null prototype] {} 原型的原型是objectconsole.log(Person.prototype.__proto__.__proto__) // null 原型链的尽头是nullconsole.log(Person.prototype.__proto__ === Object.prototype) // true 原型的原型是object的原型

重学前端|重学前端-面向对象
文章图片

构造函数 实例 原型 的关系如上图:
  • 构造函数的prototype指向原型对象
  • 原型对象的constrctor指向构造函数
  • 构造函数new 出来的实例 通过__proto__指向原型对象 Object.getPrototypeOf(实例) 获取实例的原型对象
原型链
定义:就是相互关联的原型 形成的一个链条 相互之间通过__proto__连接。原型对象是通过obejct构造函数创建的,原型的原型是object的原型对象, 原型链的尽头时null。
重学前端|重学前端-面向对象
文章图片

new的原理 手动实现
  1. 首先创建了一个空对象
  2. 然后设置空对象的__proto__作为构造函数的原型prototype
  3. 然后让构造函数的this指向这个对象,允许构造函数
  4. 判断构造函数的返回值 如果是引用类型就返回这个引用类型,如果不是返回创建的对象
    function createNew(fn) { const obj = {}; obj.__proto__ = fn.prototype; const result = fn.apply(obj,[...arguments].slice(1)); return typeof result === 'object' ? result:obj }function Person(name,age) { this.name = name; this.age = age; }Person.prototype.sayName = function() { console.log(this.name) }Person.prototype.sayAge = function() { console.log(this.age) }const p = createNew(Person,'aa',18) p.sayName() // aa p.sayAge() // 18xxxxxxxxxx funcfunction createNew(fn) { const obj = {}; obj.__proto__ = fn.prototype; const result = fn.apply(obj,[...arguments].slice(1)); return typeof result === 'object' ? result:obj}function Person(name,age) {this.name = name; this.age = age; }Person.prototype.sayName = function() {console.log(this.name)}Person.prototype.sayAge = function() {console.log(this.age)}const p = createNew(Person,'aa',18)p.sayName() // aap.sayAge() // 18

继承 原型链继承
利用原型链的特性 让child的prototype原型指向parent的实例 这样构造原型链 child就可以访问parent的元素
缺点是 1.构造函数的属性共享, 如果是引用类型 一个子类的实例修改了这个属性 其他实例继承到的属性也会改变 2. 无法向父类传参
/** * 原型链继承 * */ function Parent(firstName,lastName) { this.firstName = firstName; this.lastName = lastName; this.friend = { firstName:'li', lastName:'si' } } Parent.prototype.sayName = function () { console.log(this.lastName+this.firstName) }function Child(firstName) { this.firstName = firstName; } const parent = new Parent('san','zhang'); Child.prototype = parent const child = new Child('si'); const child1 = new Child('si'); child.sayName() // zhangsi parent.sayName() // zhangsan console.log(child.friend) // { firstName: 'li', lastName: 'si' }// 缺点是 构造函数的属性共享, 如果是引用类型 一个子类的实例修改了这个属性 其他实例继承到的属性也会改变 child1.friend.firstName = 'hua' console.log(child1.friend) // { firstName: 'hua', lastName: 'si' } console.log(child.friend) // { firstName: 'hua', lastName: 'si' }

构造函数继承
可以解决原型链 引用属性共享会导致被子类修改的问题 可以向父类传递参
缺点:所有属性只能在构造函数创建 方法会被多次创建
/*构造函数继承*/ function Parent2(firstName,lastName) { this.firstName= firstName; this.lastName = lastName; // 缺点是 只能在构造函数里面创建方法 this.sayName = function () { console.log(this.lastName+this.firstName) } this.friend = {name:'li si'} }function Child2(name,age) { Parent2.call(this,...arguments) } const parent2 = new Parent2('san','zhang'); const child2 = new Child2('si','zhang'); const child21 = new Child2('wu','zhang'); child2.sayName() // zhangsi parent2.sayName() // zhangsan console.log(child2.friend) // { name: 'li si' }// 可以解决原型链属性共享的问题,如果是引用类型 一个子类的实例修改了这个属性 其他实例继承到的属性不会改变 child21.friend.name = 'hua hua' console.log(child21.friend) // { name: 'hua hua' } console.log(child2.friend) // { name: 'li si' }

组合继承
利用构造函数+原型链 这样既可以在原型上定义方法,子类也可以继承,又可以让每一个子类有自己的属性 引用类型不会被子类改变 还可以向父类传参
/** * 组合继承 * */ function Parent3(firstName,lastName) { this.firstName= firstName; this.lastName = lastName; this.friend = {name:'li si'} }Parent3.prototype.sayName = function () { console.log(this.lastName+this.firstName) }function Child3(firstName,lastName) { Parent3.call(this,...arguments); this.firstName = firstName; } Child3.prototype = new Parent3() // 缺点是调用了两次父类构造函数 const parent3 = new Parent3('san','zhang'); const child3 = new Child3('si','zhang'); const child31 = new Child3('wu','zhang'); child3.sayName() // zhangsi parent3.sayName() // zhangsan console.log(child3.friend) // { name: 'li si' }child31.friend.name = 'hua hua' console.log(child31.friend) // { name: 'hua hua' } console.log(child3.friend) // { name: 'li si' }

原型式继承
利用一个父类对象 作为原型 创建其他子类对象,子类对象就会继承父类对象
缺点:无法传递参数 引用类型会被改
/** * 原型式继承 * */ var o = { firstName:'san', lastName:'zhang', sayName:function () { console.log(this.lastName+this.firstName) }, friend:{name:'li si'} } var o1 = Object.create(o)// 以o为新对象的原型创建对象 var o2 = Object.create(o) o2.firstName='si' o1.firstName = 'wu' o2.friend = 'haha' o1.sayName() // zhangwu o2.sayName() //zhangsi console.log(o2.friend) // haha console.log(o1.friend) //lisi// Object.create 自己实现 本质是内部有一个构造函数 把这个构造函数的原型指向传入的对象,返回构造函数的实例 function object(o) { function fn() {} fn.prototype = o; return new fn(); } var o11 = object(o)// 以o为新对象的原型创建对象 var o22 = object(o) o22.firstName='si' o11.firstName = 'wu' o22.friend = 'haha' o11.sayName() // zhangwu o22.sayName() //zhangsi console.log(o22.friend) // haha console.log(o11.friend) //lisi

寄生式继承
【重学前端|重学前端-面向对象】就是把 object.create 方法封装到了一个函数里面
/** * 寄生式继承 * */ function object(obj) { function fn {} fn.prototype = obj return new fn(); }function createAnother(parent) { varo = object(parent) o.firstName='si' return o } const oo = {firstName:'san',lastName:'zhang',friend:{name:'lisi'},sayName:function (){console.log(this.lastName+this.firstName)}} var o3 = createAnother(oo) var o4 = createAnother(oo) o3.sayName() // zhangsi o3.friend.name = 'huahua' console.log(o4.friend) // huahua

缺点:无法传递参数 引用类型会被改
寄生组合式继承
组合继承+寄生式 能解决组合继承父类构造函数会被调用两次的问题
function inherit(subType,superType) { var prototype = Object.create(superType.prototype); prototype.constructor = superType; subType.prototype = prototype; }function SuperType(firstName,lastName) { this.firstName = firstName; this.lastName = lastName; this.friend = { name:'lisi' } } SuperType.prototype.sayName= function () { console.log(this.lastName+this.firstName) } function SubType(firstName,lastName) { SuperType.call(this,...arguments) this.age = 18; }inherit(SubType,SuperType) const parent5 = new SuperType('san','zhang') parent5.sayName() // zhangsan const child5 = new SubType('si','zhang') const child6 = new SubType('wu','zhang') child5.sayName() // zhangsi child6.sayName() // zhangwuconsole.log(parent5.friend) // { name: 'lisi' } child5.friend.name ='haha' console.log(child5.friend) // { name: 'haha' } console.log(child6.friend) // { name: 'lisi' } console.log(parent5.friend) // { name: 'lisi' }

区别
继承方式 描述 优点 缺点
原型链继承 利用原型链上可以访问上一级属性,让子类的protype属性等于父类的实例 SubType.prototype=new SuperType() 实现了继承 1.引用属性共享 子类可以篡改父类的属性 2. 子类无法向父类传递参数
构造函数继承 在子类的构造函数调用父类的构造函数 改变this指向子类 Super.call(this,…arguments) 1. 应用属性不会共享 2. 子类可以向父类传递参数 1. 所有属性只能封装在构造函数里面 每次创建实例 方法都会被创建一次
组合式继承 原型式继承+构造函数继承 ,利用构造函数继承父类的属性,利用原型继承父类的方法 1. 应用属性不会共享 2. 子类可以向父类传递参数3.方法也只会创建一遍 父类构造函数会被调用两次
原型式继承 Object.create 会以传入的对象作为原型创造一个新对象 本质是内部声明了一个构造函数 把这个构造函数的prototype属性指向这个函数 最后返回这个函数的实例 同原型链继承
寄生式继承 就是把原型式继承用一个函数封装起来 其实同原型式继承 同上
寄生组合式 组合式+寄生式 利用寄生式继承的特点 解决组合式继承父类构造函数会被调用两次的问题 function inhret(subType,superType){ var prototype = Object.create(superType.prototype); prototype.constructor=SuperType; subType.prototype=protptype} 1. 应用属性不会共享 2. 子类可以向父类传递参数3.方法也只会创建一遍 4.父类构造函数也只会调用一次

    推荐阅读