面向对象(ECMAScript)
ECMAScript有两种开发模式:
- 函数化
- 面向对象(OOP)
- ECMAScript没有对象概念
- 面向对象的三个特征
- 封装:封装以前写好的方法
- 继承:继承一个对象的属性或方法
- 多态
- 对象是由属性和方法组成的:
- 属性对应变量
- 方法对应函数
面向对象的创建方法
字面量创建
适用于单个对象
var obj={
"name":"王甜甜",
"age":18,
"sex":"女",
"habby":function(){
console.log('学习!');
}
}
console.log(obj.name);
obj.habby();
缺点:复用率低,如果要创建多个对象,会代码冗余
实例创建
使用new Object创建一个实例对象
var obj1=new Object();
obj1.name='朱一龙';
obj1.sex='男'
obj1.work=function(){
console.log(this.name,'是一名普通演员。');//这里面的this代表对象本身
}
console.log(obj1.name);
obj1.work();
缺点:复用率低,如果要创建多个对象,会代码冗余
工厂模式创建
其实就是函数封装的效果,需要有返回值
function createObj(name,sex){
var obj=new Object();
obj.name=name;
obj.sex=sex;
obj.id=function(){
console.log('姓名:',this.name,'性别:',this.sex);
}
//局部变量,需要返回值
return obj;
}
var obj1=createObj('李峥','男');
var obj3=createObj('旺财','公');
console.log(obj1);
console.log(typeof obj1);//object
console.log(obj3);
console.log(typeof obj3);//object
可以看出,工厂模式创建的确解决了重复实例化的问题,但是还有一个问题,就是识别问题,很明显,‘李峥’和‘旺财’并不是同一个类型,一个是人,一个是狗,但是返回的结果都是object
构造函数创建
目的就是解决工厂无法识别的类型
- 构造函数创建对象,是使用最多的创建对象的方式,也是将所有的操作都封装起来在函数中,但是又和工厂模式创建又有些不同
- 构造函数的特点(要求)
- 首字母最好大写
- 没有明显的创建对象
- 直接将属性和方法都赋给this对象
- 必须使用new运算符
<script>
function Preson(name,sex){
//这里不用实例创建对象,直接就可以通过this添加属性和方法
this.name=name;
this.sex=sex;//这里的this指向的是window
this.id=function(){
console.log('姓名:',this.name,'性别:',this.sex);
//这里面的this指向的就是这个对象,是因为this在这里起了作业
}
}
var obj1=new Preson('朱一龙','男')
console.log(obj1);
console.log(typeof obj1);
var obj2=new Preson('狗剩','母')
console.log(obj2);
console.log(typeof obj2);
</script>
new在这里面起到了什么作用:
- 1.隐式创建了对象
- 2.改变了this的指向
- 3.隐式返回,不需要返回值
每有一个实例化对象,就要把原型分配一次,然后存储在不同的地方,所以即使他们的值相同,也不是一样的。构造函数的缺点也在这里,就是多个实例化对象,就要多次分配原型,会增加内存
function Stydent(name,sex){
this.name=name;
this.sex=sex;
this.id=function(){
console.log('确认身份,是学生')
}
}
var stu1=new Stydent('张三','17');
var stu2=new Stydent('王五','17');
stu1.id();
stu2.id();
console.log(stu1.id() == stu2.id());//true 两者输出的值是一样的
console.log(stu1.id == stu2.id);//false 对象不同,存储位置不同,
原型创建
**原型–prototype:**每一个函数都有原型,所有方法都放在原型中
**_ _ proto _ _:**是实例化对象中的一个属性,指向原型对象
<script>
function Preson(){
// 使用原型添加属性
Preson.prototype.name='黄金';
Preson.prototype.sex='女';
// 使用原型添加方法
Preson.prototype.id=function(){
console.log('123');
}
}
var per1=new Preson();
var per2=new Preson();
console.log(per1.name);
console.log(per2.name);
per1.id();
per2.id();
console.log(per1.id == per2.id);//true
//因为通过原型创建的,对象的方法和属性都是公用的
</script>
通过原型创建的对象,对象的方法和属性都是共用的,只会存储一次,因此可以解决浪费问题
原型也有缺点:原型创建不能传参,属性值都是固定的
混合模式创建
用构造函数+原型混合使用的方式构造对象
<script>
function Preson(name,sex){
this.name=name;
this.sex=sex
}
Preson.prototype.id=function(){
console.log('确认学生身份');
}
var per1=new Preson('暑假','nv');
console.log(per1.name);
per1.id();
var per2=new Preson('寒假','nan');
console.log(per2.sex);
per2.id();
console.log(per1.id == per2.id);
// 混合模式创建就是构造+原型
</script>
构造函数中放一直变化的属性和方法,原型中存储共享的属性和方法
命名空间
防止命名冲突,匿名函数自执行
- 匿名函数自执行
(function(){alert('自执行')})();
call&apply
两者的作用都是用来改变this的指向的
**区别:**传参方式不同
<div id="b"></div>
<script>
var obj1={
"name":"欧水",
"fun":function(){
console.log(this.name);
}
}
var obj2={
"name":"欧恩",
"fun":function(){
console.log(this.name);
}
}
obj1.fun();//欧水
obj2.fun();//欧恩
obj1.fun.call(obj2);//欧恩
obj1.fun.apply(obj1);//欧水
function ff(){
console.log(this);
}
ff();
ff.call(document.body);
ff.apply(document.getElementById('b'));
function ff2(a,b){
console.log(a,b)
}
ff2(2,4);
ff2.call(document.getElementById('b'),10,20)
ff2.apply(document.getElementById('b'),[10,20])
</script>
面向对象的继承
- 继承是指子类继承父类的属性和方法
原型链继承
- 就是将父亲的实例对象赋值给子类的原型对象
- 就是子类的原型继承了父类
- Son.prototype= new Parent(‘张三’,123);
<script>
// 父类
function Parent(name,age){
this.name=name;
this.age=age;
this.arr=[1,2,3];
}
Parent.prototype.fun=function(){
console.log(this.name,this.age)
}
// 子类
function Son(){}
// Son.prototype= new Parent();
// var son1=new Son('123',123);
// console.log(son1.name)//undefined
//子类不可以传参
Son.prototype= new Parent('张三',123);
var son1=new Son();
console.log(son1.name)
console.log(son1.arr);
son1.arr.push(4);
console.log(son1.arr);
var son2=new Son();
console.log(son2.arr);
son1.fun();
</script>
原型链继承的弊端
- 子类不能传参
- 如果继承了引用数据类型的,一改所有的、总的就会改,另一个子类继承父类,引用数据类型的数据就是改过的数据
对象冒充继承
- 继承属性
- 继承父类的构造函数
- 继承不了父类的prototype上的属性和方法
- 可以传参
- 继承的引用数据类型,改变他的数据,中的数据不会变,不会影响另一个子类继承
- Parent.call(this,name,age);
<script>
// 父类
function Parent(name, age) {
this.name = name;
this.age = age;
this.arr = [1, 2, 3];
}
Parent.prototype.fun = function () {
console.log(this.name, this.age)
}
// 子类
function Son(name,age) {
Parent.call(this,name,age);
}
var son1=new Son('申卡',123);
console.log(son1.name);
console.log(son1.arr);
son1.arr.push(5,5);
console.log(son1.arr);
var son2=new Son();
console.log(son2.arr);
// son1.fun() //报错
</script>
混合继承
- 对象冒充+原型链
- 既可以传参,也会继承父类的prototype的属性和方法
// 父类
function Parent(name, age) {
this.name = name;
this.age = age;
this.arr = [1, 2, 3];
}
Parent.prototype.fun = function () {
console.log(this.name, this.age)
}
// 子类
function Son(name, age) {
Parent.call(this, name, age);
}
Son.prototype=new Parent();
var son1 = new Son('申卡', 123);
console.log(son1.name);
console.log(son1.arr);
son1.arr.push(5, 5);
console.log(son1.arr);
var son2 = new Son();
console.log(son2.arr);
son1.fun()
</script>