对象:封装一个事物的属性和功能的程序结构。
本质:内存中保存多个属性和方法的一块存储空间,再起一个名字。—>相对于变量
面向对象:OOP(面向对象程序设计):程序中,描述一具体事物都要用对象来封装事物的属性和功能。
优点: 便于大程序的维护
三大特点: 封装,继承,多态
封装
封装就是将一个事物的属性和功能集中定义在一个对象中
优点: 便于大程序的维护
何时使用: 只要使用OOP,都要先将事物的属性和功能封装在一个对象中,再反复使用对象的功能。
1、直接量(字面量)创建
因为这种创建方法很直接,所以叫直接量,又叫字面量
var obj = {a:1}
2、构造函数创建
这一方法创建对象又叫工厂函数创建对象,顾名思义就是批量生产,使用这个方法我们需要用到关键字new。运行环境内置了一个对象的构造函数—Object,我们直接使用就可以了
var obj = new Object({a:1});
其中: new做了4件事:
1、先创建空对象,相当于{}
2、用空对象调用构造函数,this指向正在创建的空对象
按照构造函数的定义,为空对象添加属性和方法
3、将新创建对象的__proto__属性指向构造函数的prototype对象。
4、将新创建对象的地址,保存到等号左边的变量中
上面的例子中,我们在Object中加了一个参数,这个参数是初始化的值,这个参数的格式需要符合对象的格式要求,如果你传入了一个别的类型的参数,那么创建的就不是object类型的对象了
var a = new Object('a'); // String {"a"}
var b = new Object(1); // Number {1}
正是因为这一限制,所以显得使用new Object是那么的不必要(不如直接使用直接量).
另外,如果没有参数需要传入,则Object后面的()
可以省略,即
var a = new Object; //{}
除了环境自带的构造函数,我们还可以根据实际需求自定义构造函数
function Student(name,age){
this.name = name
this.age = age
}
var xm = new Student(‘xiaoM’,12); // Student {name: "xiaoM", age: 12}
var xh = new Student(‘xiaoH’,14); // Student {name: "xiaoH", age: 14}```
3、Object.create创建对象
我们知道,Object是一个构造函数,而js中函数也是对象类型的数据,所以函数也是有属性的,而create就是其中的一个方法,该方法的作用是根据现有的对象新创建一个子对象。
var son=Object.create(父对象);
这句话做了2件事:
1. 创建空对象son
2. 设置son的__proto__指向父对象
上面的解释其实是有一点瑕疵的,因为我们不但可以根据现有的对象来创建子对象,还可以根据null来创建子对象,我这么说是想强调下null不是一个对象
,虽然Object.prototype继承自null,但null也不是一个对象,你可能会说:
typeof null === "object" // true
Object.prototype.toString(null) === "[object Object]" // true
但它确实不是一个对象,这是js的历史遗留问题,详细可以看这里 typeof null的前世今生
继续回到我们的文章上来,如果我们使用了null作为父对象来创建一个子对象,那么
子对象.__proto__ === null //true
和Object.prototype一个级别的!!!不过不同的是,Object.prototype是运行环境封装的,里面天生有一些属性及方法,而用null创建的子对象就像一个新生儿一样,里面干干净净的,什么都没有。使用null创建子对象的缺点就是我们不再能够使用Object.prototype自带的一些属性及API,如果需要的话,你就得自己去实现了,好处就是这个子对象是一个干干净净的对象,里面的一些属性及方法可以按照自己的想法来,不用担心与原型链上属性重名,造成变量污染。
4、ES6 的 class创建对象
class关键字是ES6引入的概念,用于定义类(对象),用法如下:
//定义类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象Point。
Point类除了构造方法,还定义了一个toString方法。注意,定义“类”的方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。
ES6 的类,完全可以看作构造函数的另一种写法。
class Point {
// ...
}
typeof Point // "function"
Point === Point.prototype.constructor // true
上面代码表明,类的数据类型就是函数,类本身就指向构造函数。使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致。
var point = new Point(2,3);
point.toString() // "(2,3)"
这里执行new Point(2,3)可以看作是执行了类Point里的constructor方法为x,y赋值,同时把toString函数挂到Point.prototype上去
构造函数的prototype属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面。
更多class的使用说明及其他es6的特性看一看阮一峰老师的 ECMAScript 6 入门,讲的很好,很详细,上面关于class的讲解也都摘抄自阮一峰老师的这本书里。
父对象中的属性和方法,子对象可直接使用!
为什么继承:代码重用!节约内存空间!便于维护!
何时继承:只要多个子对象,拥有相同的属性值和功能时,都要将相同的属性和功能集中保存在父对象中一份即可.所有子对象共用!
//构造函数
function haizei(hname,power,speak){
this.hname=hname,
this.power=power,
this.speak=speak
}
//原型对象
haizei.prototype.intr=function(){
console.log(`I'm ${this.hname},my power is ${this.power},I'm from ${
this.hometown},${
this.speak}`);
}
haizei.prototype.hometown="东 海";
//新建子对象
var luFei=new haizei("路飞","橡胶","我是要成为海贼王的男人");
var suoLong=new haizei("索隆","三刀流","我要成为第一剑豪");
var wuSuoPu=new haizei("乌索普","狙击","我要成为勇敢的海上战士");
var shanZhi=new haizei("山治","做饭","我要找到ALL BLUE");
var naMei=new haizei("娜美","航海","宝藏!宝藏!");
luFei.intr();
//I'm 路飞,my power is 橡胶,I'm from 东 海,我是要成为海贼王的男人
suoLong.intr();
//I'm 索隆,my power is 三刀流,I'm from 东 海,我要成为第一剑豪
wuSuoPu.intr();
//I'm 乌索普,my power is 狙击,I'm from 东 海,我要成为勇敢的海上战士
shanZhi.intr();
//I'm 山治,my power is 做饭,I'm from 东 海,我要找到ALL BLUE
naMei.intr();
//I'm 娜美,my power is 航海,I'm from 东 海,宝藏!宝藏!
js中的继承都是继承原型对象:
原型对象: 专门集中存储一类子对象相同属性值和功能的父对象
何时使用: 今后只要一类子对象共有的相同属性值和功能都要定义在原型对象中
如何使用: 买一赠一: 每创建一个构造函数,都会自动赠送一个原型对象
用new创建子对象时,会自动设置子对象的__proto__继承构造函数的prototype
如何向原型对象中添加共有成员:
构造函数.prototype.属性=值;
构造函数.prototype.方法=function(){...}
内置对象的继承关系:
1、凡是可以new的类型,都是构造函数
2、每个内置对象的构造函数都对应一个内置的原型对象prototype
3、内置类型的原型对象中保存着该类型所有子对象共用的API
自有属性和共有属性:
自有属性: 直接保存在对象本地的属性
共有属性: 保存在原型对象中,被所有子对象共享的属性
获取时: 都可用对象.属性方式
赋值时: 自有属性(必须): 对象.属性=值
共有属性(必须): 构造函数.prototype.属性=值
自有和共有可解决浏览器兼容性问题:
问题: 如果一个API新的浏览器支持,旧浏览器不支持!
解决: if(typeof 内置类型.prototype.API!="function")
内置类型.prototype.API=function(){
... this //将来调用API的.前的对象
}
//自有和共有可以解决新旧浏览器方法不兼容问题(一些方法老浏览器没有)
//数组中的indexOf() 新浏览器有,老 没有
//如果Array.prototype中没有indexOf()
if(typeof Array.prototype.indexOf!=="function")
//向Array.prototype中添加indexOf()方法
Array.prototype.indexOf=function(val,fromi){
//如果fromi是undefined,fromi默认为0
//if(fromi==undefined) fromi=0;
document.write("这是自定义的indexOf()
");
fromi=fromi||0;
//i从fromi开始,遍历当前数组
for(var i=fromi;i<this.length;i++){
//如果当前数组的当前元素值为val,返回i
if(this[i]==val) return i;
}
//返回-1
return -1;
}
var arr=[1,2,3,2,5,6];
console.log(arr.indexOf(2));//1
console.log(arr.indexOf(2,2));//3
console.log(arr.indexOf(2,4));//-1
原型:prototype:保存所有子对象共有属性值的父对象
每个(构造)函数都自带一个prototype对象
所有被构造函数创建的子对象都有一个proto属性指向构造函数的prototype对象。
什么属性方法放在构造函数的prototype中?
所有子对象共用的属性值和方法——只在读取时共享
特殊情况:修改prototype中共有属性,只能用prototype对象,不能用子对象。
如果用子对象修改共有属性,会在子对象创建共有属性副本
原型链:由各级子对象的proto属性连续引用形成的结构。所有对象都有proto,所有对象原型链的顶部都是Object.prototype
所以原型链是:由多级父元素逐级继承形成的链式结构,保存着所有的对象成员(属性和方法)
原型链 vs 作用域链:
原型链:
保存着: 所有的变量
控制着: 对象的成员的使用顺序:
优先使用自有的。自己没有,才延原型链向父级查找。
原型链的顶端一定是Object.prototype
作用域链的终点: window
简单概括:
所有不需要"对象."访问的变量都保存在作用域链中
所有必须用"对象."访问的成员都保存在原型链中
原型相关API:
1、鉴别自有还是共有:
自有: var bool=obj.hasOwnProperty("属性名")
判断"属性名"是否是obj的自有属性
共有: 不是自有,且"属性名" in obj
in: 判断obj自己或obj的父对象中是否包含"属性名"。只要自己或父对象中包含,就返回true。
//自有 和 共有
function checkProperty(obj,pname){
if(obj.hasOwnProperty(pname))
console.log(`${pname}是自有属性`);
else if(pname in obj)//obj[pname]!=undefined
console.log(`${pname}是共有属性`);
else
console.log(`没有${pname}属性`);
}
checkProperty(luFei,"power");//power是自有属性
checkProperty(suoLong,"hometown");//hometown是共有属性
checkProperty(wuSuoPu,"life");//没有life属性
2、获得任意子对象的原型(父对象):obj.proto (个别浏览器禁用)
var prototype=Object.getPrototypeOf(子对象)
何时使用:无法获得构造函数时,又希望设置子对象的共有属性
3、判断父对象是否在子对象的原型链上:
父级对象.isPrototypeOf(子对象)
强调:isPrototypeOf不仅找直接父级,而是找整个原型链
typeof失效:只能判断原始类型的值,不能判断引用类型的值, 所以对Array无效
根据原型自定义继承:
1、仅设置两个对象间的继承关系:
子对象.__proto__ = 新父对象
问题: __proto__也是内部属性, 有可能被浏览器禁用
解决: API: Object.setPrototypeOf(子对象,新父对象);
设置子对象继承新父对象
//自定义继承 setPrototypeOf
var hmm={
sname:"Han Meimei",
sage:18,
intr(){
console.log(`I'm ${this.sname},I'm ${
this.sage}`);
}
}
hmm.intr();//I'm Han Meimei,I'm 18
var father={
bal:100000000000,car:"infinity"}
Object.setPrototypeOf(hmm,father);
console.log(hmm);//{
sname: "Han Meimei", sage: 18}
2、批量设置多个子对象的继承关系
只要修改构造函数的prototype对象即可
结果:将来再创建的子对象,所有新创建的子对象都继承新父对象
时机:在刚刚定义完构造函数后,立刻修改!
在修改原prototype对象和创建新子对象之前
步骤:1、构造函数.prototype = 新父对象
2、构造函数.prototype.constructor = 构造函数对象
var father = {balance:10000000000,car:'=B='};
function Student(sname,sage){
this.sname = sname;
this.sage = sage;
}
Student.prototype = father;
Student.prototype.constructor = Student;
Student.prototype.intrSelf = function(){
console.log("I'm "+this.sname+",I'm "+this.sage);
}
var lilei = new Student("Li Lei",19);
var Hmm = new Student("Han Meimei",18);
lilei.intrSelf();
Hmm.intrSelf();
3、用一个已有的父对象作为参照,创建一个新子对象,同时,扩展子对象自有属性。(ES5中最新提出的,本身不是继承的方法,是创建对象的方法)
var son=Object.create(父对象);
这句话做了2件事: 1、创建空对象son
2、设置son的__proto__指向父对象
语法进阶:
var son=Object.create(父对象,{
扩展属性名1:{
writable:true,//是否可修改,可以省略
value:属性值,
configurable:true//可以设置,可以省略
},
扩展属性名2:{...}
});
何时使用:在创建一个子对象时,希望随意扩展子对象的自有属性时
//老版本没有create(),模拟
if(typeof Object.create!=="function"){
Object.create=function(father,props){
//创建新对象,继承father
function F(){
};//媳妇
F.prototype=father;//媳妇->father
var obj=new F();//生小孩儿
F=null;//释放媳妇
if(props){
//如果有第二个参数
//遍历props每个属性
for(var key in props){
//为obj添加新属性:
//属性名为key
//属性值为props中key属性的value值
obj[key]=props[key].value;
}
}
return obj;//返回新对象
}
}
var father={
bal:10000000,
car:"infiniti"
}
var hmm=Object.create(father,{
phone:{
value:"iphone8plus",
writable:true,
enymerable:true,
configurable:true
}
});
console.dir(hmm);//
4、两种类型间的继承:
既继承结构,又继承原型:——推荐的继承方式
何时: 如果发现多个类型之间拥有相同的属性结构和方法时,就要抽象出一个父类型
如何实现:
1、子类型构造函数开始位置,借用父类型构造函数:
父类型构造函数.call(this,参数列表)
父类型构造函数.apply(this,[参数列表])
强调:仅完成第一步,只是借用构造函数中的语句而已,没有实现任何对象间的继承
2、定义构造函数后,设置子类型构造函数的prototype继承父类型的prototype:
Object.setPrototypeOf(子类型.prototype,父类型.prototype)
//模拟飞机大战
function Flyer(name,speed){
this.name=name;
this.speed=speed;
}
Flyer.prototype.fly=function(){
console.log(`${
this.name} 以时速 ${
this.speed} 飞行`);
}
//new Plane
function Plane(name,speed,score){
//this->新对象
//用call调换,传入参数的形式:xxx.call(obj,值1,值2,值3....)
//借用父类型构造
Flyer.apply(this,arguments);//.call(this,name,speed);
this.score=score;
}
Plane.prototype.getScore=function(){
console.log(`击落 ${
this.name} 得${
this.score} 分`);
}
Object.setPrototypeOf(
Plane.prototype,Flyer.prototype
);
//new Bee
function Bee(name,speed,award){
Flyer.apply(this,arguments);//.call(this,name,speed);
this.award=award;
}
Bee.prototype.getAward=function(){
console.log(`击落 ${
this.name} 得${
this.award} 奖励`);
}
Object.setPrototypeOf(
Bee.prototype,Flyer.prototype
);
var f16=new Plane("F16",1000,20);
console.dir(f16);//{name: "F16", speed: 1000, score: 20}
f16.fly();//F16 以时速 1000 飞行
f16.getScore();//击落 F16 得20 分
var bee=new Bee("小蜜蜂",50,"1 life");
console.dir(bee);//{name: "小蜜蜂", speed: 50, award: "1 life"}
bee.fly();//小蜜蜂 以时速 50 飞行
bee.getAward();//击落 小蜜蜂 得1 life 奖励
多态
多态: 同一个事物,在不同情况下表现出不同的状态(重写和重载)
重写override: 如果子对象觉得父对象的成员不好用,可以在子对象本地定义同名成员,覆盖父对象的成员。
为什么重写: 体现子对象与父对象之间的差异
何时重写: 只要子对象觉得父对象中的成员不好用,就可在本地重写父对象的成员。