js是面向函数编程,更多的是面向过程,但是vue等框架更是面向对象。
面向过程编程
就是把解决问题所需要的步骤分析出来,然后通过函数把这些步骤一步步实现,使用的时候在一个个依次调用。
面向对象编程
把事务分解成一个个对象,然后由对象之间分工合作。面向对象编程是按照功能来划分的,而不是步骤。
适合大项目,在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工,其具有灵活、代码可复用、容易维护和开发的特点。
面向对象的特点:封装,继承,多态
面向对象里的构造函数
构造函数可以实现面向对象的封装特性,它的缺点是浪费内存。
具体来说,当我们封装的函数里面有函数方法时,每当我们通过构造函数创建新对象,构造函数会为每一个对象创建一个新方法,而这些新方法其实是相同的,因此浪费了内存。
原型
原型可以解决上述问题。
首先什么是原型?
js规定,每一个构造函数都有一个prototype属性,指向另一个对象,所以我们也称为原型对象;这个对象可以挂载函数,对象实例化不会多次创建原型上的函数,节约内存。我们把那些不变的方法,直接定义在prototype对象上,这样所有对象的实例都可以共享这些方法。构造函数和原型对象中的this都指向实例化的对象。
通过给Array构造函数的原型对象挂载新的方法,创建数组的求最大值max方法和求和sum方法。这样每一个实例化的数组都可以使用这些方法。
Array.prototype.max = function() {
console.log(this);
return Math.max(...this)
}
const data = [1,2,3,2,5,6,7,0]
console.log(data);
console.log(data.max());
Array.prototype.sum = function(){
return this.reduce((prev,next) => prev + next,0)
}
const a =data.sum()
console.log(a);
constructor
原型对象prototype里的一个属性,指向该原型对象的构造函数。
function Star() {
}
const xujiay = new Star()
console.log(Star.prototype);
console.log(Star.prototype.constructor === Star);
const zhoujielun = new Star.prototype.constructor()
console.log(zhoujielun);
console.log(xujiay);
对于多个需要挂载到prototype的方法,我们可以给原型对象采用对象形式赋值,但是这种赋值形式会覆盖构造函数原型对象里原来的内容,导致其失去constructor,失去constructor的后果就是我们无法判断此原型对象指向哪一个构造函数,因此需要主动添加一个指向构造函数的constructor。
function People() {
}
console.log(People.prototype);
// 这个现象形成的根本原因在于通过等号形式达到的是赋值的效
// 果,会覆盖掉被赋值对象以前的内容。
People.prototype = {
sing: function(){
console.log('changge');
},
run: function(){
console.log('zoulu');
}
}
console.log(People.prototype);
People.prototype = {
constructor: People,
sing: function(){
console.log('changge');
},
run: function(){
console.log('zoulu');
}
}
console.log(People.prototype);
这个现象形成的根本原因在于通过等号形式达到的是赋值的效果,会覆盖掉被赋值对象以前的内容。
对象原型
什么是对象原型,对象原型指的是实例对象中的一个属性,写作__proto__,在浏览器中打印出来的对象原型写作[prototype],对象原型指向构造函数的原型对象prototype,这也就是为什么实例化的对象可以使用构造函数prototype所挂载的属性和方法了;而通过protopyte的constructor属性可以使得自身与其构造函数相关联,形成逻辑闭环。
对象原型用来表明当前实例对象指向哪一个原型对象;对象原型里面也有一个constructor属性,指向创建该实例对象的构造函数,实际上这个__proto__就是对应的protopyte,他们在语义上等价。
function Animo() {
}
const dog = new Animo()
console.log(dog.__proto__ === Animo.prototype);
console.log(dog);
从汉语意义上我们这样理解:所谓的原型对象是指一个对象,他是“最初的”、“根本的”、“伊始的”、“原始的”、“原生的”对象,而这个对象就是protopyte,它是由自己的构造函数所孕育出来的,如果构造函数是女娲,那么原型对象就是女娲捏造的第一个人类,我们暂且叫这个人类为领导者,而后“女娲”捏造的泥娃我们称之为实例对象,而女娲为了使得领导者与所有人建立起关联,他把这个领导者命名为“原型对象”,并让所有人类都佩戴一个项链,这个项链就是对象原型__proto__,项链指向领导者。也就是说我们把对象原型__proto__理解为一个挂件,这个挂件指向原型对象这个对象,当人类(即实例对象)查看自己的挂件(即对象原型),就会发现它指向了领导者(即原型对象)。每个人类都可以通过查看自己的挂件,得到原型对象里包括constructor属性在内的所有属性和方法,因此人类就可以模仿领导者的行动和属性(即调用原型对象的属性或者方法),而这些属性和方法都是女娲(构造函数)所赋予的,所有实例对象都可以通过对象原型里的constructor 获取创造他们的女娲(即构造函数)了。
原型继承
什么是原型继承呢?原型继承指的是将不同构造函数相同的部分提出来为另一个函数,并令自身的原型对象等于此构造函数,这样这些函数就继承了一个公共构造函数。我们把拥有共同属性的构造函数称之为父构造函数,把继承了父构造函数的属性和方法的称之为子构造函数。
在利用原型继承抽象公共函数时,需要避免constructor的丢失,以及函数prototype指向混乱的问题。
现在我们有两个构造函数,一个叫“cat”,一个叫“dog”,他们有一个公共的属性叫type=Animo,为了方便函数定义,现在希望抽取这个属性到一个父类里面,通过继承的方式使得cat和dog获取这个类,于是我们可以这么做:
/* function Cat() {
this.type = 'Animo'
}
function Dog() {
this.type = 'Animo'
}
const cat = new Cat()
console.log(cat);
const dog = new Dog()
console.log(dog); */
const type = {
type: 'animo'
}
function Cat() {}
function Dog() {}
Cat.prototype = type
Dog.prototype = type
const cat = new Cat()
console.log(cat);
const dog = new Dog()
console.log(dog);
这样我们通过控制台输出可以发现,原本直接存在于实例对象里的属性type转移到了其对象原型__proto__身上。这样做有两个弊端,其一就是__proto__的属性constructor丢失,这个漏洞可以通过给原型对象追加constructor属性恢复。
const type = {
type: 'animo'
}
function Cat() {}
function Dog() {}
Cat.prototype = type
Dog.prototype = type
// 通过给原型对象追加constructor属性恢复
Cat.prototype.constructor = Cat
Dog.prototype.constructor = Dog
const cat = new Cat()
console.log(cat);
const dog = new Dog()
console.log(dog);
但是通过打印输出我们会发现两个实例对象的constructor都是Dog,这就是第二个弊端。也就是说通过Cat.prototype = type;Dog.prototype = type将对象直接赋值给了两个构造函数的原型对象,此时两个构造函数的原型对象同时指向同一个对象——type,因此无法通过简单的追加使得构造函数的原型对象恢复,因此上面的方法是错误的,这里需要指出的依然是——通过赋值的方式,两个构造函数的原型对象都只是获取了type对象的地址,任何对type、或Cat.pprototype或Dog.prototype的修改底层都是在修改type这一对象,这不是我们想要的结果,我们希望每一个构造函数都有唯 一 一 个指向自己的prototype。所以正确的方法是将公共属性抽象为一个父类构造函数,子类的构造函数的原型对象被实例化的父类构造函数赋值,即可得到不同地址的prototype对象。
function Type() {
this.type = 'Animo'
this.foot = 4
}
function Cat() {
this.fur = 'softable'
}
function Dog() {
this.nouse = 'fixable'
}
Cat.prototype = new Type()
Dog.prototype = new Type()
Cat.prototype.constructor = Cat
Dog.prototype.constructor = Dog
Cat.prototype.eat = function() {
console.log('the cat love fish');
}
const cat = new Cat()
console.log(cat);
const dog = new Dog()
console.log(dog);
以上过程就是封装、继承的过程。将公共属性封装,子构造函数通过原型对象继承父构造函数的属性;然后给原型对象挂载构造函数本身的方法。简言之,原型对象prototype上拥有一个构造函数的公共类属性和本构造函数所有的方法,构造函数内部只定义其特有的属性。
原型链
基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状结构,称之为原型链。
原型链-查找规则
当访问一个一个对象的属性(包括方法)时首先查找这个对象自身有没有这个属性或方法;如果没有就查找它的原型(也就是__proto__指向的prototype原型);如果还没有就查找原型对象的原型;以此类推直到查找到Object的原型对象null。__proto对象原型的意义在于为对象成员查找机制提供了一个方向,或者一条线路,可以使用instanceof运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上。
function Star() { }
const ldh = new Star()
console.log(ldh.__proto__ === Star.prototype) //true
console.log(ldh.__proto__)
console.log(Star.prototype)
console.log(Star.prototype.constructor === Star) //true
console.log(ldh.__proto__.constructor === Star.prototype.constructor) //true

450

被折叠的 条评论
为什么被折叠?



