高程笔记2-对象与面向对象

对象

属性的类型

ECMA-262 使用数据属性和访问器属性内部特性来描述属性的特征。用[[Configurable]],两对中括号表示,不能直接引用。

  • 数据属性:
    • [[Configurable]]:表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特性,以及是否可以把它改为访问器属性。缺省为true。
    • [[Enumerable]]:是否可被迭代,缺省为true。
    • [[Writable]]:表示属性的值是否可以被修改,缺省为true。
    • [[Value]]:包含属性实际的值,缺省为undefined。
  • 访问器属性(不包含数据值):
    • [[Configurable]]:同上。
    • [[Enumerable]]:同上。
    • [[Get]]:获取函数,在读取属性时调用,缺省为undefined。
    • [[Set]]:设置函数,在写入属性时调用,缺省为undefined。

统一使用Object.defineProperty(对象,属性名,特性)接口修改上述属性

let person = {}; 
Object.defineProperty(person, "name", { 
 configurable: false, 
 value: "Nicholas" 
});
合并对象
  • ES6新特性:Object.assign(target_object,orginal_object)方法,返回修改后的目标对象,本质是贪心复制。会复制original_object上每个可枚举属性,用[[Get]]获取;用target_object上[[Set]]赋值。
对象相等判断

为了修正一些边界情况新增Object.is(object,object)。

console.log(NaN === NaN); // false
console.log(Object.is(NaN,NaN)); // true
增强对象语法

let person = {
sayName: function(name){
console.log(My name is ${name});
} };

let person = {
sayName(name){
console.log(My name is ${name});
} };

解构*

解构都是浅复制,解构时在内部会将原数据转为对象,从而可以解构基本数据类型(隐式转换),但是无法解构undefiend和null

  • 对象解构
let person = { 
 name: 'Matt', 
 age: 27,
 bro:{
     borName:'joker'
 } 
};
<!-- 属性名不同 -->
let { name: personName, age: personAge } = person;
<!-- 属性名相同 -->
let { name, age } = person;
<!-- 定义默认值 -->
let { name, job='Software engineer' } = person;
<!-- 嵌套解构 -->
<!-- 声明borName,赋值person.bor.borName -->
let {bro:{broName}}=person;//borName为'joker'

  • 函数参数解构

对参数的解构赋值不会影响 arguments 对象,函数签名中声明会在函数体内变成局部变量

let person = { 
 name: 'Matt', 
 age: 27,
 bro:{
     borName:'joker'
 } 
};

function getMsg(name,{age:myAge}){
    <!-- myAge为局部变量 -->
    console.log(arguments);'Matt,27'
    console.log(myAge);'27'
}

创建对象

工厂模式

用于抽象创建特定对象

function createPerson(name, age, job) { 
 let o = new Object(); 
 o.name = name; 
 o.age = age; 
 o.job = job; 
 o.sayName = function() { 
 console.log(this.name); 
 }; 
 return o; 
}

var obj1 = createPerson('xx',1,'webfront')
构造函数

优点:每个实例有独自的属性。缺点:每个实例之间没有共享属性和方法,每个方法都会在创建实例时创建一次

function Person(name, age, job){ 
 this.name = name; 
 this.age = age; 
 this.job = job; 
 this.sayName = function() { 
 console.log(this.name); 
 }; 
}
var p = new Preson("xxx",2,"server")

使用new 关键字时:

  • 在内存中创建一个新对象。
  • 这个新对象内部的[[Prototype]]特性被赋值为构造函数的 prototype 属性。
  • 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)。
  • 执行构造函数内部的代码(给新对象添加属性)。
  • 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。

Tips:new 构造函数时,构造函数后的括号非必须。

原型模式

优点:因为构造实例时会实例的原型会指向构造函数的原型,从而有共享的方法和实例。缺点:没有单独的属性。

function Person() {}
Person.prototype.job = "Software Engineer"; 
Person.prototype.sayName = function() { 
 console.log(this.name); 
};

原型链

核心是:实例的[[prototype]]指向构造函数的prototype,实例的constructor指向构造函数;构造函数的constructor指向Funciton函数。

原型层级(访问属性的层级)

实例本身属性>>>原型上的属性
从而实例上的属性如果和原型同名会遮蔽原型上的属性,只有通过delete操作才能删除。

hasOwnProperty()和 in 操作符

hasOwnProperty用于查询原型自身属性,in即可以查询原型也可查询自身。

function Person(){
    this.faName = "abc"
};
var p = new Person;
p.chName = "xxx";
"chName" in p //true
"faName" in p //true
p.hasOwnProperty("chName") //true
p.hasOwnProperty("faName") //false

属性枚举
  • 枚举顺序不确定:for-in 循环和 Object.keys()
  • 枚举顺序确定:Object.getOwnPropertyNames()、Object.getOwnPropertySymbols() Object.assign()

继承

tips:instanceof操作符可以用于检测实例对象的原型中是否出现相应构造函数
继承主要为了继承父类的原型和实例上的属性

不常用的继承
盗用构造函数

为了解决原型在继承时包含引用值导致一改多动问题。
基本思路:在子类构造函数中调用父类构造函数,并绑定当前上下文,缺点是只继承父类实例属性,没能继承到父类原型。

function SuperType() { 
 this.colors = ["red", "blue", "green"]; 
} 
function SubType() { 
 // 继承 SuperType 
 SuperType.call(this); 
}
组合继承

综合了原型链和盗用构造函数继承,即继承了原型也继承了父类实例。
思路:用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。

function SuperType(){ 

} 
SuperType.prototype.sayName = function() { 

}; 
function SubType(name, age){ 
 // 继承属性
 SuperType.call(this, name); 
 this.age = age; 
} 
// 继承方法
SubType.prototype = new SuperType();
原型式继承

适用场景:你有一个对象,想在它的基础上再创建一个新对象。你需要把这个对象先传给 object(),然后再对返回的对象进行适当修改。

function object(o) { 
 function F() {} 
 F.prototype = o; 
 return new F(); 
}
let person = { 
 name: "Nicholas", 
 friends: ["Shelby", "Court", "Van"] 
}; 
let anotherPerson = object(person);

ES5对其规范化,变成新的api:Object.create(original,Object)|original是原对象,需要继承的对象,object同Object.defineProperties()的第二个参数一样,每个新增属性都通过各自的描述符来描述

let person = { 
 name: "Nicholas", 
 friends: ["Shelby", "Court", "Van"] 
}; 
let anotherPerson = Object.create(person, { 
 name: { 
 value: "Greg" 
 } 
}); 
console.log(anotherPerson.name); // "Greg"
寄生式继承(略)
寄生式组合继承(略)

类*

类的定义

与函数定义不同的是,虽然函数声明可以提升,但类定义不能
类的组成:函数方法、实例方法、获取函数、设置函数和静态类方法
类的本身就是一种特殊的函数
在类块定义的所有内容都会添加在类的原型上,在constructor函数内定义会添加到实例上

// 类声明
class Person {} 
// 类表达式
const Animal = class {};
类构造函数

方法名 constructor 会告诉解释器在使用 new 操作符创建类的新实例时(调用时同普通构造函数一样的过程),应该调用这个函数,缺省时会自动定义一个空constructor(){}
类实例化时传入的参数会用作构造函数的参数。如果不需要参数,则类名后面的括号也是可选的
与普通构造函数的区别:必须使用new关键字,否则会报错

类的实例属性

定义在constructor函数内this上

class Person { 
 constructor() { 
     this.name = "xxx";
     this.doSomeThing = function(){};
 } 
}
类的访问器
class Person { 
 set name(newName) { 
 this.name_ = newName; 
 } 
 get name() { 
 return this.name_; 
 } 
}
let p = new Person(); 
p.name = 'Jake'; 
console.log(p.name); // Jake
类的静态方法

静态方法通常用于执行不特定于实例的操作,也不要求存在类的实例。
定义在类本身上

class Person { 
 constructor() { 
 // 添加到 this 的所有内容都会存在于不同的实例上
 this.locate = () => console.log('instance', this); 
 } 
 // 定义在类的原型对象上
 locate() { 
 console.log('prototype', this); 
 } 
 // 定义在类本身上!!
 static locate() { 
 console.log('class', this); 
 } 
}
迭代器与生成器方法

类定义语法支持在原型和类本身上定义生成器方法

ES6继承

虽然类继承使用的是新语法,但背后依旧使用的是原型链。

基础基础

。使用 extends 关键字,就可以继承任何拥有[[Construct/]]和原型的对象。很大程度上,这意味着不仅可以继承一个类,也可以继承普通的构造函数(保持向后兼容):

class Vehicle {} 
// 继承类
class Bus extends Vehicle {}
function Person(){}
// 继承普通构造函数
class Engineer extends Person {} 
let e = new Engineer();
super()和构造函数

派生类的方法可以通过 super 关键字引用它们的原型。这个关键字只能在派生类中使用而且仅限于类构造函数、实例方法和静态方法内部。在类构造函数中使用 super 可以调用父类构造函数

  • 在constructor中调用(不要在super之前引用this),可以传参

如果没有定义类构造函数,在实例化派生类时会调用 super(),而且会传入所有传给派生类的参数
如果在派生类中显式定义了构造函数,则要么必须在其中调用 super(),要么必须在其中返回一个对象。

class Vehicle { 
 constructor(licensePlate) { 
 this.hasEngine = true; 
 } 
}
class Bus extends Vehicle { 
 constructor(licensePlate) { 
    // 不要在调用 super()之前引用 this,否则会抛出 ReferenceError 
     super(licensePlate); // 相当于 super.constructor() 
    console.log(this instanceof Vehicle); // true 
    console.log(this); // Bus { hasEngine: true } 
 }
  • 在静态方法中调用(可以通过super调用继承类上的静态方法):
class Vehicle { 
    static identify() { 
        console.log('vehicle'); 
    } 
} 
class Bus extends Vehicle { 
    static identify() { 
        super.identify(); 
    } 
} 
Bus.identify(); // vehicle
抽象基类

它可供其他类继承,但本身不会被实例化。
new.target 保存通过 new 关键字调用的类或函数。通过在实例化时检测 new.target 时不是抽象基类,可以阻止对抽象基类的实例化

类混入(不常用,略)

总结

  • 原型链涉及把构造函数的原型赋值为另一个类型的实例。
    这样一来,子类就可以访问父类的所有属性和方法,就像基于类的继承那样。原型链的问题是所有继承的属性和方法都会在对象实例间共享,无法做到实例私有
  • 盗用构造函数模式通过在子类构造函数中调用父类构造函数,可以避免这个问题。这样可以让每个实例继承的属性都是私有的,但要求类型只能通过构造函数模式来定义(因为子类不能访问父类原型上的方法)。
  • 目前最流行的继承模式是组合继承,即通过原型链继承共享的属性和方法,通过盗用构造函数继承实例属性。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值