js最重要也是最核心的东西就是对象了,入行这么长时间,一直对面向对象一知半解。网上有很多介绍对象对象的内容,这里也做了很多借鉴,
尤其是阮一峰老师的文章。我这里写的大多例子都是阮一峰老师文章的例子,但是加上了我自己的见解。JavaScript面向对象编程
js最核心的东西就是对象,万物皆对象。对象分为普通对象和函数对象。分区是看是否有function关键字。
为什么会出现面向对象编程?
场景:如果我们把属性和方法封装成一个对象,或者从原型对象上生成一个实例对象,我们如何做?
技术大佬们为了让技术界更核心,开始了他们的研究:
第一:原始模式(最古老的模式)
这里我们创建一个对象Cat,把它当做其他对象的原型。其他对象都按照这个模式来创建。
然后我们在下方创建了cat1和cat2。
但是问题是我们看不到cat1和cat2之间的关系,最重要的是如果有n的cat,我们岂不是要写到天荒地老?
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]) // 大毛 // 黑色 // 二毛 } }
学到这里,让我们回想一下以下几个概念:
1、什么是构造函数?
2、什么是实例对象?它是如何创造出来的?
3、实例对象的constructor指向什么?
4、构造函数的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
1、cat1通过new Cat()创建。
2、cat1.constructor = Cat;
3、Cat.prototype.constructor = Cat;
4、cat1.__proto__ = Cat.prototype;
再次声明:
这里我主要借鉴了阮一峰老师的JavaScript面向对象编程,加上我我自己的简介。对理解面向对象感觉熟悉很多。希望能帮助大家。