面向对象
面向对象三大特性
- 封装
- 继承
- 多态
01 封装:
作用:方便代码的维护,提高代码的复用性,更安全的访问数据的方式.
注意: js中封装多了一层意思,即用对象来封装变量和函数
02 继承
在编程语言中:一个类(对象)获得另一个类(对象)的属性和方法的一种方式.
03多态
对于相同的操作,不同的对象表现出不同的行为. 隐藏不同.
`因为js是一种弱类型的语言,天生具备多态的特性.
对象的几种创建方式
- 字面量的方式
- 使用内置构造函数
- 使用工厂函数
- 使用自定义构造函数
字面量的方式:
适用场景: 只需简单创建单个对象
如需要创建多个对象,那么代码的冗余度太高(重复代码太多)
举例
var obj = {
属性名:属性值,
方法名:function(){函数体}
}
内置构造函数的方式:
内置构造函数包括:Object Function Number String Boolean Array Date ...
同样的问题是,如需要创建多个对象,代码冗余度高(重复代码多)
举例
var obj = new Object();
obj.name = "name";
obj.showName = function() {
console.log(this.name)
}
工行函数的方式创建对象
特点:把固定的部分提取作为函数体,把变化的部分提取作为参数传递
问题是:如果创建多个不同类型的对象,那么我们无法分辨.
举例
//01 提供函数(工厂函数)
function createBook(){
//02 创建空的对象
var o = new Object();
//03 设置属性和方法
o.name = "默认的名称";
o.author = "默认的作者";
o.logDes = function(){
console.log("作者是:" + this.author);
}
//04 返回新创建的对象
return o;
}
自定义构造函数的方式创建对象
自定义构造函数创建对象的内容实现细节:
01 我们在使用new关键字调用构造哈函数的时候,内部默认会创建一个空的对象 02 默认会把这个空的对象赋值给this 03 通过this来设置新对象的属性和方法 04 在构造哈函数的最后,默认会把新创建的对象返回
自定义构造函数和工厂函数对比
01 函数的名字不一样,构造函数首字母要大写.
02 自定义构造函数创建对象的方式,内部会自动生成空的对象并赋值给this
03 默认会return新创建的对象
返回值:
没有显示return,默认会把新创建的对象返回.
显示执行了return语句,则要看具体情况:
如返回的是值类型,那么会直接忽略返回,把新创建的对象返回.
如返回的是引用类型,则会覆盖掉新创建的对象,直接返回引用数据类型的值.
function Dog(name)
{
this.name = name;
//return "demo"; 忽略
//return function (){};
}
var dog = new Dog("阿黄");
console.log(dog);
函数的调用&this
函数的调用
new:创建对象,并在最后返回该对象
构造函数:用于初始化对象
加入以普通的函数方式来调用构造函数,那么内部的this指向window.
function Person(name)
{
if(this instanceof Person)
{
this.name = name;
}else
{
return new Person(name);
}
}
var p1 = new Person("哔哩哔哩");
//构造函数本身是一个函数,在调用可以直接调用
var p2 = Person("哗啦哗啦"); //这是一个错误的演示(不要这样写代码)
console.log(p2); //undefined
函数传值
function Student(number,className,log){
this.number = number;
this.className = className;
this.log = log;
}
var stu1 = new Student("201701","九阴真经修炼01班",function(){
console.log("学号:" + this.number);
});
var stu2 = new Student("201702","九阴真经修炼01班",function(){
console.log("班级名称:" + this.className);
});
stu1.log();
stu2.log();
instanceof 对象类型判断
<script>
function Person(){};
function Dog(){};
var p1 = new Person();
var dog1 = new Dog();
//关键字 instanceOf 用来判断当前对象是否是某个类型的实例(检查某个对象是否是使用指定构造函数创建的)
//语法: 对象 instanceOf 构造函数(类型)
console.log(p1 instanceof Person);
console.log(p1 instanceof Dog);
console.log(dog1 instanceof Person);
console.log(dog1 instanceof Dog);
</script>
构造器属性的获取:constructor
<script>
function Person(){};
function Dog(){};
var p1 = new Person();
var dog1 = new Dog();
//在所有的对象中,都拥有一个构造器属性:constructor
console.log(p1.constructor);
console.log(dog1.constructor);
</script>
构造函数的原型对象
01 什么是原型对象
在使用构造函数创建对象的时候,默认会生成一个与构造函数相关联的对象,这个对象就是原型对象.默认情况下该对象是个空对象:{}
02 原型对象的作用
使用构造函数创建的对象,自动拥有(可以使用)原型对象中所有属性和方法
03 如何访问原型对象
构造函数.prototype
对象.__proto__ 注: 在开发中不建议使用该属性,因为这个属性不是ECMA标准.
04 如何设置原型对象
可以像设置普通对象一样利用对象的动态特性设置属性和方法
使用字面量的方式设置原型对象(直接替换)
05 约定
正确的说法:该对象的构造函数的原型对象
构造函数的原型
构造函数的原型对象
对象的原型对象
对象的原型
以上四种说法,她们的意思是一样的,都是该对象的构造函数的原型对象
<script>
//01 提供构造函数
function Dog(name)
{
//02 设置属性和方法
this.name = name;
this.color = "绿色";
}
//设置原型对象
// Dog.prototype.sayWangWangWang = function(){
// console.log("汪汪汪");
// };
//03 创建对象
var dog1 = new Dog("阿黄");
var dog2 = new Dog("拉拉");
dog1.sayWangWangWang();
dog2.sayWangWangWang();
</script>
什么是实例化
通过构造函数创建对象的过程就叫做实例化
什么是实例
通过构造函数创建的对象被称为该构造函数的实例.
原型的使用方法
01 利用对象的动态特性来设置原型对象
<script>
function Person(){
this.name = "默认的名称";
}
//设置原型对象
//成员= 属性|方法
//01 增加成员
Person.prototype.des = "我是直立行走的人";
Person.prototype.logName = function(){
console.log(this.name);
}
//02 修改成员
Person.prototype.des = "我是直立行走的人++++";
//console.log(Person.prototype);
var p1 = new Person();
console.log(p1.des);
p1.logName();
//03 删除成员
//delete关键字
//语法 delete 对象.属性
//console.log(delete p1.des); //不能用这种方式删除原型对象上面的属性(删除的是自己的属性)
delete Person.prototype.des ;
console.log(p1.des);
</script>
02 字面量的方式 (替换原型对象)
注意点:
01 如果替换了原型对象, 那么在替换前后所创建的对象,它们所指向的原型对象并非同一个
02 构造器属性: 在替换之后创建的对象中,它的构造器属性指向的并非Person构造函数,而是Object的原型对象的构造器属性
建议: 在设置完原型对象之后 在同一创建对象
举例
function Person(){
this.age = 40;
}
var p1 = new Person();
Person.prototype.sayHi = function(){
console.log("hi");
}
//设置原型对象
Person.prototype = {
name:"默认的名称",
showName:function(){
console.log(this.name);
}
}
var p2 = new Person();
// p1.sayHi(); //可以
// p2.sayHi(); //不可以
// console.log(p1.name); //undefined
// console.log(p2.name); //默认的名称
// p1.showName(); //报错
// p2.showName(); //默认的名称
<script>
function Person(){
this.age = 40;
}
var p1 = new Person();
//var obj = new Object();
Person.prototype = {
//注意:如果替换了原型对象,那么需要在原型对象中修正构造器属性
constructor:Person,
sayHi:function (){
console.log("hi");
}
};
var p2 = new Person();
//p2.constructor = Person; //在p2对象上添加了一个属性(constructor)
//var p3 = new Person();
//构造器属性
console.log(p1.constructor == p2.constructor); //false
// console.log(p1.constructor); //Person
// console.log(p2.constructor); //Object
// console.log(p2.constructor == p2.__proto__.constructor);
// console.log(p2.constructor == Object.prototype.constructor);
console.log(p2);
</script>
构造器:
属性: constructor
值:与之关联的构造函数
注意点:constructor是在原型对象身上,我们通过(对象.constructor)访问得到的值,其实就是原型对象中对应的值.
使用原型对象的注意事项
01 如何访问原型对象的属性
对象.属性
构造函数.prototype.属性
<script>
function Person(){
this.des = "默认的描述信息"
}
Person.prototype = {
constructor:Person,
des :"描述信息",
logDes:function (){
console.log("嘲笑我吧");
}
}
//01 对象.属性
var p1 = new Person();
console.log(p1.des);
//console.log(Person.prototype.des);
p1.logDes();
console.log(p1.age);
</script>
02属性的访问(读取)原则
就近原则,在访问属性的时候,先在对象自身查找(实力属性和方法),如果找到就直接用.否则,就去它的原型对象上查找,如果有就调用,找不到则提示undefined或者报错
<script>
function Person(){
}
Person.prototype = {
constructor:Person,
des :"描述信息",
}
var p1 = new Person();
var p2 = new Person();
//设置
//左值 --- 右值
//如果是左值,那么在设置对象的属性的时候,不会去原型对象上面去找(查找自己是否有,如果有就是修改,没有就是添加)
//如果是右值,那么在读取值得时候,先查找自己,如果找不到,再去原型身上找
// p1.hello(左值) = "hi"(右值);
Person.prototype.hello = "hello";
p1.hello = "hi";
console.log(p1.hello); //hi
console.log(p2.hello); //hello
</script>
03 设置属性的原则
如果是左值,那么在设置对象的属性的时候,不会去原型对象上查找(查找自己是否有,如果有就修改,没有就添加)
如果是右值,那么在读取值的时候,先找自己,找不到在去原型对象上去找.
设置原型属性的方法:
只能通过(构造函数.prototype.属性)或者直接替换原型对象的方式来设置
如果原型对象的类型是引用类型,那么可以通过对象的方式来设置
function Person(){};
Person.prototype.className = "逍遥派修仙技能班01";
Person.prototype.showClassName = function(){console.log(this.className)};
Person.prototype.car = {type:"火车",price:"$ 44444.44"};
var p1 = new Person();
var p2 = new Person();
p1.showClassName();
p1.className = "洗衣服班级";
console.log(p1);
console.log(p2);
//如果原型对象的属性是引用类型的
p1.car.type = "飞船";
console.log(p1.car.type); //飞船
console.log(p2.car.type); //飞船
//额外的补充
p1.car = {type:"火箭"};
console.log(p1.car.type); //火箭
console.log(p2.car.type); //飞船
p1.car = {};
console.log(p1.car.type); //undefined
console.log(p2.car.type); //飞船
in关键字
01 检查对象中是否有指定属性(实例属性+原型属性)
02 遍历对象
语法:k in obj
<script>
//01 提供一个构造函数
function Person(name) {
this.name = name;
}
//02 设置构造函数的原型对象的属性
Person.prototype.sayHello = function () {
console.log("hello");
}
//03 创建对象
var p1 = new Person();
//04 使用in关键字判断对象中是否存在以下属性:name age sayHello
console.log("age" in p1); //false
console.log("name" in p1); //true
console.log("sayHello" in p1); //true
</script>
hasOwnProperty
作用:检查对象中是否有指定属性(实例属性)
语法:`obj.hasOwnproperty.属性
<script>
//01 提供一个构造函数
function Person(name) {
this.name = name;
}
//02 设置构造函数的原型对象的属性
Person.prototype.sayHello = function () {
console.log("hello");
}
Person.prototype.des = "默认的描述信息";
//03 创建对象
var p1 = new Person();
//04 使用hasOwnProperty方法判断该属性是否是对象的实例属性
console.log(p1.hasOwnProperty("age")); //false
console.log(p1.hasOwnProperty("name")); //true
console.log(p1.hasOwnProperty("sayHello")); //false
console.log(p1.hasOwnProperty("des")); //false
</script>
isPrototype
判断是否是某个实例对象的原型对象
instanceof
检查是否是某个构造函数的实例
<script>
function Person(){};
var demo = {};
var demo1 = {};
Person.prototype = demo;
Person.prototype.constructor = Person;
var p1 = new Person();
//判断某个对象是否是指定对象的原型对象
console.log(demo1.isPrototypeOf(p1));
</script>
继承的实现
js的继承只支持实现继承,实现继承主要依赖原型链完成
js 中几种继承实现方式
- 01混入式继承
- 02原型式继承
- 03原型链继承
- 04Object create(同上)
- 05借用构造函数继承
- 06组合(原型链 + 借用)继承
混入式继承的实现方式
//创建一个空的对象
var o = {};
//提供一个已经有的对象 obj
var obj = {
name: "对象属性",
age:20,
sayHolle: function(){
console.log("Holle");
}
}
//混入式继承(把原有对象的所有属性和方法拷贝给新的对象)
for (var k in obj){
o[k] = obj[k];
}
console.log(o);
//问题: 新对象的引用类型属性和就对象中对应的属性是否相等??
console.log(o.sayHolle == obj.sayHolle)//true
//答案是相等的,那么两个对象中其中一个修改了属性,则另外一个会受到影响.
原型链式继承
原型对象中的成员(属性和方法)可以被使用该构造函数创建出来的所有对象共享;
利用这一特性,可以实现原型式继承;
//提供一个构造函数
function Person(name, age){
this.name = name;
this.age = age;
}
//设置原型对象的属性
Person.prototype.className = "逍遥派1班";
//使用构造函数,创建原型对象;
var p1 = new Person("张三",10);
var p2 = new Person("李四", 20);
//04 打印p1和p2对象中的className属性
console.log(p1.className);
console.log(p2.className);
//结论:对象p1和p2继承了构造函数原型对象中的属性className
//但是这并不是严格意义上的继承
安全的扩展内置对象
过程:
01 提供一个构造函数
02 设置 构造函数的原型对象为内置构造函数创建出来的对象
<script>
//Array
function MyArray(){};
//设置原型对象
MyArray.prototype = new Array();
MyArray.prototype.des = "描述信息";
MyArray.prototype.logDes = function(){
console.log(this.des);
}
//使用自己的构造函数来创建对象
var myArr01 = new MyArray();
var myArr02 = new MyArray();
myArr01.push("123","abc");
console.log(myArr01);
console.log(myArr01.des);
function OtherArray(){};
OtherArray.prototype = new Array();
OtherArray.prototype.des = "描述信息";
OtherArray.prototype.logDes = function(){
console.log("哈哈哈哈");
}
var otherArr01 = new OtherArray();
var otherArr02 = new OtherArray();
console.log(otherArr01.des);
otherArr01.logDes();
</script>
原型链的结构
01 每个对象都是由构造函数创建出来的,因为每个对象都有构造函数
02 每个构造函数都有一个与之对应的原型对象
03 原型对象自身也是对象
04 因此,原型对象也有自己的构造函数
05 原型对象的构造函数也有自己的原型对象
06 原型对象的构造函数的原型对象也是对象,所以它也有自己的构造函数
07 原型对象的构造函数的原型对象的构造函数也有自己的原型对象。。。
以上形成一个链式结构,就称之为原型链
原型链的顶端是Object.prototype , Object.prototype
本身也是一个对象,因此也有原型对象 : Object.__proto__
打印出来为null
.
原型链中对象属性的搜索规则
对象.属性的方法去访问属性的时候,先查找有没有对应的实例属性,如果有那么就直接使用
如果没有,那么就去该对象的原型对象上面去找,如果有那么就直接使用
如果没有,那么就接着查找原型对象的原型对象,如果有,那么就直接使用,
如果没有,那么就继续上面的搜索过程
直到搜索到Object.prototype为止,如果还是没有找到就返回undefined或者是报错
注意:
原型链搜索的路径越长,查询属性所花费的时间就越多
原则:就近原型
原型链继承
001 提供一个父构造函数
002 提供子构造函数
003 设置子构造函数的原型对象为父构造函数的一个实例对象
003 在这个实例对象上面设置属性和方法
<script>
function A(){
this.age = 55;
this.showAge = function(){
console.log(this.age);
}
};
//设置A的属性和方法
A.prototype.logDes = function(){
console.log("des");
}
function B(){
};
//设置原型链继承
B.prototype = new A();
B.prototype.des = "des";
B.prototype.show = function(){
console.log("show");
};
var b1 = new B();
console.log(b1.age);
b1.showAge();
b1.logDes();
console.log(b1);
</script>
原型链继承注意点:
- 1在设置玩原型链继承后,要修正构造器属性的指向(Student.prototype. constructor)
- 2在设置完原型链继承之后再来为原型对象添加属性和方法
- 3在设置完原型链继承之后只能通过对象的动态特性来设置原型对象的属性和方法,不可以使用字面量的方式.
<script>
function Person(){};
function Student(){};
// Student.prototype.sayHello = function(){
// console.log("hello");
// }
//设置原型链继承
Student.prototype = new Person();
Student.prototype.constructor = Student;
Student.prototype.sayHello = function(){
console.log("hello");
}
//创建对象
var stu1 = new Student();
console.log(stu1.constructor);
//stu1.constructor = Student; //添加属性
stu1.sayHello();
console.log(stu1.name);
</script>
原型链继承的问题:
01 无法对父构造函数传递参数
02 继承过来实例属性会成为当前对象的原型属性,会被创建出来的多个对象共享.
Object.create
作用:创建对象,并设置该对象的原型对象
问题:存在兼容性问题,是ES5推出的
<script>
var obj = {name:"张浩瀚"};
if(typeof Object.create == "function") {
var o = Object.create(obj);
}else
{
Object.create = function(){
function F(){};
F.prototype = obj;
var o = new F();
}
}
Object.create(); //只需要调用该方法就可以创建对象
</script>
call&apply
都是Function原型对象上的方法
作用:借用其他对象的方法
参数:
第一个参数是调用该方法的对象(函数内部的this绑定的对象)
后面的参数:
call:参数列表
apply:数组
对象1.方法.call(调用方法的真正的对象,参数1,参数2,参数3);
对象1.方法.apply(调用方法的真正的对象,[参数1,参数2,参数3...])
this -- >总是指向一个对象
函数的调用方式
001 作为对象的方法来调用 this--->当前的对象
002 作为普通的函数调用 this--->window
003 作为构造函数和new使用 this--->构造函数内部新创建的对象
004 被call或者是apply调用(函数上下文调用) this--->第一个参数
问题 -- this的丢失
<script>
window.name = "能够看见我吗";
var demo01 = {
name:"小偷",
age:20,
showName:function(param1,param2){
console.log(this.name,param1,param2);
}
}
var getName = demo01.showName;
//调用函数
demo01.showName(); //以对象的方式来调用 this-->当前的对象
//函数的调用方式发生了改变,导致this丢失
getName("S1","S2"); //以普通的函数调用 this--->window
</script>
借用构造函数实现继承的基本写法
<script>
function Person(name,age){
this.name = name;
this.age = age;
};
function Boy(bookName,name,age){
this.bookName = bookName;
//Person.call(this,"悟空",600); //借用构造函数
Person.call(this,name,age);
}
//创建对象
var boy = new Boy("水煮三国","悟空",600);
var boy02 = new Boy("大话西游","云风",40);
console.log(boy);
console.log(boy02);
</script>
组合继承的基本写法
01 借用构造函数实现继承:可以获得父构造函数中的实例属性 Person.call(this,name,age)
02 获得原型属性和方法 ( Boy.prototype = Person.prototype)
组合继承:原型 + 借用构造函数
<script>
function Person(name,age){
this.name = name;
this.age = age;
};
Person.prototype.des = "描述信息";
Person.prototype.logDes = function(){
console.log(this.des);
};
function Boy(bookName,name,age){
this.bookName = bookName;
//Person.call(this,"悟空",600); //借用构造函数
Person.call(this,name,age);
}
//设置原型继承
//Boy.prototype = new Person();
Boy.prototype = Person.prototype;
//创建对象
var boy = new Boy("水煮三国","悟空",600);
var boy02 = new Boy("大话西游","云风",40);
console.log(boy);
console.log(boy02);
boy.logDes();
var p1 = new Person();
p1.logDes();
Boy.prototype.des = "描述信息-bOY";
boy.logDes();
p1.logDes();
</script>
浅拷贝&深拷贝
#####浅拷贝
var obj = {
name = "小花脸",
car:{
type:"客车"
}
}
var o = {};
for (var k in obj){
o[k] =obj.[k];
}
console.log(o);
o.car.type = "银河战舰";
console.log(obj);
深拷贝
- 01 提供一个函数,两个参数(元对象,要拷贝属性的对象)
- 02 在函数中先检查第一个参数是否有值,如果没有值那么就初始化一个空的对象
- 03 for..in循环来遍历参数2,
001 检如何获取一个字符串对象查当前的属性值是什么类型
002 如果是值类型,那么就直接拷贝赋值
003 如果是引用类型,那么就再调用一次这个方法,去内部拷贝这个对象的所有属性
var obj = {
name:"小花脸",
car:{
type:"客车",
},
friends:["1","2","3"]
};
var o = {};
//deepCopy(o,obj);
function deepCopy(obj1,obj2) {
obj1 = obj1 || {};
for (var i in obj2)
{
if (obj2.hasOwnProperty(i))
{
if( typeof obj2[i] == "object")
{
//判断是数组还是对象
obj1[i] = Array.isArray(obj2[i])?[]:{};
//引用类型
deepCopy(obj1[i],obj2[i]); //函数调用
}else
{
//值类型
obj1[i] = obj2[i];
}
}
}
}
deepCopy(o,obj);
console.log(o);
//共享问题
//o.car.type = "小货车";
console.log(obj);
通过深拷贝来实现继承
比较完整的继承方案
<script>
if(typeof Array.isArray != "function") {
Array.isArray = function(obj){
return Object.prototype.toString.call(obj) == "[object Array]";
}
}
function deepCopy(obj1,obj2) {
obj1 = obj1 || {};
for (var i in obj2)
{
if (obj2.hasOwnProperty(i))
{
if( typeof obj2[i] == "object")
{
//判断是数组还是对象
obj1[i] = Array.isArray(obj2[i])?[]:{};
//引用类型
deepCopy(obj1[i],obj2[i]); //函数调用
}else
{
//值类型
obj1[i] = obj2[i];
}
}
}
}
function Person(name,age){
this.name = name;
this.age = age;
};
Person.prototype.des = "描述信息";
Person.prototype.car = {
type:"汽车"
}
Person.prototype.logDes = function(){
console.log(this.des);
};
function Boy(bookName,name,age){
this.bookName = bookName;
//Person.call(this,"悟空",600); //借用构造函数
Person.call(this,name,age);
}
//设置原型继承
//Boy.prototype = Person.prototype;
deepCopy(Boy.prototype,Person.prototype); //把父构造函数原型对象上面的属性和方法全部拷贝一份给Boy
//创建对象
var boy01 = new Boy("水煮三国","悟空",600);
var boy02 = new Boy("大话西游","云风",40);
Boy.prototype.car.type = "火车";
console.log(Boy.prototype);
var p1 = new Person();
console.log(p1.car.type);
</script>