面向对象(OOP)
构造函数
创建对象
1.字面量创建对象
var obj={
name:'jack',
age:18,
sayHi:function(){console.log('hello')}
}
2.内置构造函数创建对象
var obj= new Object()
obj.name='jack'
obj.age=18
obj.sayHi=function(){console.log('hello')}
3.工厂函数创建对象
//创建工厂函数
function createObj(){
var obj={}
obj.name='jack'
obj.age=18
obj.sayHi=function(){console.log('hello')}
return obj
}
//使用工厂函数创建对象
var obj1=creatObj()
4.自定义构造函数创建函数
//制造一个自定义构造函数
function CreateObj(name,age){
this.name=name
this.age=age
this.sayHi=function(){console.log('hello')}
}
var obj1=new CreateObj()
1.构造函数在使用时需要和new关键字连用,才有自动创建和返回对象的能力,不连用没有意义 就是调用一个普通函数
new构造函数的时候,会在构造函数隐形的创建一个空对象
会将构造函数中的this指向当前这个空对象
执行构造函数中的代码
将这个对象返回
2.调用构造函数时,如果不需要传参可以不写后面的小括号,传参则必须小括号
3.CreateObj里面的this指向实例obj1,向自动创建出来的对象里添加内容
4.构造函数内部不要写return,return一个基本数据类型时没有效果,return无效
如果return一个复杂数据类型,则返回该数据,构造函数无效
5.构造函数创建对象,在原型添加方法时,作用域改变导致this改变,则需要定义一个变量储存this
构造函数的缺陷
使用构造函数创建对象,需要向对象上添加方法时,只要创建一次对象(new 一次)就会有一个函数在占用空间,创建100次对象就会开辟100处内存存放一模一样的函数,其中有99处为重复的,没有存在必要。
原型
对象
1.每一个对象,在你访问他的成员的时候,如果自己没有这个属性就会自动去所属的构造函数prototype上查找
2.自定义构造函数创建的对象也是对象,当你访问某一个成员的时候,如果没有,也会自动去所属构造函数的原型上查找
3.哪一个构造函数创建对象,这个对象就属于哪一个构造函数
4.因为构造函数在创建对象的过程,我们起名为实例化的过程,创建出来的对象叫做这个构造函数的一个实例化对象
prototype
1.每一个函数天生自带的一个属性,叫做prototype,是一个对象数据类型
2.构造函数也是函数,也会有这个自带的空间prototype
3.当你需要给实例对象添加方法,直接书写在构造函数体内,每次创建实例都会创建一个函数数据类型,多个函数方法一模一样,占据了多份储存空间
4.把需要添加给实例的方法,放在构造函数的原型(prototype)上,不会开辟新的空间,且实例可以进行访问使用
5.对原型对象进行修改时,不能覆盖只能添加
Array.prototype={} //覆盖数组原型对象上的所有方法
Array.prototype.xxx=function(){} //对原型对象添加方法
__proto__原型指针
1.每一个对象天生自带一个属性__proto__,是一个对象空间,指向所属构造函数的prototype
2.当你访问对象的成员的时候,首先在自己身上查找,如果没有,自动去到__proto__上查找
原型链
用__proto__ 串联起来的对象链状结构,每一个对象数据类型,都有一个属于自己的原型链,为了访问对象成员
constructor构造器
对象的__proto__ 里面的一个成员。这个属性就是指向当前这个对象所属的构造函数
实例对象.constructor可以访问构造函数
instanceof
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
对象访问机制
当你需要访问对象的成员时,首先在自己身上查找,如有直接使用,
没有会自动去__proto__ 上查找,
如果还没有,就在去__proto__ 上查找,
直到Object.prototype都没有,那么返回undefined
原型指向
1.实例对象身上的__proto__ 指向
所属构造函数的prototype
2.构造函数.prototype 的__proto__指向
构造函数.prototype是一个对象数据类型(Object)
在JS中所有的Object数据类型都是属于Object这个内置构造函数
构造函数.prototype是属于Object这个内置构造函数的
构造函数.prototype的__proto__指向Object.prototype
3.构造函数的__proto__指向
构造函数是一个函数,函数本身也是对象,就会有__proto__
在JS中,所有函数都是属于内置构造函数Function的实例
构造函数.__proto__指向Function.prototype
4.Object.prototype的__proto__指向
Object.prototype在JS内叫做顶级原型,不存在__proto__了
Object.prototype的__proto__指向null
5.Object.__proto__指向
Object是一个内置的构造函数,同时也是一个函数,也是一个对象
在JS中,所有的函数都属于内置构造函数Function的实例
Object也是Function的实例
Object.的__proto__指向Function.prototype
6.Function.prototype的__proto__指向
Function.prototype也是一个对象数据类型
只要是对象数据类型都是Object的实例
Function.prototype的__proto__指向Object.prototype
7.Function.的__proto__指向
Function也是一个内置构造函数,也是一个函数
在JS内,所有的函数都是属于内置构造函数Function的实例
Function自己是自己的构造函数
Function自己是自己的实例对象
Function所属的构造函数是Function本身
继承
本质是子类使用父类的实例做原型(子类的原型是父类的实例)
继承和构造函数相关的应用是指让一个构造函数去继承另一个构造函数的属性和方法
所以继承一定出现在两个构造函数之间
继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有先执行子类的
如果子类没有,就去查找父类有没有这个方法,如果有,就执行父类的这个方法
class语法糖
class Son extends Father{ }
//子构造函数继承父构造函数的属性和方法
super()
super关键字用于访问和调用对象父类上的函数。可以调用父类的构造函数,
也可以调用父类的普通函数(子构造函数调用父构造函数 访问父构造函数中的this)
class Son extends Father{ //子类继承父类
constructor(x,y){
super(x,y) //调用父构造函数
super.say() //调用父类方法
//code //定义子类独有属性
}
}
子类在构造函数中使用super,必须放在this前面(必须先调用父类的构造函数,在使用子类构造方法)
对象之间的”继承”的其他五种方法。
比如,现在有一个”动物”对象的构造函数。
function Animal(){
this.species = "动物";
}
还有一个”猫”对象的构造函数。
function Cat(name,color){
this.name = name;
this.color = color;
}
一、 构造函数绑定
第一种方法也是最简单的方法,使用call或apply方法,将父对象的构造函数绑定在子对象上,即在子对象构造函数中加一行:
function Cat(name,color){
Animal.apply(this, arguments);
this.name = name;
this.color = color;
}
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物
二、 prototype模式
第二种方法更常见,使用prototype属性。
如果”猫”的prototype对象,指向一个Animal的实例,那么所有”猫”的实例,就能继承Animal了。
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物
三、 直接继承prototype
第三种方法是对第二种方法的改进。由于Animal对象中,不变的属性都可以直接写入Animal.prototype。所以,我们也可以让Cat()跳过 Animal(),直接继承Animal.prototype。
现在,我们先将Animal对象改写:
function Animal(){ }
Animal.prototype.species = "动物";
然后,将Cat的prototype对象,然后指向Animal的prototype对象,这样就完成了继承。
Cat.prototype = Animal.prototype;
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物
与前一种方法相比,这样做的优点是效率比较高(不用执行和建立Animal的实例了),比较省内存。缺点是 Cat.prototype和Animal.prototype现在指向了同一个对象,那么任何对Cat.prototype的修改,都会反映到Animal.prototype。
四、 利用空对象作为中介
由于”直接继承prototype”存在上述的缺点,所以就有第四种方法,利用一个空对象作为中介。
var F = function(){};
F.prototype = Animal.prototype;
Cat.prototype = new F();
Cat.prototype.constructor = Cat;
F是空对象,所以几乎不占内存。这时,修改Cat的prototype对象,就不会影响到Animal的prototype对象。
alert(Animal.prototype.constructor); // Animal
我们将上面的方法,封装成一个函数,便于使用。
function extend(Child, Parent) {
var F = function(){};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
Child.uber = Parent.prototype;
}
使用的时候,方法如下
extend(Cat,Animal);
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物
五、 拷贝继承
function Animal(){}
Animal.prototype.species = "动物";
然后,再写一个函数,实现属性拷贝的目的。
function extend2(Child, Parent) {
var p = Parent.prototype;
var c = Child.prototype;
for (var i in p) {
c[i] = p[i];
}
c.uber = p;
}
这个函数的作用,就是将父对象的prototype对象中的属性,一一拷贝给Child对象的prototype对象。
使用的时候,这样写:
extend2(Cat, Animal);
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物
拷贝
对象的拷贝分为浅拷贝和深拷贝
浅拷贝
1.for in遍历对象并赋值到新的对象
2.Object方法糖
Object.assign(newObj,oldObj)
深拷贝
使用递归拷贝
函数封装
function deepCopy(newobj,oldobj){
for(let k in oldobj){
//获取属性值oldobj[k]
let item=oldobj[k];
if(item instanceof Array){ //数组也属于对象,先判断数组
newobj[k]=[];
deepCopy(newobj[k],item)
}else if(item instanceof Object){
newobj[k]={};
deepCopy(newobj[k],item)
}else{
newobj[k]=item;
}
}
}
总结
OPP三大特性
封装 继承 多态
封装
变量+表达式=>函数
变量/属性+函数/方法=>类
隐藏类的实现细节,可以方便加入逻辑控制性,限制对属性的不合理操作,便于修改增强代码的可维护性,提高代码复用性
继承
在原型复用的机制上,实现类与类的父子关系
子类一行代码都不写,默认获得父类的所有属性和方法(本质上是得到其共享内存的访问权限)
继承的好处:
便于维护 修改公共属性/方法,只需要在父类中修改一次,所有子孙类都会被同步
不满足于父类实现的时候,可以通过重写(覆写/覆盖/override)
扩展带来了多态
多态
一个父类有许多不同的子类实现
既有共性又有个性