JS面向对象

封装

工厂模式创建对象

function createPerson(name, age){
    let obj = {};
    obj.name = name;
    obj.age = age;
    obj.sayHi= function(){
        console.log('My name is '+ name +'!');
    }
    return obj;
}
let p1 = createPerson('Sam', 18);
p1.sayHi()  //My name is Sam!

构造函数模式创建对象

function Person(name, age){
    this.name = name;
    this.age = age;
}
Person.prototype.sayHi = function(){
    console.log('My name is '+ this.name +'!');
}
let p2 = new Person('ayu', 22);
p2.sayHi()  //My name is ayu!

其实内部流程跟上面的工厂模式差不多,主要有几点区别:

  • Person称为类,约定俗成以大驼峰命名方式,Person这个函数叫做构造函数。
  • 构造函数有个 prototype 属性,这个就是对象原型(存放实例公共方法),JS中主要以原型方式实现继承
  • new 关键字方式创建实例,代码中的 p2 称为Person类的实例。
  • 构造函数模式下,如果函数不需要传参 new Person()new Person 效果一样。

注意:任何类的类型都是function,任何实例的类型都是object

那么构造函数模式创建对象的整个流程大致是:

  1. 浏览器默认创建一个空对象
  2. 构造函数体代码由上往下执行,this 指向该对象
  3. 原型指向
  4. 返回该对象(如果构造函数内写有返回值且返回值类型为object,那么new出来的也是该返回值而不是类的实例)

依照这个流程我们也可以手动实现一个 new 关键字效果:

/**
 * @param {function} Fn 构造函数
 * @param  {...any} arg 一些其他参数
 */
function _new(Fn, ...arg){
    let instance = {};
    instance.__proto__ = Fn.prototype;  //你也可以用Object.create
    let res = Fn.call(instance, ...arg);
    return typeof res === 'object' ? res : instance;
}
let p3 = _new(Person, 'lala', 19);
p3.sayHi()  //My name is lala!

ES6

ES6提供了class关键字,所以现在可以用class来定义类,不过这种方式实际上是构造函数方式的语法糖

class Person{
    //构造函数
    constructor(name, age){
        this.name = name;
        this.age = age;
    }
    //原型上的方法,不能用键值对方式
    sayHi(){
        console.log('My name is '+ this.name +'!');
    }
    //静态方法,不能是静态属性
    static aa(){...}
}

ES6用class封装类有几点注意:

  1. 类名不能直接当普通函数使用,也就是说构造函数模式的Person有类名、普通函数、普通对象3个身份,而class模式的Person只有类名、普通对象两个身份。
  2. class体内定义的函数会绑到原型上,不可用键值对方式定义。
  3. class体内可用static关键字定义静态方法,但不能是静态属性。
  4. class体外依然可以像平时一样用Person.prototype.xx方式扩展。

 

继承

原型

1、每一个函数都有一个天生自带的属性 prototype(称为显示原型),并且该属性是个对象数据类型

2、prototype 身上也天生自带一个属性 constructor,属性值是当前构造函数本身

3、每一个对象数据类型也天生自带一个属性 __proto__ (称为隐式原型),属性值是当前实例所属类的原型(prototype)。

4、Object 在JS中是所有对象数据类型的基类Object.prototype上没有 __proto__ 属性。

注意:函数在JS中比较特殊。函数作为 Function 的实例,自然也有 __proto__ 属性,函数.__proto__ 指向 Function.prototype。Object构造函数是 Function 的实例,但 Function.prototype又是一个Object的实例

 

 

原型链

还是参考上图

通过 对象名.属性名 方式获取属性值的时候,首先会在对象的私有属性上查找,如果私有属性中存在,则获取的是私有属性值。

如果私有属性中没有,则通过对象的 __proto__ 找到所属类的原型(原型上定义的属性和方法都是类的实例共有的),如果原型上有,则获取原型上的属性值。

如果原型上没有,则继续通过原型的 __proto__ 到下一层查找,一直找到 Object.prototype 为止。

这种查找模式叫作原型链模式。

注意:IE浏览器中禁止手动操作 __proto__,因为每个实例都有 __proto__ 属性,而 __proto__ 指向的原型对象是共有的,要想操作这个对象用 类.prototype 操作就行了。

 

几种继承方式

上面提到过,JS中主要以原型来实现继承,实际下面几种方式都是围绕着这个中心思想。

就像html文档中的DOM元素一样也是原型链继承的:

div.__proto__ = HTMLDivElement.prototype

HTMLDivElement.prototype.__proto__ = HTMLElement.prototype

HTMLElement.prototype.__proto__ = Element.prototype

Element.prototype.__proto__ = Node.prototype

Node.prototype.__proto__ = EventTarget.prototype

EventTarget.prototype.__proto__ = Object.prototype

 

原型继承法

子类原型是一个父类的实例

//父类
function Animal(height){
    this.height = height;
}
Animal.prototype.walk = function(){
    console.log('I can walk')
}
//子类
function Person(name, age){
    this.name = name;
    this.age = age;
}
Person.prototype = new Animal;
Person.prototype.constructor = Person;

let p1 = new Person('Sam', 22)
p1.walk()  //I can walk

该方式优缺点一目了然,继承了父类的共有方法,但没有继承父类的私有属性。

call继承法

解决上面存在的继承父类私有属性。在子类构造函数中直接调用父类构造函数(用call以普通函数形式执行)

//父类
function Animal(height){
    this.height = height;
}
Animal.prototype.walk = function(){
    console.log('I can walk')
}
//子类
function Person(name, age, height){
    this.name = name;
    this.age = age;
    Animal.call(this, height);
}

let p1 = new Person('Sam', 22, 180)
console.log(p1.height)  //180

该方式缺点也一目了然,继承了父类的私有属性,但却没有父类原型上的方法。强迫症都犯了,为什么不组合一下上面两种方式,忍一忍,别人的文章都是细分的,我也要细分,笑哭。

冒充对象继承法

说白了就是将父类的所有属性 for in 遍历克隆一份给子类

//父类
function Animal(height){
    this.height = height;
}
Animal.prototype.walk = function(){
    console.log('I can walk')
}
//子类
function Person(){
    let temp = new Animal;
    for(let key in temp){
        this[key] = temp[key]
    }
}

该方式缺点还是一目了然,将父类的私有共有属性方法都克隆给子类私有。

混合继承法

组合原型继承与call继承

//父类
function Animal(height){
    this.height = height;
}
Animal.prototype.walk = function(){
    console.log('I can walk')
}
//子类
function Person(name, age, height){
    this.name = name;
    this.age = age;
    Animal.call(this, height)
}
Person.prototype = new Animal;
Person.prototype.constructor = Person;

let p1 = new Person('Sam', 18, 179);
p1.walk()  //I can walk

已经逐渐趋近完美了,就是子类原型上还有父类的私有属性,虽不碍事,但总不完美。

寄生组合式继承(推荐)

名字看看就好,实际就是借助 Object.create()

//父类
function Animal(height){
    this.height = height;
}
Animal.prototype.walk = function(){
    console.log('I can walk')
}
//子类
function Person(name, age, height){
    this.name = name;
    this.age = age;
    Animal.call(this, height)
}
Person.prototype = Object.create(Animal.prototype);
Person.prototype.constructor = Person;

let p1 = new Person('Sam', 21, 183);
p1.walk()  //I can walk

完美

简单粗暴继承法

子类原型直接指向父类原型

//父类
function Animal(height){
    this.height = height;
}
Animal.prototype.walk = function(){
    console.log('I can walk')
}
//子类
function Person(name, age){
    this.name = name;
    this.age = age;
}
Person.prototype = Animal.prototype;
Person.prototype.constructor = Person;

let p1 = new Person('Sam', 18);
p1.walk()  //I can walk

ES6

语法相当简单,实际效果类似上面的寄生组合式继承法

class Person extends Animal{
    constructor(){
        super()  //类似call继承
        ...
    }
    ...
}

 

多态

多态意思是方法的多种形态。主要分为重载重写

重载:方法名相同,参数不同。明确表示JS中不存在重载。你可以函数内部通过判断参数数量、类型来达到类似重载的效果。

重写:子类重写父类的方法

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值