javaScript构造函数、原型、面向对象编程

js最重要也是最核心的东西就是对象了,入行这么长时间,一直对面向对象只知其一;不知其二。网上有不少介绍对象对象的内容,这里也作了不少借鉴,javascript

尤为是阮一峰老师的文章。我这里写的大多例子都是阮一峰老师文章的例子,可是加上了我本身的看法。JavaScript面向对象编程html

 

 

 

js最核心的东西就是对象,万物皆对象。对象分为普通对象函数对象。分区是看是否有function关键字。java

为何会出现面向对象编程?git

场景:若是咱们把属性和方法封装成一个对象,或者从原型对象上生成一个实例对象,咱们如何作?github

技术大佬们为了让技术界更核心,开始了他们的研究:编程

 

第一:原始模式(最古老的模式)ide

这里咱们建立一个对象Cat,把它当作其余对象的原型。其余对象都按照这个模式来建立。函数

而后咱们在下方建立了cat1和cat2。this

可是问题是咱们看不到cat1和cat2之间的关系,最重要的是若是有n的cat,咱们岂不是要写到天荒地老?spa

 

 1 let Cat = {
 2     name:'',
 3     color:''
 4 }
 5 // 而后根据这个规格来实现两个实例对象。
 6   let cat1 = {};
 7     cat1.name = '大毛';
 8     cat1.color = '黄色';
9   let cat2 = {}; 10 cat2.name = '二毛'; 11 cat2.color = '黑色';

 

因此,进化到了封装成一个函数

 

第二:原始模式改进

咱们把Cat封装为一个函数,咱们每次只须要调用该函数就行。

问题是,咱们依然看不出cat1和cat2之间的关系?不能一眼看出他们之间的关联。

function Cat(name,color){
    return {
        name:name,
        color:color
    }
}
let cat1 = Cat('大毛','黄色');
let cat2 = Cat('二毛','黑色');
console.log(cat1);//{ name: '大毛', color: '黄色' }
console.log(cat2);//{ name: '二毛', color: '黑色' }

 

 

第三:构造函数(重点!!!)

为了解决从原型对象上生成实例的问题,js开发人员给咱们提供了一个构造函数模式。

那么什么是构造函数呢?

 

先了解几个概念:

构造函数(constructor):其实就是一个普通的函数,只是内部使用了this关键字。(这里咱们记住构造函数的英文--Constructor

实例对象:经过new操做符生成的对象就是实例对象。而且this变量会绑定在实例对象上。(记住:生成的是实例对象!!)

constructor:构造函数生成的实例对象,会有一个constructor属性,指向该构造函数。(咱们能够理解为,实例对象是从构造函数上衍生出来的,固然要有它的烙印了!)

// 咱们先建立一个构造函数Cat
 function Cat(name,color){
     this.name = name;
     this.color = color;
 }
//  用new生成Cat的实例。cat1和cat2
 let cat1 = new Cat('大毛','黄色');
 let cat2 = new Cat('二毛','黑色');
//  看一下输出结果
 console.log(cat1);//Cat { name: '大毛', color: '黄色' }
 console.log(cat2);//Cat { name: '二毛', color: '黑色' }

咱们看一下经过构造函数Cat建立出来的实例对象cat1和cat2身上的constructor属性:

//  我使用的是构造函数生成的实例(Constructor),因此实例上得有个人烙印。属性名称就是Constructor
// 指向的就是他们的构造函数。须要让别人知道,是谁建立了你。
console.log(cat1.constructor === Cat);//true
console.log(cat2.constructor === Cat);//true

看到这里,咱们已经明白了什么是构造函数,什么是原型对象,什么是实例对象,以及实例对象的constructor属性。

奖励一下本身!

看到这里,说明咱们已经成功了一半了!

 

咱们知道了构造函数和实例对象的关系,那咱们如何验证呢?

js提供了一个instanceof运算符。instance(例子的意思)。从字面量就能看出某某是某某的实例。

看一下下面的代码就会很清晰了。

console.log(cat1 instanceof Cat);//true

咱们了解了instanceof,那你们脑海里还知不知道js提供的其余两种验证的方法呢?建议你们看一下js秘密花园,里面讲解的不少东西很清晰。

除了instanceof,咱们还了解到typeof,以及Object.prototype.toString方法。

typeof其实没什么做用,不能验证类型(验证类型不够完全),只能验证一个对象或者一个变量是否存在。是一个垃圾产品。

instanceof的做用就是咱们上面提到的验证构造函数之间的关系。

Object.prototype.toString则是咱们常用的方法,来验证类型。先看一下下面的例子:

console.log(Object.prototype.toString.call([]));//[object Array]
console.log(Object.prototype.toString.call({}));//[object Object]
console.log(Object.prototype.toString.call(null));//[object Null]
console.log(Object.prototype.toString.call(undefined));//[object Undefined]
console.log(Object.prototype.toString.call(function f(){}));//[object Function]
// 而后咱们能够根据slice方法来截取字符串
// slice(8,-1)表示从第八位开始,截取到倒数第一位(负数从后往前截取。)。
console.log(Object.prototype.toString.call([]).slice(8,-1));//Array

经过熟悉,咱们又知道了js存在的三种验证类型。

 

继续接着咱们构造函数开始:

构造函数的方法很好用,可是存在一个浪费内存的问题,这句话怎么理解呢?先看个例子。

咱们给构造函数Cat内部多增长一个type属性,以及eat方法(运行在对象上的函数成为方法)。

而后咱们根据Cat生成两个实例对象,cat1和cat2。根据输出结果咱们发现,都会存在type属性和eat方法。

若是说我生成的实例上面不须要这个属性或这个eat方法,这就存在了浪费内存的问题了。

 function Cat(name,color){
     this.name = name;
     this.color = color;
     this.type = '猫科动物';
     this.eat = function(){
         console.log('吃老鼠');
     };
 }

 let cat1 = new Cat('大毛','黄色');
 let cat2 = new Cat('二毛','黑色');
 console.log(cat1);//Cat { name: '大毛', color: '黄色', type: '猫科动物', eat: [Function] }
 console.log(cat2);//Cat { name: '二毛', color: '黑色', type: '猫科动物', eat: [Function] }
//  为何是false?由于cat1和cat2是两个不一样的实例对象,而对象的比较是内存的比较,不是值的比较。
console.log(cat1.eat === cat2.eat);//false

 

那咱们如何解决呢?

 

第五:prototype

js规定,每一个构造函数都有一个prototype属性,指向另一个对象。(咱们这里能够把它当作一个虚无的对象A)。A对象上面的全部属性和方法,都会被构造函数

的实例继承。因此这就意味着,咱们能够把这些不变的属性和方法放到这个对象A上面。

 咱们继续使用构造函数Cat的例子:

 function Cat(name,color){
     this.name = name;
     this.color = color;
 }
 Cat.prototype.type = '猫科动物';
 Cat.prototype.eat = function(){
     console.log('吃老鼠');
 }
//  继续生成实例
let cat1 = new Cat('大毛','黄色');
let cat2 = new Cat('二毛','黑色');
console.log(cat1.type);  //猫科动物
// 实例对象cat1和实例对象cat2上面的eat方法,都是继承过来的,都指向咱们所说的对象A上面。
// 因此它们的指针是同样的。
console.log(cat1.eat === cat2.eat);//true
console.log(cat1.type === cat2.type);//true

 

 到了这里,咱们知道了每一个构造函数上面都会有一个prototype属性。

就以咱们建立的构造函数Cat来讲,就至关于 let A = Cat.prototype;

这个A就是一个对象,咱们Cat.prototype.type = "猫科动物",其实就是给该对象新增属性罢了。

 

isPrototype 

 那咱们如何判断呢?还记得咱们上面说过的instanceof,这里有一个新的方法,isPrototype,来判断某个prototype对象(A)和某个实例(cat)之间的关系。

console.log(Cat.prototype.isPrototypeOf(cat1));

 

hasOwnProperty() 

 常用for...in循环的应该知道该属性,用来过滤继承的属性。

function Cat(name, color) {
    this.name = name;
    this.color = color;
}
// 咱们在其原型上增长两个属性
Cat.prototype.type = "猫科动物";
Cat.prototype.address = "杭州";
let cat1 = new Cat("大毛", "黑色");
cat1.lover = '二毛';
for (let i in cat1) {
    console.log(cat1[i]);
    // 输出结果是以下,也就是说把继承的属性和自身的属性所有遍历出来了(这就是咱们不多使用for in 循环的缘由。)
    // 大毛
    // 黑色
    // 二毛
    // 猫科动物
    // 杭州
}
// 而后咱们使用hasOwnPrototype()来过滤
for (let j in cat1) {
    if (cat1.hasOwnProperty(j)) {
        console.log(cat1[j])
        // 大毛
        // 黑色
        // 二毛
    }
}

 

学到这里,让咱们回想一下如下几个概念:

一、什么是构造函数?

二、什么是实例对象?它是如何创造出来的?

三、实例对象的constructor指向什么?

四、构造函数的prototype属性是什么?

 

答案:

构造函数是一个普通函数,函数内经过this关键字来声明变量。

实例对象同构new+构造函数生成的。构造函数的this指向生成的实例对象。

实例对象的constructor属性指向构造函数。

构造函数的prototype属性指向的是一个对象,该对象上面的全部属性和方法,都会被构造函数建立的实例对象所继承。

 

下面开始咱们的最重要部分,了解后面的知识,咱们差很少了解了原型,了解了构造函数,了解了面向对象

 先看下面的例子:

给构造函数Cat的原型对象上新增三个属性。

function Cat(name,color){
    this.name = name;
    this.color = color;
}
Cat.prototype.address = '杭州';
Cat.prototype.lover = '小翠';
Cat.prototype.type = '猫科动物';

看到这里,会有小伙伴问,为何不写在一块儿?那咱们改变一下:

function Cat(name,color){
    this.name = name;
    this.color = color;
}
Cat.prototype = {
    address:'杭州',
    lover:'小翠',
    type:'猫科动物'
}
// 等价于
// Cat.prototype.address = '杭州';
// Cat.prototype.lover = '小翠';
// Cat.prototype.type = '猫科动物';

看到这里是否是会明白点什么?Cat.prototype就是咱们建立的原型对象,也就是咱们上面提到的A。

其实它还有一个默认的属性,constructor属性。

这种状况下,全部的原型对象都会自动得到一个constructor(构造函数)属性,这个属性指向prototype所造的函数。

翻译过来就是,A有一个默认的属性,constructor。这个属性是一个指针,指向Cat。

 

Cat.prototype.constructor = Cat;

看到constructor是否是感受很熟悉?

咱们以前也提到过,每一个构造函数生成的实例上面都有一个constructor属性,指向该构造函数。

对比一下下面的代码,看看有什么发现?

cat1.constructor = Cat;
Cat.prototype.constructor = Cat;

 

cat1有constructor咱们知道,由于它是Cat的实例对象。

那么Cat.prototype是怎么回事呢?

其实Cat.prototype也是一个实例对象。咱们看一下下面的代码

function Cat(){

};
console.log(Cat.prototype);//Cat {}
let cat1 = new Cat();
console.log(cat1);//Cat {}

发现什么没?Cat.prototype其实就是Cat的一个实例对象!!!因此它也有constructor属性!!!!!

 

 

_proto_

js建立对象的时候,会有一个_proto_的内置属性,用于指向建立它的构造函数的原型对象。

function Cat(){};
let cat1 = new Cat();
// 实例对象上面的__proto__指向的是构造函数的原型
console.log(cat1.__proto__ == Cat.prototype);

 

 

总结一下吧:

构造函数Cat

实例对象cat1

 

一、cat1经过new Cat()建立。

二、cat1.constructor = Cat;

三、Cat.prototype.constructor = Cat;

四、cat1.__proto__ = Cat.prototype;

 

再次声明:

这里我主要借鉴了阮一峰老师的JavaScript面向对象编程,加上我我本身的简介。对理解面向对象感受熟悉不少。但愿能帮助你们。

相关文章
相关标签/搜索