面向对象-构造函数、原型、原型链
JS基本介绍
- JS的用途:Javascript可以实现浏览器端、服务器端(nodejs)。。。
- 浏览器端JS由以下三个部分组成:
- ECMAScript:基础语法(数据类型、运算符、函数。。。)
- BOM(浏览器对象模型):window、location、history、navigator。。。
- DOM(文档对象模型):div、p、span。。。
- ECMAScript又名es,有以下重大版本:
- 旧时代:
- es1.0。。。es3.1
- 新时代:
- es5
- es6(es2015)
- es7(es2016)、es8(es2017)
- 旧时代:
数据类型
- 基本数据类型(值类型):(数字、字符串、布尔值、null、undefined)
- undefined类型?
1.声明了一个变量,但没有赋值,值默认为undefined, 如 var a;
2.声明一个变量了,并赋值了一个undefined的值,如 var b = undefined;
3.一个对象中,获取某个不存在的属性,值为undefined
- undefined类型?
- 复杂数据类型(引用类型):(对象)
- 数组
- 函数
- 正则表达式
- Date
对象的基本使用
创建一个对象
var student={
name:"李白" , //student有一个name属性,值为"李白"
grade:"初一" ,
//a、student有一个say属性,值为一个函数
//b、student有一个say方法
say:function(){
console.log("你好");
},
run:function(speed){
console.log("正在以"+speed+"米/秒的速度奔跑");
}
}
对象是键值对的集合:对象是由属性和方法构成的 (ps:也有说法为:对象里面皆属性,认为方法也是一个属性)
- name是属性 grade是属性
- say是方法 run是方法
对象属性操作
获取属性:
第一种方式:.语法
- student.name 获取到name属性的值,为:"李白"
- student.say 获取到一个函数
第二种方式:[]语法
- student["name"] 等价于student.name
- student["say"] 等价于student.say
注意:2种方式的差异:
- .语法更方便,但是坑比较多(有局限性),比如:
- .后面不能使用js中的关键字、保留字(class、this、function。。。)
- .后面不能使用数字
var obj={};
obj.this=5; //语法错误
obj.0=10; //语法错误
- []使用更广泛
- o1[变量name]
- ["class"]、["this"]都可以随意使用
obj["this"]=10
- [0]、[1]、[2]也可以使用
obj[3]=50 = obj["3"]=50
- 甚至还可以这样用:["[object Array]"]
- jquery里面就有这样的实现
- 也可以这样用:["{abc}"]
- 给对象添加了{abc}属性
设置属性
student["gender"]="男"
等价于:student.gender="男"
- 含义:如果student对象中没有gender属性,就添加一个gender属性,值为"男"
-
如果student对象中有gender属性,就修改gender属性的值为"男"
- 案例1:
student.isFemale=true
- 案例2:
student["children"]=[1,2,5]
- 案例3:
student.toShanghai=function(){
console.log("正在去往上海的路上")
}
删除属性
- delete student["gender"]
- delete student.gender
通过构造函数创建对象
构造函数创建对象的例子:
-
var xiaoming = new Object() --> var xiaoming = {};
-
var now = new Date()
-
var rooms = new Array(1,3,5) --> var rooms = [1,3,5]
-
var isMale=/123/;
==>var isMale=new RegExp("123")
- isMale是通过RegExp构造函数创建出来的对象
- isMale是RegExp构造函数的实例
-
以上例子中,Object、Date、Array都是内置的构造函数
自定义一个构造函数来创建对象
- 构造函数
function Person(name,age){
this.name=name;
this.age=age;
}
var p1=new Person("赵云",18)
等同于
function person(name ){
Object obj = new Object();
obj.name = name;
obj.age = age;
return obj;
}
var p1 = new Person("赵云",18)
- 说明:
p1
就是根据Person构造函数
创建出来的对象
构造函数的概念
- 在JavaScript中,用new关键字来调用的函数,称为构造函数。构造函数首字母一般大写(PS:非强制,但这么写有助于区分构造函数和普通函数);
- 任何函数都可以当成构造函数
function CreateFunc(){ }
- 只要把一个函数通过new的方式来进行调用,我们就把这一次函数的调用方式称之为:构造函数的调用
- new CreateFunc(); 此时CreateFunc就是一个构造函数
- CreateFunc(); 此时的CreateFunc并不是构造函数
1)构造函数和普通函数的唯一区别,就是他们调用的方式不同。只不过,构造函数也是函数,必须用new 运算符来调用,否则就是普通函数。
2)this就是代表当前作用域对象的引用。如果在全局范围this 就代表window 对象,如果在构造函数体内,就代表当前的构造函数所声明的对象。
关于new Object()
- new Object()等同于对象字面量{}
构造函数 new 的执行过程
var p1 = new Person();
- 1、new在内存中创建一个空的对象 (我们把这个对象称之为Person构造函数的实例)-
p1
- 2、让构造函数中的this指向这个新的对象-(p1)
- 3、执行函数内部的代码,其中,操作this的部分就是操作了该实例(p1)
- 4、返回值:
- a、如果函数没有返回值(没有return语句),那么就会返回构造函数的实例(p1)
- b、如果函数返回了一个基本数据类型的值,那么本次构造函数的返回值是该实例(p1)
function fn(){ ··· } var f1 = new fn(); //f1就是fn的实例 function fn2(){ return "abc"; } var f2 = new fn2(); //f2是fn2构造函数的实例
- c、如果函数返回了一个复杂数据类型的值,那么本次函数的返回值就是该值
function fn3(){ return [1,3,5]; //数组是一个对象类型的值, //所以数组是一个复杂数据类型的值 //-->本次构造函数的真正返回值就是该数组 //-->不再是fn3构造函数的实例 } var f3=new fn3(); //f3还是fn3的实例吗?错 //f3值为[1,3,5]
注意:
- f1 和 f2会自动含有一个constructor属性,指向它们的构造函数
console.log(f1.constructor == fn); //true console.log(f2.constructor == fn2); //true
- Javascript还提供了一个instanceof运算符,验证原型对象与实例对象之间的关系
console.log(f1 instanceof fn); //true console.log(f2 instanceof fn2); //true
构造函数模式的问题
构造函数方法很好用,但是存在一个泄露内存(浪费内存)的问题。如下:
function Person(name,age){
this.name=name;
this.age=age;
this.say=function(){}
}
var p1=new Person();
var p2=new Person();
var p3=new Person();
var p4=new Person();
...
//p1对象和p2对象的say方法是否是同一个方法:false
console.log(p1.say === p2.say);
//由于say方法功能相似,但是不是同一个方法(没有指向同一块内存,会造成内存浪费)
//解决方案1:(弊端:如果有多个需要共享的函数的话就会造成全局命名空间冲突的问题。)
function say(){
···
}
function Person(name,age){
this.name = name;
this.age = age;
this.say = say;
}
//解决方案2:(基本上解决了构造函数的内存浪费问题。但是代码看起来有些许的格格不入···)
var fns = {
say: function(){
console.log("你好");
},
run: function(){
console.log("时速500KM");
}
}
function Person(name,age){
this.name = name;
this.age = age;
this.say = fns.say;
this.run = fns.run;
}
//解决方案3(很完美!):把say方法写在他们共同的(父对象)中,他们共同的父对象,就可以通过:Person.prototype来获取
//-->只要把say方法写在Person.prototype中,那么say方法就是同一个方法(所有对象实例需要共享的属性和方法直接定义在 `prototype` 对象上)
Person.prototype.say = function(){
console.log('你好');
}
Person.prototype.run = function(){
console.log('时速500KM');
}
Person.prototype.sex = "男";
p1.run();
p2.run();
//验证p1.run和p2.run是否是同一个方法?
//这时所有实例的sex属性和say(),eat()方法,其实都是同一个内存地址,指向prototype对象,因此就提高了运行效率。
console.log(p1.run == p2.run); //true 指向同一个方法,这种方法避免了内存的浪费
console.log(p1.run == Person.prototype.run);//true
var p3=new Person();
console.log(p3.run == p1.run); //true
console.log(p3.run === p1.run);//true
//结论:只要往某个构造函数的prototype对象中添加某个属性、方法,那么这样的属性、方法都可以被所有的构造函数的实例所共享
//==>这里的【构造函数的prototype对象】称之为原型对象
// Person.prototype是 p1 p2 p3 的原型对象
// Person.prototype是Person构造函数的【实例】的原型对象
// Person的原型对象是谁呢?
// -->首先要知道Person的构造函数:-->Function
// -->所以Person的原型对象是:Function.prototype
// p1的原型对象是谁呢?
// -->首先要知道p1是谁创建的? -->Person
// -->所以p1的原型对象时: Person.prototype
- JavaScript 规定,每一个构造函数都有一个
prototype
属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的所拥有。这也就意味着,我们可以把所有对象实例需要共享的属性和方法直接定义在prototype
对象上。
Person.prototype={
constructor: Person,
say:function(){
console.log("你好");
},
run:function(){
console.log("时速500KM");
}
}
- 注意:
- 一般情况下,应该先改变原型对象,再创建对象
- 一般情况下,对于新的原型对象,会添加一个constructor属性,从而不破坏原有的原型对象的结构
Prototype模式的验证方法
为了配合prototype属性,Javascript定义了一些辅助方法,帮助我们使用它。
- isPrototypeOf() 这个方法用来判断,某个proptotype对象和某个实例之间的关系。
console.log(Person.prototype.isPrototypeOf(p1)); //true
- hasOwnProperty() 每个实例对象都有一个hasOwnProperty()方法,用来判断某一个属性到底是本地属性,还是继承自prototype对象的属性。
console.log(p1.hasOwnProperty('name')); //true console.log(p1.hasOwnProperty('sex')); //false
- in 运算符
- 可以用来判断,某个实例是否含有某个属性,不管是不是本地属性。
console.log('name' in p1); //true console.log('sex'in p1); //true
- 还可以用来遍历某个对象的所有属性。
for(var prop in p1) { console.log("p1[" + prop + "]=" + p1[prop]); }
构造函数、原型对象、实例 三者关系:
- 任何函数都具有一个
prototype
属性,该属性是一个对象 - 构造函数的
prototype
对象默认都有一个constructor
属性,指向prototype
对象所在函数 - 通过构造函数得到的实例对象内部会包含一个指向构造函数的
prototype
对象的指针__proto__
(__proto__
是非标准属性。) - 所有实例都直接或间接继承了原型对象的成员
原型链
- 概念:通过继承来实现的一套关系谱,继承的核心就是通过__proto__属性将若干个对象连接起来。
- 根本:继承
- 属性:每一个对象(null除外)都具有的一个__proto__属性,指向该对象的原型(父对象)
- 意义:可以实现让该对象访问到父对象中相关属性
- 根对象:Object.prototype
var arr=[1,3,5]
arr.proto:Array.prototype
arr.proto.__proto__找到根对象
function Animal(){}
var cat=new Animal();
//cat.__proto__ 指向Animal.prototype
//cat.__proto__.__proto__ 指向根对象