前端基础进阶(九):详解面向对象、构造函数、原型与原型链

http://www.javashuo.com/article/p-fnxpmwol-er.html  https://yangbo5207.github.io/wutongluo/前端

说明:此处只是记录阅读前端基础进阶的理解和总结,若有须要请阅读上面的连接git

1、对象的定义github

在JavaScript中对象被定义为无序属性的集合,其属性能够是基本类型,也能够是对象,函数segmentfault


1.建立对象的两种方式app

1) 使用new关键字函数

var o =new Object()

2)使用字面量形式this

  var person = {
        name: "xiaoming",
        age: 13,
        getName: function () { return this .name}
    }

2.给对象添加属性和方法的两种方法spa

    //方法1
    var o = new Object();
    o.name = "lilei"; //添加属性name并赋值
    o.getName = function () { return this .name}//添加方法

    //方法2
    var person = {
        name: "xiaoming",
        age: 13,
        getName: function () { return this .name}
    }

3.访问对象属性的两种方式prototype

 //访问name属性的两种方式
    person.name

    person["name"]

若是要同时访问一个对象里面的多个属性能够这样。注:forEach不能写成foreach,不然会报错,而且上一个语句必须有分号结束3d

    //方法2
    var person = {
        name: "xiaoming",
        age: 13,
        getName: function () { return this.name }
    };//必须写分号,不然下面的forEach会报错

     ['name', 'age'].forEach(function (item) {//是forEach,不是foreach
        console.log(person[item]);
    });

 

2、工厂模式

使用上面的方法建立对象很简单,可是若是须要用到两个对象,好比Tom和Jack就不得不把代码相似的代码写两遍,浪费时间,这样工厂模式就出现了。

工厂模式就是按照给定的模式建立出咱们想要的对象,想建立多少就建立多少

 //工厂模式
    function createPerson(name, age) {
        var o = new Object();
        o.name = name;
        o.age = age;
        o.getName = function () { return this.name; };
        return o;
    }

    var perTom = createPerson("Tom", 12);
    var perJack= createPerson("Jack",14);

 

使用工厂模式虽然简单,可是有两个缺陷:

1)不能使用instanceof判断对象的实例类型

var person = {};
    var foo = function () { }

    console.log(obj instanceof Object);  // true
    console.log(foo instanceof Function); // true

2)当有不一样的实例对象时,每一个对象都要生成一个getName方法,每次都要为getName分配一个单独的内存空间

 3、构造函数

为了解决第一个问题就须要用到构造函数了。

 //构造函数
    function Person(name, age) {
        this.name = name;
        this.age = age;
        this.getName = function () { return this.name; };
    }

    var perNike = new Person("Nike", 13);
    console.log(perNike instanceof Person);//true
    console.log(perNike.getName());//Nike

构造函数与通常函数并无本质区别,首字母大写只是约定用来区别构造函数和普通函数的。任何函数均可以当作是构造函数用来产生对象

new关键字能够产生一个对象,调用new时函数内部执行了以下过程:

1)产生一个新对象,把函数的this指向这个对象

2)新对象的原型指向构造函数的原型

3)给新对象添加属性和方法

4)返回新对象

4、原型

为了解决工厂模式的第二个问题须要用到另外一个东西——原型

每个函数都有一个prototype属性,该属性指定一个对象,这个对象就是原型。

从构造函数部分咱们指定每个new出来的示例对象都有一个__proto__属性,这个属性指向构造函数的原型对象,若是咱们建立对象时经过prototype属性挂载一些方法和属性,那么实例对象就能够经过__proto__属性访问以前挂载的属性和方法,而每一个实例对象都是访问同一个原型对象这样就解决了工厂模式的第二个问题。

咱们把挂载在原型对象上的属性和方法叫作公有属性和公有方法(相似C#的static属性和方法),由于能够被全部实例对象访问;把在构造函数中经过this声明的属性和方法叫作私有属性和方法,由于它只属于一个实例对象。

能够经过一个例子看构造函数,实例对象和原型三者的关系

// 声明构造函数
function Person(name, age) {
    this.name = name;
    this.age = age;
}

// 经过prototye属性,将方法挂载到原型对象上
Person.prototype.getName = function() {
    return this.name;
}

var p1 = new Person('tim', 10);
var p2 = new Person('jak', 22);
console.log(p1.getName === p2.getName); // true

 

 经过图片能够看出构造函数和实例对象都指向原型对象,而原型constructor属性指向构造函数

若是构造函数中声明了和原型中同样的属性和方法,会优先访问实例对象中的属性和方法

function Person(name,age){
  this.name = name;
  this.age=age;
  this.getName=function(){
    console.log('this is constuctor.');
}     
      
}

Person.prototype.getName(){return this .name;};

var p1=new Person('Tim',12);
p1.getName();//this is constuctor.

咱们能够经过in判断一个对象是否拥有某个属性或方法无论这个属性或对象是否是挂载在原型对象上

 

function Person(name,age){
  this.name = name;
  this.age=age;
  this.getName=function(){
    console.log('this is constuctor.');
}     
      
}

Person.prototype.getName(){return this .name;};

var p1=new Person('Tim',12);

console.log('name' in p1);//true

比较经常使用的用法是判断页面是否在移动端打开

var isMobile='ontouchstart' in document;

有多个原型能够这样写

function Person(){}

Person.prototype.getName(){}
Person.prototype.getAge(){}
....

还有更简单的写法,可是这种写法是给Prototype新建了一个原型对象,并非原来的原型对象,须要显式指定constructor:Person

function Person(){}

Person.prtotype={
  constructor:Person,
  getName:function(){},
  getAge:function()(),
    ....      
}

 5、原型链

咱们指定每一个方法都有一个toString()方法,可是这个方法从哪里来的呢

先声明一个函数

function add() {}

能够用下图来展现这个函数的原型链

 

其中add是Function的实例,Function的原型又是Object的实例对象,这样就构成了一条原型链。经过__proto__属性,add能够访问原型链上面全部的属性和方法。

 

6、继承

通常咱们须要结合构造函数和原型来建立一个对象,因此当须要继承时也要考虑构造函数和原型的继承。

构造函数的继承

构造函数的继承只须要使用call/apply把父级构造函数调用一下就能够了,这样子对象就具备了父对象在构造函数中定义的属性和方法

    function Person(name, age) {
        this.name = name;
        this.age = age;
    }

    Person.prototype.getName = function () { return this.name; };
    Person.prototype.getAge = function () { return this.age; };

    function Student(name, age, grade) {
        Person.call(this, name, age);
        this.grade = grade;
    }

原型的继承须要把子对象的原型设置为父级的一个实例,加入原型链中

// 继承原型
Student.prototype = new Person(name, age);

// 添加更多方法
Student.prototype.getLive = function() {}

更好的方法是定义一个空对象,让这个空对象的__proto__属性指向父级对象的原型,并封装成一个方法

    function create(proto, options) {
        var tmp = {};//声明一个空对象
        tmp.__proto__ = proto; //把临时对象的__proto__属性指向父类的原型对象,使得子类能够访问原型链上的全部方法和属性
        Object.defineProperties(tmp, options); //把须要公开的属性和方法挂载再子类的原型tmp上面
        return tmp;
        };


    Student.prototype = create(Person.prototype, {
        constructor: { value: Student },
        getGrade: { value: function () { return this.grade; } }
    });

最后验证一下继承的正确性,完整代码

 function Person(name, age) {
        this.name = name;
        this.age = age;
    }

    Person.prototype.getName = function () { return this.name; };
    Person.prototype.getAge = function () { return this.age; };

    function Student(name, age, grade) {
        Person.call(this, name, age);
        this.grade = grade;
    }

    function create(proto, options) {
        var tmp = {};//声明一个空对象
        tmp.__proto__ = proto; //把临时对象的__proto__属性指向父类的原型对象,使得子类能够访问原型链上的全部方法和属性
        Object.defineProperties(tmp, options); //把须要公开的属性和方法挂载再子类的原型tmp上面
        return tmp;
        };


        Student.prototype = create(Person.prototype, {//除了使用本身封装的create方法能够用Object.create中现有的方法代替,和封装的方法是同样的
        constructor: { value: Student },
        getGrade: { value: function () { return this.grade; } }
    });

    var std1 = new Student("Nike", 13, 1);
    console.log(std1.getName()); //Nike
    console.log(std1.getAge()); //13
    console.log(std1.getGrade()); //1

 

7、属性类型

继承的时候用到了Object.defineProperties,这个方法能够用来设置属性类型。

属性类型顾名思义就是属性的类型,直白点说就是属性的属性,用来描述属性的特定

有几种属性类型,分别是:

1)configurable:表示改属性是否能被delete,默认为true,若是为false,其余属性类型不能被改变;

2)enumerable:是否可以枚举,即可否被for-in遍历,默认为true

3)writable:是否可以修改值,默认为true;

4)value:该属性的具体值是多少,默认undifined;

5)get:当访问person.name的时候get方法被调用,该方法能够定义返回的具体值是多少,默认为undifined;

6)set:当给person.name赋值时,set方法被调用,用于给name属性赋值,默认undifined;

须要注意的是value,writable不能与get,set同时设置,不然乎报错,且get\set应该成对设置,不然可能形成不能赋值或者不能设置值

可使用Object.difineProperty方法设置属性类型

configurable

    var tom = { name: 'Tom' };
    //利用delete上传name属性,返回true表示成功
    console.log(delete tom.name); //true

    //使用Object.defineProperty设置name属性
    Object.defineProperty(tom, 'name', { value: 'jack', configurable: false });

    console.log(delete tom.name);//false,已经不能删除
    console.log(tom.name); //jack
    tom.name = "nik";//尝试改变name的值也不能修改
    console.log(tom.name); //仍是jack

enumerable

    var tom = { name: 'Tom',age:11 };

    var keys = [];
    for (var k in tom) {
        keys.push(k);
    }

    console.log(keys);//["name","age"]

    //使用Object.defineProperty设置name属性
    Object.defineProperty(tom, 'name', { enumerable: false });

    var key2 = [];
    for (var k in tom) {
        key2.push(k);
    }
    console.log(key2); //["age"]

    //注意enumerable:false是指不能经过for-in循环来判断name属性在不在tom对象中,并非不能经过循环输出该属性的值,下面的语句依次输出tom,11
    ["name", "age"].forEach(function (item) { console.log(tom[item]) });

 

writable

var tom = { name: 'Tom',age:11 };


    console.log(tom.name); //Tom
    tom.name = "Jack";
    console.log(tom.name); //Jack,修改为功
    //使用Object.defineProperty设置name属性
    Object.defineProperty(tom, 'name', { writable: false });
    tom.name = "jone";
    console.log(tom.name); //Jack ,修改失败

 

value

  var tom = {  };

    Object.defineProperty(tom, 'name', { value: 'tom' });
    console.log(tom.name); //tom

get/set

 var tom = {  };

    Object.defineProperty(tom, 'name', {
        get: function () { return 'Tom'; },
        set: function (value) { console.log(  value + ' in set'); }
    });
    tom.name = 'jack';//jack in set
    console.log(tom.name);  //Tom

上面是一次设置一个属性的属性类型,可使用Object.defineProperties同时设置多个属性的属性类型

 var tom = {  };

    Object.defineProperties(tom, {
        name: {
            value: 'tom',
            configurable:false
        },
        age: {
            value: 12
        }
    });

    var descriptor = Object.getOwnPropertyDescriptor(tom, 'name'); //使用getOwnPropertyDescriptor获取某个属性的属性类型
    console.log(descriptor); //Object { value: "tom", writable: false, enumerable: false, configurable: false }
相关文章
相关标签/搜索