> 文档中心 > 面向对象编程

面向对象编程

目录

为什么要使用面向对象

对象是什么

生成对象

 JavaScript_new命令

JavaScript_Prototype原型

 构造函数的缺点

prototype 原型的作用

JavaScript_实例_静态属性和方法

实例方法和静态方法

实例属性和静态属性

JavaScript_proto__属性

 JavaScript_constructor属性

JavaScript_原型链

JavaScript_instanceof运算符

JavaScript_Object对象的相关方法

Object.getPrototypeOf()

Object.setPrototypeOf()

Object.create()

Object.getOwnPropertyNames()

Object.prototype.hasOwnProperty()

JavaScript_对象的继承

JavaScript_多重继承

JavaScript_严格模式

设计目的

启用方法

显式报错


面向对象编程(Object Oriented Programming,缩写为 OOP)是目前主流的编程范式

为什么要使用面向对象

《大话设计模式》中大鸟给小菜讲的故事非常经典:

“话说三国时期,曹操带领百万大军攻打东吴,大军在长江赤壁驻扎,军船连成一片,眼看就要灭掉东吴,统一天下,曹操大悦,于是大宴众文武,在酒席间,曹操诗性大发,不觉吟道:‘喝酒唱歌,人生真爽……’众文武齐呼:‘丞相好诗!’于是一臣子速命印刷工匠刻版印刷,以便流传天下。”

“样张出来给曹操一看,曹操感觉不妥,说道:‘喝与唱,此话过俗,应改为‘对酒当歌’较好!’于是此臣就命工匠重新来过。工匠眼看连夜刻版之工,彻底白费,心中叫苦不迭。只得照办。”

“样张再次出来请曹操过目,曹操细细一品,觉得还是不好,说:‘人生真爽‘太过直接,应改问语才够意境,因此应改为‘对酒当歌,人生几何……’当臣子转告工匠之时,工匠晕倒……”

大鸟:“小菜你说,这里面问题出在哪里?”

小菜:“是不是因为三国时期活字印刷还未发明,所以要改字的时候,就必须要整个刻板全部重新刻。”

大鸟:“说得好!如果是有了活字印刷,则只需更改四个字就可,其余工作都未白做。岂不妙哉。

一、要改,只需更改要改之字,此为可维护

二、这些字并非用完这次就无用,完全可以在后来的印刷中重复使用,此乃可复用

三、此诗若要加字,只需另刻字加入即可,这是可扩展

四、字的排列其实可能是竖排可能是横排,此时只需将活字移动就可做到满足排列需求,此是灵活性好。”

“而在活字印刷术出现之前,上面的四种特性都无法满足,要修改,必须重刻,要加字,必须重刻,要重新排列,必须重刻,印完这本书后,此版已无任何可再利用价值。”

小菜:“是的,小时候我一直奇怪,为何火药、指南针、造纸术都是从无到有,从未知到发现的伟大发明,而活字印刷仅仅是从刻版印刷到活字印刷的一次技术上的进步,为何不是评印刷术为四大发明之一呢?原来活字印刷是思想的成功,面向对象的胜利。”

对象是什么

要了解面向对象编程,要先了解对象是什么?

对象其实是一个抽象概念的具体实例,例如:

  1. 人类:张三
  2. 动物:猫

抽象概念:人类、动物 特别常见称呼:类、模板

对象:张三、猫 特别常见称呼:实例对象

生成对象

对象是一个抽象概念的具体实例,那么我们要生成一个对象,需要有一个模板,表示某一类实物的共同特征,然后对象根据这个模板生成。

JavaScript 语言使用构造函数(constructor)作为对象的模板。所谓”构造函数”,就是专门用来生成实例对象的函数。

构造函数就是一个普通的函数,但具有自己的特征和用法

function People(){    this.name = "张三"}

温馨提示

构造函数的首字母大写:例如People中的P是大写的

构造函数的特点有两个

  1. 函数体内部使用了this关键字,代表了所要生成的对象实例
  2. 生成对象的时候,必须使用new命令
function People(){    this.name = "张三"}var p = new People();p.name // 张三

 JavaScript_new命令

new命令的作用,就是执行构造函数,返回一个实例对象

使用new命令时,根据需要,构造函数也可以接受参数

this关键字

在构造函数中的this指向当前实例对象

                Document     /*     理解面向对象  1. 为什么要使用面向对象      1. 易维护      2. 可复用      3. 可扩展      4. 灵活性    2. 面向对象的代码编写      1. 理解面向对象的模板(构造函数)      2. 通过模板生成一个实例对象(new关键字) */ /*     this关键字:  1. 事件中this指向DOM元素  2. 闭包中的this指向window  3. 定时器中的this指向window  4. 对象中的this指向调用者  5. 改变this指向:call\apply\bind  6. 构造函数中的this,指向当前实例对象 */ function Person(name,age){     this.name = name;     this.age = age; } var zhangsan = new Person("张三",20); var lisi = new Person("李四",22); console.log(zhangsan); console.log(lisi);        

JavaScript_Prototype原型

 构造函数的缺点

JavaScript 通过构造函数生成新对象,因此构造函数可以视为对象的模板。实例对象的属性和方法,可以定义在构造函数内部

function People(name,age) {    // 属性    this.name = name;    this.age = age;    // 方法;    this.sayHello = function(){ console.log("Hello");    }}var p1 = new People("张三",20);var p2 = new People("李四",30);

通过构造函数为实例对象定义属性和方法,虽然很方便,但是有一个缺点。同一个构造函数的多个实例之间,无法共享属性和方法,从而造成对系统资源的浪费

console.log(p1.sayHello === p2.sayHello); // false

这个问题的解决方法,就是 JavaScript 的原型对象(prototype)

prototype 原型的作用

prototype原型对象的所有属性和方法,都能被实例对象共享。也就是说,如果属性和方法定义在原型上,那么所有实例对象就能共享,节省了内存。

怎么为对象指定原型呢,JavaScript 规定,每个函数都有一个prototype属性,指向一个对象

function People() { }console.log(People.prototype);

对于普通函数来说,该属性基本无用。但是,对于构造函数来说,生成实例的时候,该属性会自动成为实例对象的原型。

function People(name,age) {    // 属性    this.name = name;    this.age = age;}// 原型属性People.prototype.color = "黄种人";// 原型方法People.prototype.sayHello = function(){    console.log("Hello");}var p1 = new People("张三",20);var p2 = new People("李四",30);console.log(p1.sayHello === p2.sayHello); // trueconsole.log(p1.color === p2.color); // true

p1p2共享了原型属性和方法

原型对象的属性不是实例对象自身的属性。只要修改原型对象,变动就立刻会体现在所有实例对象上。

People.prototype.color = "白种人";p1.color  // 白种人p2.color  // 白种人

如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法

p1.color = "白种人"console.log(p1.color); // "白种人"

总结一下,原型对象的作用,就是定义所有实例对象共享的属性和方法。

JavaScript_实例_静态属性和方法

实例方法和静态方法

在JavaScript中有静态方法和实例方法,静态方法是函数自己定义的,而实例方法是通过原型来定义。它们的区别是静态方法是可以直接用类名.方法名去调用的,而实例方法是不可以的,它必须要用实例才可以去调用实例.方法名

function Person(name){    this.name = name;}// 实例方法Person.prototype.getName = function(){    console.log(this.name);}// 静态方法Person.getAge = function(age){    console.log(age);}var person = new Person("itbaizhan");person.getName();Person.getAge(10);

JavaScript内置的Array方法则分为实例方法和静态方法

var arr = [10,20,30];// 实例方法arr.push(40);// 静态方法Array.isArray(arr);

实例属性和静态属性

实例属性和静态属性与方法类似,实例属性是通过实例对象调用的实例对象.属性,静态属性是通过类名调用的类名.属性

function Person(name){    // 实例属性    this.name = name;}// 静态属性 Person.age = 20;var person = new Person("itbaizhan");console.log(person.name);console.log(Person.age);

JavaScript_proto__属性

实例对象__proto__属性(前后各两个下划线),返回该对象的原型。该属性可读写

function People(){}var p = new People();console.log(p.__proto__);

对象的prototype属性等同于实例对象的__proto__属性

function People(){}var p = new People();console.log(p.__proto__ === People.prototype); // true

温馨提示

根据语言标准,__proto__属性只有浏览器才需要部署,其他环境可以没有这个属性

 JavaScript_constructor属性

prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数

function Person(){}console.log(Person.prototype.constructor === Person);  // true

 constructor属性的作用是什么呢

通俗的讲,就是为了将实例原型对象暴露出来, 比如你写了一个插件,别人得到的都是你实例化后的对象, 如果别人想扩展下对象,就可以用 constructor.prototype 去修改或扩展原型对象

       // function Person(name){ //     this.name = name // } // // 获得到原型 // console.log(Person.prototype.constructor); var person; (function(){     function Person(){  this.name = "张三";  this.age = 20;     }     Person.prototype.getName = function(){  console.log(this.name);     }     person = new Person(); })(); person.getName() // 问题:现在这个库缺少了一个方法,getAge的方法 // Person.prototype.getAge = function(){ // } person.constructor.prototype.getAge = function(){     console.log(this.age); } person.getAge();    

JavaScript_原型链

JavaScript 规定,所有对象都有自己的原型对象(prototype)。一方面,任何一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个“原型链”(prototype chain):对象到原型,再到原型的原型...

如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype,即Object构造函数的prototype属性。也就是说,所有对象都继承了Object.prototype的属性。这就是所有对象都有valueOftoString方法的原因,因为这是从Object.prototype继承的。

function Person(){}console.log(Person.prototype.__proto__);console.log(Object.prototype);

那么,Object.prototype对象有没有它的原型呢?回答是Object.prototype的原型是nullnull没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是null

console.log(Object.prototype.__proto__); // null

读取对象的某个属性时,JavaScript 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined。如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overriding)

function Person(){}Person.prototype.toString = function(){    return "这是Person的toString方法"}var person = new Person();person.toString()

JavaScript_instanceof运算符

instanceof运算符返回一个布尔值,表示对象是否为某个构造函数的实例

function Person(){}var person = new Person();console.log(person instanceof  Person);

由于instanceof检查整个原型链,因此同一个实例对象,可能会对多个构造函数都返回true

var d = new Date();d instanceof Date // trued instanceof Object // true

instanceof运算符的一个用处,是判断值的类型

var arr = [10,20,30];var obj = {};console.log(arr instanceof Array);console.log(obj instanceof Object);

温馨提示

instanceof运算符只能用于对象,不适用原始类型的值。

var str = 'itbaizhan';str instanceof String // false
  function Person(){} var p = new Person(); var p1 = []; console.log(p instanceof  Person); // true:当前p是Person的实例对象 console.log(p1 instanceof Person); // false:p1不是Person的实例对象 /*     typeof:检测基本数据类型:number、string、boolean     isArray:检测数组:true,false     instanceof:检测对象:true,false */ // intanceof会检测整个原型链 console.log(p instanceof Object); // true console.log(p1 instanceof Object); // true // var num = 10;// instanceof:只能检测对象 // console.log(num instanceof Number); //false var arr = []; console.log(arr instanceof Array);    

JavaScript_Object对象的相关方法

Object.getPrototypeOf()

Object.getPrototypeOf方法返回参数对象的原型。这是获取原型对象的标准方法

 function Person(){} Person.prototype.getName = function(){} var p = new Person() // 获取实例对象的原型对象 console.log(Object.getPrototypeOf(p)); console.log(Person.prototype); // 不推荐使用 console.log(p.__proto__);    

 

Object.setPrototypeOf()

Object.setPrototypeOf方法为参数对象设置原型,返回该参数对象。它接受两个参数,第一个是现有对象,第二个是原型对象

   var sxt = {}; var itbaizhan = {     teacher:"老师",     getAge:function(){  console.log(13);     } } Object.setPrototypeOf(sxt,itbaizhan); sxt.getAge() console.log(sxt.teacher);    

 

Object.create()

JavaScript 提供了Object.create()方法,让一个对象继承另一个对象的属性和方法

var sxt = {    teacher: function () { console.log('hello');    }};var itbaizhan = Object.create(sxt);itbaizhan.teacher() // hello

 Object.create()方法生成的新对象,动态继承了原型。在原型上添加或修改任何方法,会立刻反映在新对象之上

var sxt = { t: 10 };var itbaizhan = Object.create(sxt);sxt.t = 20;itbaizhan.t // 20

除了对象的原型,Object.create()方法还可以接受第二个参数。该参数是一个属性描述对象,它所描述的对象属性,会添加到实例对象,作为该对象自身的属性

var sxt = {    java: { value: "全体系"    },    web: { value: '大前端'    }};var itbaizhan = Object.create(sxt, {    python:{//必须是对象形式value:"全方向"    }});console.log(itbaizhan.java.value);console.log(itbaizhan.python);//全方向  注意调用形式

 

Object.getOwnPropertyNames()

Object.getOwnPropertyNames方法返回一个数组,成员是参数对象本身的所有属性的键名,不包含继承的属性键名

Object.getOwnPropertyNames(Array)// ['length', 'name', 'prototype', 'isArray', 'from', 'of']

 

Object.prototype.hasOwnProperty()

对象实例的hasOwnProperty方法返回一个布尔值,用于判断某个属性定义在对象自身,还是定义在原型链上

  console.log(Object.getOwnPropertyNames(Array)); console.log(Array.hasOwnProperty("length")); // true console.log(Array.hasOwnProperty("toString")); // false    

JavaScript_对象的继承

面向对象编程很重要的一个方面,就是对象的继承。A 对象通过继承 B 对象,就能直接拥有 B 对象的所有属性和方法。这对于代码的复用是非常有用的。

大部分面向对象的编程语言,都是通过“类”(class)实现对象的继承。传统上,JavaScript 语言的继承不通过 class,而是通过“原型对象”(prototype)实现

温馨提示

ES6版本已经提供了 class 语法

  function Person(name,age){     this.name = name;     this.age = age; } Person.prototype.getName = function(){     console.log(this.name); } Person.prototype.getAge = function(){     console.log(this.age); } function Student(name,age,learn){     // 继承属性:调用父类的构造函数     Person.call(this,name,age);     this.learn = learn; } // 继承父类的方法 for(var p in Person.prototype){     Student.prototype[p] = Person.prototype[p]; } Student.prototype.getLearn = function(){     console.log(this.learn); } // 就近原则 Student.prototype.getName = function(){     console.log("姓名:"+this.name); } var student = new Student("张三",19,"it"); student.getAge() student.getName() student.getLearn()    

JavaScript_多重继承

 JavaScript 不提供多重继承功能,即不允许一个对象同时继承多个对象。但是,可以通过变通方法,实现这个功能

  function Sxt(){     this.hello = "hello" } Sxt.prototype.getHello = function(){     console.log("Hello"); } function Itbaizhan(){     this.world = "world" } Itbaizhan.prototype.getWorld = function(){     console.log("World"); } function Sum(){     Sxt.call(this);     Itbaizhan.call(this); } // 继承Sxt // Sum.prototype = Object.create(Sxt.prototype); for(var p in Sxt.prototype){     Sum.prototype[p] = Sxt.prototype[p]; } // 继承Itbaizhan Object.assign(Sum.prototype,Itbaizhan.prototype); // 指定constructor Sum.prototype.constructor = Sum; console.log(Sum.prototype); var s = new Sum(); console.log(s.hello); console.log(s.world); s.getHello(); s.getWorld();    

JavaScript_严格模式

除了正常的运行模式,JavaScript 还有第二种运行模式:严格模式(strict mode)。顾名思义,这种模式采用更加严格的 JavaScript 语法。

同样的代码,在正常模式和严格模式中,可能会有不一样的运行结果。一些在正常模式下可以运行的语句,在严格模式下将不能运行。

设计目的

早期的 JavaScript 语言有很多设计不合理的地方,但是为了兼容以前的代码,又不能改变老的语法,只能不断添加新的语法,引导程序员使用新语法。

严格模式是从 ES5 进入标准的,主要目的有以下几个。

  • 明确禁止一些不合理、不严谨的语法,减少 JavaScript 语言的一些怪异行为。
  • 增加更多报错的场合,消除代码运行的一些不安全之处,保证代码运行的安全。
  • 提高编译器效率,增加运行速度。
  • 为未来新版本的 JavaScript 语法做好铺垫。

总之,严格模式体现了 JavaScript 更合理、更安全、更严谨的发展方向。

启用方法

进入严格模式的标志,是一行字符串use strict

'use strict';
  1. use strict放在脚本文件的第一行,整个脚本都将以严格模式运行
  2. use strict放在函数体的第一行,则整个函数以严格模式运行
  'use strict';  console.log('这是严格模式');function strict() {  'use strict';  return '这是严格模式';}

显式报错

eval、arguments 不可用作标识名

严格模式下,使用eval或者arguments作为标识名,将会报错。下面的语句都会报错

'use strict';var eval = 17;var arguments = 17;

函数不能有重名的参数

正常模式下,如果函数有多个重名的参数,可以用arguments[i]读取。严格模式下,这属于语法错误

function f(a, a, b) {  'use strict';  return a + b;}

全局变量显式声明

正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,全局变量必须显式声明

'use strict';v = 1; // 报错,v未声明for (i = 0; i < 2; i++) { // 报错,i 未声明  // ...}function f() {  x = 123;}f() // 报错,未声明就创建一个全局变量

禁止 this 关键字指向全局对象

禁止使用 with 语句

arguments 不再追踪参数的变化

保留字

为了向将来 JavaScript 的新版本过渡,严格模式新增了一些保留字(implements、interface、let、package、private、protected、public、static、yield等)。使用这些词作为变量名将会报错

function package(protected) { // 语法错误  'use strict';  var implements; // 语法错误}

 选项卡(面向对象实现)

                Document     .tab input {     background: #f1f1f1;     border: 1ps solid #ff0000; } .tab .active {     background: #e9f212; } .tab div {     display: none;     width: 300px;     height: 250px;     padding: 10px;     background: #e9f212;     border: 1px solid #ff0000; }         function Tab(id) {     var tabBox = document.getElementById(id);     // 属性     this.tabBtn = tabBox.getElementsByTagName("input");     this.tabDiv = tabBox.getElementsByTagName("div");     for (var i = 0; i < this.tabBtn.length; i++) {  this.tabBtn[i].index = i;  var _this = this;  this.tabBtn[i].onclick = function () {      _this.clickBtn(this);  }     } } Tab.prototype.clickBtn = function (btn) {     for (var j = 0; j < this.tabBtn.length; j++) {  this.tabBtn[j].className = "";  this.tabDiv[j].style.display = "none";     }     this.className = "active";     this.tabDiv[btn.index].style.display = "block" } // window.onload = function () { //     new Tab("tabBox1") //     new Tab("tabBox2") // }         // 每一个选项卡在加载之后,都知道这个选共享卡是干嘛,或者是输出专业 function ItbaizhanTab(id,effect){     Tab.call(this,id);     this.effect = effect; } for(var t in Tab.prototype){     ItbaizhanTab.prototype[t] = Tab.prototype[t]  } ItbaizhanTab.prototype.getEffect = function(){     console.log(this.effect); } window.onload = function(){     var t1 = new ItbaizhanTab("tabBox1","bz程序员主流专业");     var t2 = new ItbaizhanTab("tabBox2","bz程序员参考");     t1.getEffect();     t2.getEffect(); }        
React、Vue
SpringBoot、SpringMVC
flask
sxt出品java
sxt视频,bz视频
课堂笔记文档