【面向JS--面向对象和原型】

来源:互联网 发布:北航知行论坛 编辑:程序博客网 时间:2024/06/05 20:19

什么是对象和面相对象

对象:封装一个事物的属性和功能的程序结构。

本质:内存中保存多个属性和方法的一块存储空间,再起一个名字。—>相对于变量

面向对象:OOP(面向对象程序设计):程序中,描述一具体事物都要用对象来封装事物的属性和功能。

优点: 便于大程序的维护三大特点: 封装,继承,多态

封装

封装就是将一个事物的属性和功能集中定义在一个对象中

优点: 便于大程序的维护

何时使用: 只要使用OOP,都要先将事物的属性和功能封装在一个对象中,再反复使用对象的功能。

创建对象:3种:

事物的属性,会成为对象的属性事物的功能,会成为对象的方法属性和方法统称为对象的成员

1、直接量:

var obj={    属性名: 属性值,    ...  : ... ,    方法名 (){        ... this.属性名 ...    },    ...}什么是this: 引用 正在调用函数的对象的关键词(点前的对象)为什么使用this: 方法中不加this的变量,默认仅在作用域链中找,不会去对象中查找。何时使用this: 只要对象的方法向使用自己的属性时,必须加this不用this:默认在作用域链(AO和window)中找加this:在this引用的当前对象中找this和定义在哪儿无关,只和调用时使用的对象有关!没有用任何对象,就直接调用的函数,this默认值window

2、用new创建:

var obj=new Object();//var obj={}obj.属性名=值;obj.方法名=function(){  ... this.属性 ...}强调: js中的对象即使创建完成,仍然可以添加新成员  简写: new Object() => new Object  或  Object()

如何访问对象的属性和方法:

js中一切对象底层都是关联数组!每个属性可用["属性名"]访问,每个成员名都是字符串"",方法其实就是属性引用了一个函数对象    console.dir(obj) 存储结构和关联数组是完全一样的    obj.属性 => obj["属性"] -> 用法和普通变量完全一样    obj.方法()             -> 用法和普通函数完全一样    方法也是属性,只不过保存的值是一个函数而已   
//案例var zhangfei={    sname:"Zhang Fei",    sage:110,    like:"酒",    intr(){        console.log(`I'm ${this.sname},I'm ${this.sage},I'm like ${this.like}`);    }}console.log(zhangfei.sname);//Zhang Feiconsole.log(zhangfei.sage);//110console.log(zhangfei.like);//酒zhangfei.intr();//I'm Zhang Fei,I'm 110,I'm like 酒zhangfei.sage++;zhangfei.like+=",肉";zhangfei.intr();//I'm Zhang Fei,I'm 111,I'm like 酒,肉var liufei=zhangfei;liufei.sage++;liufei.intr();//I'm Zhang Fei,I'm 112,I'm like 酒,肉zhangfei.intr();//I'm Zhang Fei,I'm 112,I'm like 酒,肉

问题: 以上两种封装方式仅适合封装一个单独的对象

解决: 构造函数

3、构造函数:

构造函数是描述一类对象统一结构的函数——相当于图纸

何时使用: 只要反复创建多个相同结构的对象时,都要先定义统一的构造函数,再使用构造函数反复创建对象

如何使用: 2步:

1、定义构造函数描述统一结构: function 类型名(属性参数){    this.属性名=属性参数;    this.方法名=function(){        ... this.属性 ...    }}2、用new调用构造函数,传入具体对象的属性值  var obj=new 类型名(属性值);其中: new做了4件事:    1、先创建空对象,相当于{}   2、用空对象调用构造函数,this指向正在创建的空对象       按照构造函数的定义,为空对象添加属性和方法   3、将新创建对象的__proto__属性指向构造函数的prototype对象。   4、将新创建对象的地址,保存到等号左边的变量中
//利用构造函数创建对象function Animal(name,vel,eat){    this.name=name;    this.vel=vel;    this.eat=eat;}var Zebra=new Animal("斑马","210km/h","草");var Leop=new Animal("豹子","200km/h","肉");console.log(Zebra);//{name: "斑马", vel: "210km/h", eat: "草"}console.log(Leop);//{name: "豹子", vel: "200km/h", eat: "肉"}

优点: 代码重用

问题: 无法节约内存

解决: 继承

继承

父对象中的属性和方法,子对象可直接使用!

为什么继承:代码重用!节约内存空间!便于维护!

何时继承:只要多个子对象,拥有相同的属性值和功能时,都要将相同的属性和功能集中保存在父对象中一份即可.所有子对象共用!

//构造函数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 BLUEnaMei.intr();//I'm 娜美,my power is 航海,I'm from 东 海,宝藏!宝藏!

js中的继承都是继承原型对象:

原型对象: 专门集中存储一类子对象相同属性值和功能的父对象何时使用: 今后只要一类子对象共有的相同属性值和功能都要定义在原型对象中如何使用: 买一赠一: 每创建一个构造函数,都会自动赠送一个原型对象   用new创建子对象时,会自动设置子对象的__proto__继承构造函数的prototype如何向原型对象中添加共有成员:    构造函数.prototype.属性=值;   构造函数.prototype.方法=function(){...}

内置对象的继承关系:

1、凡是可以new的类型,都是构造函数2、每个内置对象的构造函数都对应一个内置的原型对象prototype3、内置类型的原型对象中保存着该类型所有子对象共用的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()<br>");        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));//1console.log(arr.indexOf(2,2));//3console.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无效

判断一个对象是不是数组? 有几种办法

首先、typeof 只能识别原始类型,function和object。     无法进一步识别object中的不同对象类型1、验证原型对象:    1、Object.getPrototypeOf(obj)==Array.prototype      getPrototypeOf: 获取一个对象的原型对象   2、var bool= Array.prototype.isPrototypeOf(obj)      判断father是否是child的父对象2、验证构造函数:    1、if(obj.constructor==Array)//不常用      构造函数的prototype指向原型对象,同时,原型对象有constructor指回构造函数对象,constructor只在原型对象上有   2、var bool=obj instanceof Array      instance: 判断obj是否由构造函数Array创建出来      问题: 验证不够严格: 即使创建时不是使用数组创建的,但是只要原型链上有数组类型,也认为是数组。3、检查内部属性class:    class是每个对象中记录对象创建时使用的类型的属性,一旦对象被创建,class属性就无法被修改!如何获得class: 唯一的办法: 调用Object.prototype中的toString()输出结果: "[object    Object]"        引用类型的对象   Class问题: 几乎所有内置对象的原型对象都重写了Object中的toString方法,所有内置对象的子对象,都无法直接调到Object的toString解决: call和apply4、call和apply:(可以返回任何对象的构造函数)    任意对象.任意方法.call(替换的对象)    if(Object.prototype.toString.call(obj)=="[object Array]")    call vs apply:     相同:在调用方法时,修改当前调用方法的对象    不同:传入参数的形式:         xxx.call(obj,值1,值2,值3....)                   xxx.apply(obj,[值1,值2,值3....])  5、ES5中新函数:Array.isArray(obj) 

根据原型自定义继承:

1、仅设置两个对象间的继承关系:

子对象.__proto__ = 新父对象问题: __proto__也是内部属性, 有可能被浏览器禁用解决: API: Object.setPrototypeOf(子对象,新父对象);     设置子对象继承新父对象
//自定义继承 setPrototypeOfvar 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 18var 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 Beefunction 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: 如果子对象觉得父对象的成员不好用,可以在子对象本地定义同名成员,覆盖父对象的成员。

为什么重写: 体现子对象与父对象之间的差异
何时重写: 只要子对象觉得父对象中的成员不好用,就可在本地重写父对象的成员。