一.面向对象:在程序中都是用一个对象来描述现实中一个具体的东西。
1.现实中的一个东西都包含属性和功能:
(1)属性:描述一个东西特点的变量,一个值;
(2)功能:东西可以执行的操作。
2.对象:封装多个数据和方法的存储空间。
3.自定义对象:封装现实中一个东西的属性和功能的存储空间。
现实中东西的属性,会成为对象中的属性变量;
现实中东西的功能,会成为对象中的方法(函数)。
4.面向对象三大特点:封装、继承、多态
(1)封装:将描述同一类东西的属性和方法,定义在一个对象中;
(2)继承:父对象中的属性和方法,子对象可直接使用;
(3)多态:同一个对象,在不同情况下,呈现不同的状态。
*重载:同一方法名,传入参数不同,执行不同的操作;
*重写:子对象觉得父对象的成员不好用,可自己定义一个,覆盖父对象的成员。
二.创建自定义对象(4种):(封装)
1.直接变量
var hmm={
sname:"Han MM",
age:11,
intrSelf:function(){
alert("I`m Han MM,I`m 11")
}
}
Js中一切都是对象!所有对象的底层都是hash数组。(遍历对象的所有成员,相当于遍历hash数组中每个元素)
属性:如何访问属性:(2种):obj.属性名; obj["属性名"]
(1)访问对象中不存在的属性(访问数组中不存在的下标),不会出错,返回undefined
(2)强行给不存在的属性赋值,不报错,js自动创建同名属性。
如何判断某个对象是否包含指定成员:(3种)
(1)obj.hasOwnProperty("成员名"); 找到true;没有false。
(2)"属性名" in 对象; 找到true;没有false。
(3)直接使用 obj.属性名;作为条件:
arr.indexOf===undefined
若不包含,返回undefined —> false
若包含,返回值或function —> true
何时省略:判断方法是否存在时,可省略===;如果确定属性值不是null、0、" "、NaN 时也可省略。
如何在方法中,访问当前对象自己:this关键字
***this关键字:运行时,指代正在*调用*方法的对象(即 . 前的对象)。
***this本质是window下唯一的指针,指向当前正在调用方法的对象。(this只有一个)
***在方法内访问当前对象自己的属性,必须用this.属性名。
***this和定义在哪无关!仅和调用时使用的当前对象有关。
***如果无主的调用或赋值,默认this都是window!
/*笔试题*/
//this-->window, 刚开始什么都没有时
var a = 2;
//this-->window
function fun(){
console.log(this.a);
}
//this-->window
var o = {a:3, fun:fun}; //this-->o, 因为定义的是对象
var p = {a:4}; //this-->p
o.fun(); //this-->o
(p.fun = o.fun)(); //2
//o.fun中存放的是函数fun的地址0x1011.
//p.fun=0x1011; p={a:4, fun:0x1011}; 即返回o.fun中的值!0x1011
//***赋值表达式的结果相当于等号右侧表达式的值!!!
//则,(0x1011)();--->即匿名函数自调-->this-->window
p.fun; //this-->p;输出4
2.var obj = new Object();//创建一个空对象
obj.属性名 = 值;
obj.方法名 = function(){...this.属性名...}
3.利用构造函数*反复*创建*相同结构*的对象:(2步)
构造函数:描述一类对象结构的特殊函数。
(1)定义构造函数
(2)利用构造函数创建对象
new:1.创建一个空对象:new obj={ };
2.用空对象,调用构造函数;构造函数在空对象中添加属性和方法;
3.设置新对象的_proto_,指向构造函数的prototype对象;(程序自动设置_proto_)
所有对象都有_proto_属性,且指向其构造函数的prototype对象。(_proto_指向其父级对象)
4.返回新对象的地址。
/*先定义构造函数*/
function Student(sname, age){
this.sname = sname;
this.age = age;
this.intrSelf = function(){
alert("I`m"+ this.sname + "I`m"+ this.age);
};
}
/*利用构造函数创建对象*/
var lilei = new Student("Li Lei",12);
function Student(sname, age){
this.sname=sname;
this.age=age;
}
//在构造函数原型对象中定义公共方法
Student.prototype.intrSelf=function(){
alert("I`m"+ this.sname + "I`m"+ this.age);
}
var lilei=new Student("Li Lei",12);
var hmm=new Student("Han MM",11);
Student.prototype.money=100;
console.log(lilei.money); //输出100。 先在当前对象本地找; 找不到再去原型找; 原型也没有才undefined。
4.Object.create(父对象,{扩展属性的列表对象})
三.继承:js中一切继承都是用原型对象实现的!
原型对象:每个函数对象都有一个原型对象。
构造函数的原型对象负责保存所有子对象共享的成员!(构造函数的原型对象负责保存所有,利用构造函数创建的所有子对象,共享的成员。即,如果子对象有相同的部分,都放在构造函数的原型中,取缔构造函数中的。)
建议:所有子对象共享的方法,都应定义在构造函数的原型中。----避免重复定义方法对象,浪费内存。
所有内置对象的方法都是定义在构造函数对象的原型中的(也应该是这样,节省内存),比如:Array.prototype.xxx;(xxx是Array中的方法)属性是定义在构造函数中的。
子对象不能够修改原型中的共享属性,只读。
所有对象都有什么属性、方法,取决于Object.prototype中定义了哪些属性、方法。(原型prototype存放共同成员)
(1)扩展对象属性:2种扩展
1.扩展共有属性:通过构造函数.prototype添加的属性;
2.扩展自有属性:通过某一个具体子对象添加的属性;
(2)判断自有属性或共有属性:
1.判断自有属性:obj.hasOwnProperty("属性名");
2.判断共有属性:"属性名" in obj && !obj.hasOwnProperty("属性名");
原型关系中包含 && 子对象没有
3.判断原型链上的属性:(判断不包含)
(1)if(!("属性名" in obj))
(2)if(obj.属性名===undefined)
(3)if(!obj.属性名)
(3)删除属性:delete 对象.属性名; 仅能删除当前对象自己的属性,无法删除共有属性。
(4)原型、原型链、继承:
1.原型:保存所有子对象共有属性和方法的对象!
***所有函数都有prototype,指向自己的原型对象;
***所有对象都有_proto_,指向自己父级原型对象。
***所有原型对象都有constructor,指回原型对应的构造函数。
2.原型链:所有父子级对象间由_proto_形成的多级引用关系 ——> 多级继承
3.原型相关API:
(1)判断自有属性或共有属性:见上
(2)获得任意对象原型:obj._proto_; Object.getPrototypeOf(obj);
(3)判断父对象是否在子对象的原型链上:父对象.isPrototypeOf(子对象);
(5)***检测一个对象是不是数组类型:(4种)
在 JS 里使用 typeof 来判断数据类型,只能区分基本类型,即 “number,string,undefined,boolean,object,function”,symbol” (ES6新增)七种。对于数组、null、对象来说,其关系错综复杂,使用 typeof 都会统一返回 “object” 字符串。
1.Array.prototype.isPrototypeOf(obj);
2.obj instanceof Array;(对象 是不是 构造函数 的实例)
3.obj.constructor==Array;仅判断直接父级
4.利用当前对象,强行调用原始的toString方法:(不同的原型对象的toString方法有不同的作用)
Object.prototype.toString.call(obj)=="[object Array]";
Object.prototype.toString.apply(obj)=="[object Array]";
js中的对象都继承自Object
,所以当我们在某个对象上调用一个方法时,会先在该对象上进行查找,如果没找到则会进入对象的原型(也就是.prototype
)进行查找,如果没找到,同样的也会进入对象原型的原型进行查找,直到找到或者进入原型链的顶端Object.prototype
才会停止。
所以,当我们使用arr.toString()
时,不能进行复杂数据类型的判断,因为它调用的是Array.prototype.toString
,虽然Array
也继承自Object
,但js在Array.prototype
上重写了toString
,而我们通过toString.call(arr)
实际上是通过原型链调用了Object.prototype.toString
。(详见:https://www.cnblogs.com/bq-med/p/8796836.html)
call方法: 调用一个对象(Object)的一个方法,以另一个对象(obj)替换当前对象。(改变this指向)(apply与其一样)
详见:https://www.cnblogs.com/sghy/p/7646633.html;https://uule.iteye.com/blog/1158829;
继承:代码重用!节省空间!
1.直接继承对象:想方设法修改对象的_proto_
(1)仅修改一个对象的_proto_:
Object.setPrototypeOf(子对象,父对象);用于给子对象一个新的原型父对象。(给子对象新找了个干爹)
(2)通过修改构造函数的原型对象,实现批量修改后续子对象的继承关系:
构造函数.prototype=父对象;(改变构造函数)
强调:仅影响之后创建的对象的继承关系,之前创建的对象依然继承,旧构造函数.prototype
(3)var obj=Object.create(父对象 [,{属性列表}])
创建一个空对象;继承父对象中的属性;继承同时可再扩展属性和方法。
2.仅继承结构:模拟Java中的继承
/*定义一个构造函数包含所有飞行物的属性---父*/
function Flyer(fname, speed){
this.fname=fname;
this.speed=speed;
this.fly=function(){
console.log(this.fname+"以时速:"+this.speed+"飞行");
};
}
/*再定义一个飞机构造函数,除包含飞行物的所有属性外,还扩展容量属性---子*/
function Plane(fname, speed, capacity){
//先调用Flyer构造函数
//用当前正在创建的对象强行调用window.Flyer
//并传入所需参数
Flyer.call(this,fname,speed);
//Flyer.apply(this,[fname,speed]);
this.capacity=capacity;
}
var A380=new Plane("A380",1800,555);
//_proto_ ---> Plane.prootype
//1.创建空对象
//2.调用Plane():先调用Flyer构造函数,再添加capacity属性。
A380.fly();
只要见到“[ ]”,就是数组;只要见到“{ }”,就是对象。
任何一个方法使用变量,只会从自己的活动范对象中,或window全局下找变量。