JS之三面向对象
概述
1.面向对象的概念
2.对象的创建方法
3.面向对象继承
4.私有变量
5.模块化演变
1.面向对象的概念
2.对象的创建方法
1.工厂的方式创建对象
2.构造函数创建对象
3.原型创建对象
4.混合模式:原型+构造函数(推荐)
5.稳妥构造函数模式
1. 工厂的方式创建对象
function createPerson(name,age,job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
}
return o;// 如果是构造函数执行模式,如果返回的是一个引用类型,就把引用类型返回。
// 如果是简单类型,那么就返回this
}
var person1 = createPerson('Grey',27,'Doctor');
函数 createPerson()每次它都会返回一个包含三个属性一个方法的对象
优点:可以进行批量的创建 都有公共默认值和属性的对象。
缺点:
1、对象的原型不能确认具体的类型、对象的构造函数也不明确。
2、对象的方法不能重用,每个对象的内存中都存储一份函数对象的内存。
2.构造函数创建对象
function Cat(age, name) {
this.age = age;
this.name = name;
this.run = function() {
console.log(this.name + ' running....');
};
// 通过构造函数创建一个对象。
var c1 = new Cat(19, 'dd');
c1.age = 20; // 修改对象的属性值。
c1.run();
var c2 = new Cat(22, 'ss');
// 当使用new来调用构造函数时
// 1、创建一个空对象
// 2、把空对象赋值给this
// 3、执行构造函数的里面代码,并给this的属性做赋值初始化
// 4、把新创建的对象返回(如果有返回值,返回值是简单类型会直接忽略,还是返回this),如果是引用类型直接返回引用对象。
优点:
1、创建对象的时候默认初始化一些属性。
2、可以进行使用instanceof追溯对象的原型及构造函数。
c1 instanceof Cat; // true
c1.constructor === Cat
缺点:
对象的方法不能进行重用,每个对象里面都要存储一份方法对象。浪费内存。
3.原型创建对象
<script>
function Cat() {
this.age = 19;
// 如果需要共享的方法和属性咱们一般放到 原型中定义。
// this.run = function() {
// console.log('run');
// }
}
// 原型中定义属性和方法
Cat.prototype.run = function() {
console.log(this.name, this.age);
};
// 问题: 私有的属性希望每个对象私自拥有。
Cat.prototype.name = 'black cat!'; // 所有的新对象都共享这个一个属性。
var c1 = new Cat(); // c1 有run方法
var c2 = new Cat(); // c2 有run方法
console.log(c1.name); // black cat!
console.log(c2.name); // black cat!
c1.run(); // black cat! 19
console.log( c1.run === c2.run); // true
c1.name = 'good cat!'; // 对象的属性分为:读取和设置两种模式。
// 如果读取:自己没有这个属性,那么去原型上找,直到找到位置。 如果找不到undefined
// 如果是写入:那么自己没有这个属性,那么直接添加一个自己属性。
console.log(c1.name , c2.name);
// good cat! black cat!
</script>
优点:
所有原型上的属性和方法在所有的新对象中可以进行共享。
缺点:
如果对象需要自己特有的属性值,不与其他对象共享,则必须跟构造函数模式进行配合
4.混合模式:原型+构造函数
<script>
function Cat(age, name) { // 一般构造函数的首字母大写,我也称是Cat类。
this.age = age; // 每个对象都有自己私有的属性值的属性,放到构造函数中。
this.name = name;
}
// 一般类型的方法,都放在原型上,让所有的对象都共享方法的内存。
Cat.prototype.run = function() {
console.log(this.name + ' running....');
}
var c1 = new Cat(19, 'jimi');
var c2 = new Cat(19, 'Dimk');
// c1.run === c2.run // => true
c1.age = 20; // c2.age = 19
</script>
组合使用构造函数模式与原型模式
公共的属性和方法放到 原型上,独有的属性使用构造函数模式,放到对象自己身上。
优点:
既保证了方法等共享的属性能只在内存中保存一份,节省内存。
又可以实现每个对象有自己单独存放的属性。是一种经典的构建对象的方法
5.稳妥构造函数模式(不使用new)
<script>
function Cat() {
var o = {};
o.age = 10;
o.name = 'laoma';
o.run = function() {
console.log(o.name + ' running....');
};
return o; // 如果是构造函数执行模式,如果返回的是一个引用类型,就把引用类型返回。
// 如果是简单类型,那么就返回this
}
// 稳妥构造函数模式,要实现使用new构造一个对象和不使用new构造一个对象效果一样。
var c1 = new Cat();// 构造函数调用模式
var c2 = Cat(); // 函数调用模式
// 缺点: 不能溯源 原型、构造函数 、 对象的方法内存不能共享,浪费内存。
</script>
优点:
可以共享属性和方法的初始化代码。
无论用户是否用了new还是没有使用new都会被正确的返回新的对象
缺点:
无法追溯对象的原型和构造函数,默认没有公共的属性和方法,内存浪费。
3.面向对象继承
1.原型继承的方式
借用构造函数继承
组合继承
原型式继承
寄生继承方式
终极方式:寄生组合的方式
1.原型继承的方式
<script>
// 动物基类
function Animal(age, name) {
this.age = age;
this.name = name;
this.foods = ['鱼', '鸟', '水果'];
}
// 在动物基类的原型上添加方法run
Animal.prototype.run = function() {
console.log(this.name + ' running....');
}
function Cat(age, name) {
this.age = age;
this.name = name;
}
// 原型的继承方式。
// Cat.prototype.constructor === Cat
Cat.prototype = new Animal(); // 父类的构造函数:执行第一次。
Cat.prototype.constructor = Cat; // 因为上面的代码把Cat的prototype指向了Animal
var c = new Cat(19, 'sss'); //希望:cat继承animal的属性和方法
c.run(); // 从Animal原型上继承的方法。
c.foods.push('栗子');
var c2 = new Cat(20, 'ssfdd');
// c2.foods;// ['鱼', '鸟', '水果', '栗子'];
// 问题:
// 1、子类的构造函数的参数,没法传递给父类的构造函数。
// 2、子类的原型的constructor会被改变,需要自己改回来。
// 3、如果父类里有引用类型的属性,那么所有的子类会共享这个引用类型。
</script>
2.组合继承
// 父类
function Animal(age, name) {
this.age = age;
this.name = name;
this.foods = ['水', '苹果'];
}
// 在父类的原型上创建一个run方法
Animal.prototype.run = function() {
console.log(this.name + ' running');
};
// 定义子类
function Cat(age, name) {
// Animal(age, name); // this === window; 函数执行模式,this === window
// this == c
// 第一次执行父类的构造函数。
Animal.call(this, age, name); //借用父类的构造函数,给子类创建实例属性。
}
// 第二次执行父类的构造函数。
Cat.prototype = new Animal(); // 组合原型继承模式
Cat.prototype.constructor = Cat;
var c = new Cat(19, 'deail');
组合模式就是经典模式,解决了原型模式的两大问题。
组合继承模式:用原型继承原型属性和方法。用构造函数模式继承实例的属性和方法,非常经典的用法。
缺点:父类的构造函数需要执行两次。一次:设置原型对象,二次:构造函数借用时需要再执行一次
3.原型式继承
<script>
// o就是要借用的对象。
function object(o) {
function F() {}
F.prototype = o; // 让空函数的原型指向 o对象。
return new F(); // 创建一个f实例,f的内部原型指向o对象。
}
var m = {age : 19, name: 'laoma', friends: ["laoma2", "jimik" ]};
var m1 = object(m);
console.log(m1.friends);
m1.age = 20;
console.log(m1.age);
// 优点:
// 不需要使用new构造函数就可以直接 构造另外其他对象。
// 缺点:
// 所有构造出来的实例会共享 原型对象上的引用类型的属性。
// EC5
// Object.create();
</script>
原型式继承是避免调用父类构造函数的一种巧妙的方式。本质就是借用对象来构造另外一个对象。
缺点:原型对象上的引用类型的属性会造成子类对象进行共享。
4.寄生继承模式
寄生式继承是原型式继承的加强版。
寄生继承模式是在原型式继承模式上增强原型对象的增强模式。只是对原型式继承的扩展而已。
寄生继承类似一个工厂模式,工厂内部把原型对象进行构造出另外一个实例,并对构造出来的实例进行增强,最后返回这个实例。
// 寄生继承方式: 其实就是传一个对象到一个方法(工厂方法),方法内部
// 根据传来的对象构造一个新对象,并对新对象进行扩展增强,
// 返回新对象。
function createPersion(p) {
var o = object(p); // 同p对象构造一个新对象o
o.say = function() { // 对我新构造出来的对象o进行扩展
console.log('hi');
}
return o;
}
</script>
5.寄生组合继承模式
由于组合继承模式需要执行两次的父类的构造函数,使用寄生继承模式替换原型继承模式,那么就可以实现父类的构造函数只执行一次的效果,是目前认为最经典的继承模式,最好的继承模式。YUI库也是大量使用了这种继承模式。
<script>
// 寄生组合继承模式
// 组合了:寄生继承模式和借用构造函数继承模式。
// 父类
function Animal(age, name) {
this.age = age;
this.name = name;
this.foods = ['水果','ls'];
}
// 父类原型上的方法:通过寄生继承的方式进行继承.
Animal.prototype.run = function() {
console.log(this.name + ' runnging');
};
function Cat(age, name) {
// 使用借用构造函数继承模式来构建对象的实例属性。
Animal.call(this, age, name);
}
// Cat.prototype = new Animal(); // 多执行了一次父类的构造函数。
// 寄生继承的方法
Cat.prototype = inheritFrom(Animal.prototype);
var c1 = new Cat(19, 'laom');
var c2 = new Cat(19, 'laom2');
c1.run();
c2.run();
// 寄生继承模式。
function inheritFrom(o) {
var t = object(o);
t.constructor = Cat; // 把Cat原型的构造函数指回Cat构造函数。
return t;
}
// 原型式继承的方法。
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
</script>
4.私有变量
JavaScript中并没有私有变量的概念,但是我们可以通过某些方式进行模拟。所谓的私有变量就指:对象的某个属性只能通过对象的方法进行访问,不能直接通过对象进行访问。
构造函数模拟私有变量的方式
其他模拟方法
第一种模拟私有变量的方式
function Persion() {
var age = 0; // 私有变量,只能通过getAge和setAge来操作age变量。
this.getAge = function() {
return age;
}
this.setAge = function(a) {
age = a;
}
}
var p = new Persion();
p.setAge(90);
// 访问p的年龄
console.log(p.getAge());//90
第二个种模拟私有变量的方式
function Persion() {
var age = 0;
return {
getAge: function() {
return age;
},
setAge: function(num) {
age = num;
}
}
}
var p = Persion(); // 创建一个persion对象。
p.setAge(20);
console.log(p.getAge());
5.模块化演变
JS开发的灾难:
全局变量的相互污染?
不同开发人员不同js文件相互干扰
不同框架中的全局变量相互干扰
没有模块、没有命名空间等手段
解决多人合作和模拟模块的尝试
模拟命名空间
自执行函数模拟局部作用域
模块化尝试封装
解决方法一:模拟命名空间
// 第一个尝试;命名空间
// a.js 老马
var Shop = {}; // 顶层命名空间
Shop.User = {}; // 电商的用户模块
Shop.User.UserList = {}; //用户列表页面模块。
Shop.User.UserList.length = 19; // 用户一共有19个。
// b.js 小米开发的。
Shop.User.UserDetial = {};
Shop.User.UserDetial.length = 20;
console.log(Shop.User.UserDetial.length)//因为2者的UserDetial和UserList是不一样的,所以length是不会发生冲突的
console.log(Shop.User.UserList.length)
解决方法二:自执行函数模拟局部作用域
// => 给单个文件里面定义的局部变量都 变成 局部作用域里面的变量。
// 第二个尝试:
// a.js
(function(){
var a = 9;
})();
// b.js
(function(){
var a = "ssss";
})();
// 局部作用域和命名空间的用法减少了变量冲突的可能性。
- gsoap开发webservice
- [Go 语言社区]测试模块之---utf8例子
- org.hibernate.type.StringType cannot be cast to org.hibernate.type.VersionType
- JBPM4.4(2)-state结点和decision结点
- [Go 语言社区] Golang架构底层---日志函数
- [Go 语言社区]服务器游戏用户登陆数据读取函数
- 设计Go API的管道使用原则
- Goroutine背后的系统知识
- 从零到 Go:Google感恩节火鸡涂鸦开发纪实
- JBPM4.4(1)-简单工程的搭建
- 未绑定元素“mx:Panel”的前缀“mx”
- [Go 语言社区] Golang架构底层函数图片保存-原创
- [Go 语言社区]Golang架构--服务器与客户端自定义传输规则--原创
- Go语言 -浮点数
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法