# 谈谈你对面向对象是如何理解的

面向对象的底层其实还是面向过程,把面向过程抽象成,然后封装

面向对象的三大特性:

  1. 封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式,将变化隔离,便于使用,提高复用性和安全性。
  2. 继承:提高代码复用性;继承是多态的前提。
  3. 多态:父类或接口定义的引用变量可以指向子类或具体实现类的实例对象。提高了程序的拓展性。

# 面向过程和面向对象对比

面向过程:

  • 优点:性能比面向对象好,因为类调用时需要实例化,开销比较大,比较消耗资源。
  • 缺点:不易维护、不易复用、不易扩展.

面向对象:

  • 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护
  • 缺点:性能比面向过程差

# 常用js类定义的方法有哪些

两种方式:构造函数+原型链, 对象创建

  • 构造函数+原型链
function Person(){
	this.name = 'michaelqin';
}
Person.prototype.sayName = function(){
	console.log(this.name);
}

var person = new Person();
person.sayName();
1
2
3
4
5
6
7
8
9
  • 对象创建 Object.create({某某对象});
var Person = {
	name: 'michaelqin',
	sayName: function(){ alert(this.name); }
};

var person = Object.create(Person);
person.sayName();
1
2
3
4
5
6
7

# js类继承的方法有哪些

四种方法:构造,原型链,组合,寄生

  1. 构造继承
  2. 原型链继承
  3. 组合继承
  4. 寄生组合式继承

# 构造继承

缺陷:

  • 只能继承父类的实例属性和方法,不能继承prototype上的属性/方法

代码实现:

function Animal() {
    this.name = 'animal';
}

Animal.prototype.sayName = function () { 
    console.log(this.name);
};

function Person() {
    Animal.call(this); // 使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类
}

let p = new Person()
p.sayName() // TypeError: p.sayName is not a function
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 原型链继承

缺陷:

  1. 原型链上的引用类型的数据会被所有实例共享
  2. 无法实现多继承

代码实现:

function Animal() { // 父类
    this.name = 'animal'
    this.arr = [1]
}

function Dog() { // 子类
    this.name = 'dog'
}

Dog.prototype = new Animal() // 原型链继承;将父类的实例作为子类的原型;
// 缺陷:虽然实现了继承;但切断了`Dog.prototype.constructor`与`Dog`的关系
Dog.prototype.constructor = Dog // 弥补上面的缺陷

let dog1 = new Dog()
let dog2 = new Dog()
dog1.arr.push(2) // 缺陷:原型链上的引用类型数据会被所有实例共享
console.log(dog1.arr) // [ 1, 2 ]
console.log(dog2.arr) // [ 1, 2 ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 组合继承

核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

构造继承 + 原型链继承的组合

推荐:

  • 优点:解决了构造函数继承和原型链继承的缺点
  • 缺点:调用了两次父类的构造函数,即new 父类()使用了两次,原型链继承父类的构造函数一次,实例化一次

代码实现:

function Animal() { // 父类
    this.name = 'animal'
    this.arr = [1]
}

Animal.prototype.sayName = function () {
    console.log(this.name);
};

function Dog(name) { // 子类
    Animal.call(this); // 绑定上下文
    this.name = name || 'huahua'
}

Dog.prototype = new Animal() // 原型链继承;父类实例
Dog.prototype.constructor = Dog // 弥补上面的缺陷

let dog1 = new Dog()
let dog2 = new Dog()
dog1.arr.push(2) 
console.log(dog1.arr) // [ 1, 2 ]
console.log(dog2.arr) // [ 1 ]
dog1.sayName()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 寄生组合式继承

核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免组合继承的缺点

构造继承 + 原型链继承 + 父类原型链的浅拷贝

  • 特点:使用到了Object.create(父类.prototype)实现原型链的浅拷贝
  • 优点:解决了原型链继承构造函数继承的缺点
  • 缺点:暂无

代码实现:

function Animal() { // 父类
    this.name = 'animal'
    this.arr = [1]
}

Animal.prototype.sayName = function () {
    console.log(this.name);
};

function Dog() { // 子类
    Animal.call(this); // 构造继承
}

Dog.prototype = Object.create(Animal.prototype) // 对父类prototype的浅拷贝,而不是复制父类的实例
Dog.prototype.constructor = Dog
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# JS多重继承的实现方法是怎么样的

多继承中典型的问题,Diamond Problem

当 A, B, C 中都定义了一个相同名称的函数时,而在 D 的实例对象中调用这个函数时,究竟应该去执行谁。。。

MRO算法

Method Resolution Order (MRO) 指的是在继承结构中确定类的线性顺序

# defineProperty,hasOwnProperty,propertyIsEnumerable都是做什么用的

  • Object.defineProperty(obj, prop, descriptor)用来给对象定义属性,有value,writable,configurable,enumerable,set/get等.
  • hasOwnPrpoerty用于检查某一属性是不是存在于对象本身,继承来的父亲的属性不算.
  • propertyIsEnumerable用来检测某一属性是否可遍历,也就是能不能用for..in循环来取到.

# 理解原型设计模式以及JavaScript中的原型规则

  1. 所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展属性;
  2. 所有的引用类型(数组、对象、函数),都有一个__proto__属性(隐式原型),属性值是一个普通的对象;
  3. 所有的函数,都具有一个 prototype(显式原型),属性值也是一个普通对象;
  4. 所有的引用类型(数组、对象、函数),其隐式原型指向其构造函数的显式原型(obj._proto_ === Object.prototype)
  5. 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的 __proto__ (即它的构造函数的 prototype)中去寻找;

# 为什么要用原型模式

原型模式的好处

  • 工厂模式:不明确创建对象o的类型
  • 构造函数模式:会创建多个完成同样任务的Function实例

# instanceof的底层实现原理

instanceof的判断逻辑是:从当前引用的proto一层一层顺着原型链往上找,能否找到对应的prototype。找到了就返回true

简单说就是判断实例对象的__proto__是不是强等于对象的prototype属性,如果不是继续往原型链上找,直到 __proto__ 为 null 为止。

function instance_of(L, R) {//L 表示左表达式,R 表示右表达式 
    var O = R.prototype;   // 取 R 的显示原型 
    L = L.__proto__;  // 取 L 的隐式原型
    while (true) {    
        if (L === null)      
             return false;   
        if (O === L)  // 当 O 显式原型 严格等于  L隐式原型 时,返回true
             return true;   
        L = L.__proto__;    // 隐式原型
    }
}
1
2
3
4
5
6
7
8
9
10
11