1
OOP:Object Oriented Programming 面向对象编程。
题外话:面向对象的范围实在太大,先把这些大的东西理解理解。

1.什么是对象?

根据高程和权威指南上的定义,对象是一组没有特定顺序的值,我们可以把对象看成是从字符串到值的映射。

2.怎样理解原型和原型链?

原型:

根据权威指南上的定义:每一个js对象(null除外)都和另一个对象相关联,
“另一个”对象就是我们熟知的原型,每一个对象都从原型上继承属性。原型也是对象。

通俗点讲,就是一个拥有prototype属性、且这个属性指向函数的原型对象的对象。
原型的好处就是:原型上的方法和属性会被所有实例所共享

原型链

当访问某个实例属性或方法时,会先自身对象中查找,查不到时再往当前对象原型上找;
若依然没找到,则会继续往原型对象的原型上找,一直到找到结果或者找到Object.prototype为止也没找到,
然后这时就会返回undefined,这么一个链式查找过程形成的结构就叫原型链。
    

构造函数,实例,原型三者关系图

每个对象都有一个__proto__属性,函数也是对象,所以函数也有;
每个函数都有一个prototype属性,而实例对象没有。

3.面向对象的三大特性是什么?

封装,继承,多态(指一个方法可以有多种调用方式:例如有参或无参)

4.创建对象有哪些方式?


(1).对象字面量

let obj={};

(2).Object方式

let obj=new Object();

(3).Object.create

let obj=Object.create({}/null);

注意!!前面这三种方式的的构造函数都是Object,而Object已经是原型链的最顶端了,所以Object.prototype都为undefined。

可以用实例的__proto__.constructor查看构造函数是否指向Object构造函数。

(4).工厂模式

    function person(name,job){
        let o={};
        o.name=name;
        o.job=job;
        o.sayName=function(){
            console.log(this.name);
        }
        return o;
    }
    let p1=person('nagi','sleep');
    console.log(p1.constructor); // 指向Object
    p1 instanceof person;  // false;

优缺点:

这种模式虽然解决了量产对象的问题,但却无法获知当前对象是何类型 (例如类型:Array,Math等内置对象,或者BOM(window)/DOM的宿主对象,又或者自定义对象等)

注意点:函数首字母不用大写。

(5).构造函数模式

    function Person(name,job){
        this.name=name;
        this.job=job;
        this.sayName=function(){
            console.log(this.name);
        }
    }
    let p1=new Person('nagi','sleep');
    console.log(p1.constructor); // 指向Person
    p1 instanceof Person;   // true

与工厂模式区别:

a.没有显式创建对象;
b.直接将属性和方法赋给了this对象
c.不有return语句;

拓展:new操作符做了些什么?

a.创建一个新对象;
b.将构造函数的作用域赋给新对象(因此this就指向一这个新对象);
c.执行构造函数中的代码(为这个新对象添加属性);
d.返回新对象(默认返回当前对象,除非有显示返回某个对象)

优缺点:

首先是解决了工厂模式中不能判断类型的问题;
但缺点是每实例一次的同时还会把方法重新创建一遍,造成内存资源浪费;
其次,因构造函数与其它函数的唯一区别就是调用方式不一样,所以当被当作普通函数调用时,其内部的this会指向全局,引发作用域问题。

(6).原型模式

    function Person(){};
    // 写法一:
    Person.prototype.name='nagi';
    Person.prototype.job='sleep';
    Person.prototype.sayName=function(){
        console.log(this.name);
    };
    // 写法二:注意,这种直接更改原型指向的写法,会改变constructor指向,指向Object构造函数
    /* Person.prototype={
    //  constructor:Person,
        name:'nagi',
        job:'sleep',
        sayName:function (){
            console.log(this.name)
        }
    }*/
    let p1=new Person();

优缺点:

优点:解决了上述构造函数的缺点,原型对象上的属性和方法为所有实例所共享。
缺点:
    a.缺点也是所有实例共享方法和属性,因此其中一个实例更改了引用类型的属性值时,其他的实例也会被迫改变。(属性)
    b.默认情况下所有实例都取得相同的属性值。
    

(7).组合模式(结合构造函数和原型模式)

    function Person(name,job){
        this.name=name;
        this.job=job;
    }            
    Person.prototype.sayName=function(){
        console.log(this.name);
    }
    let p1=new Person('nagi','sleep');

4.对象继承方式有哪些?

(1).原型链继承

    function Parent(){
        this.name='nagi';
        this.colors=['red','blue','green'];
    }
    Parent.prototype.sayName=function(){
        console.log(this.name);
    }
    function Child(){
        this.job='sleep';
    };
    Child.prototype=new Parent(); 
    // 要注意:这种重写原型链的写法是会切断构造函数与最初原型之间的联系的,
    // 意味着此时Child.prototype.constructor指向Parent
    var child=new Child();        
这种方式的基本思想就是利用原型让一个引用类型继承另一个引用类型的属性和方法。

问题点:

a.上述例子中,通过原型继承方式继承相当于专门创建了一个Child.prototype.colors的属性,
 因为引用类型的原型属性会被所有实例共享,也就意味着Child的所有实例都会共享colors这一属性,
 当其中一实例修改它的值时,那么其他的实例的值也会跟着被改变。
 
b.在创建子类实例时,不能向超类型的构造函数中传递参数。鉴于此,实际很少单独用原型链继承。
超类型:比如Child类继承了Parent类的属性,那么Parent就是Child的超类(也叫父类)。

(2).构造函数继承(call/apply)

    function Parent(name){
        console.log(`子类传进的name值是:${name}`)
        this.name='nagi';
        this.colors=['red','blue','green'];
    }
    Parent.prototype.sayName=function(){
        console.log(this.name);
    }
    function Child(){
        Parent.call(this,'Bob')
        this.job='sleep';
    };
    var child1=new Child();
    var child2=new Child();
基本思想是在子类构造函数的内部调用超类(或父类)型构造函数。

优缺点:

优势:解决了原型链的两个问题;
缺点:
    a. 方法都在构造函数定义的话,那函数复用就无从谈起了。
    b. 父类在原型中定义的方法,对于子类型来说是不可见的。鉴于此,构造函数也很少用。[捂脸]

(3).组合继承(即原型链+构造函数)

    function Parent(name){
        this.name=name;
        this.job='sleep';
        this.colors=['red','blue','green'];
    }
    Parent.prototype.sayName=function(){
        console.log(this.name);
    }
    function Child(name){
        Parent.call(this,name);
        this.job='eat';
    }
    Child.prototype=new Parent();
    Child.prototype.constructor=Child; 
    let child1=new Child('nagi');
    let child2=new Child('Bob');
其实现思路是用原型链实现对原型属性和方法的继承,而借助构造函数实现对实例属性的继承。

优点:在前两者基础上,补足了构造函数和原型链的缺点,是比较常用的方式。

(4). Object.create继承

    function Parent(name){
        this.name=name;
        this.job='sleep';
        this.colors=['red','blue','green'];
    }
    Parent.prototype.sayName=function(){
        console.log(this.name);
    }
    function Child(){
        Parent.call(this); // 保证构造函数指针指向Child
    }
    Child.prototype=Object.create(Parent.prototype,{name:{value:'nagi'},job:{value:'eat'}})
    // 如果想同时继承多个,还可使用Object.assign()添加属性
    // Object.assign(A.prototype, B.prototype);
    let child=new Child();

(5)ES6的extends方式

类继承,class A extends B

夕凪
258 声望11 粉丝