JavaScript:面向对象编程

面向对象简介

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

为什么要使用面向对象
《大话设计模式》中大鸟给小菜讲的故事非常经典:
“话说三国时期,曹操带领百万大军攻打东吴,大军在长江赤壁驻扎,军船连成一片,眼看就要灭掉东吴,统一天下,曹操大悦,于是大宴众文武,在酒席间,曹操诗性大发,不觉吟道:‘喝酒唱歌,人生真爽……’众文武齐呼:‘丞相好诗!’于是一臣子速命印刷工匠刻版印刷,以便流传天下。”“样张出来给曹操一看,曹操感觉不妥,说道:‘喝与唱,此话过俗,应改为‘对酒当歌’较好!’于是此臣就命工匠重新来过。工匠眼看连夜刻版之工,彻底白费,心中叫苦不迭。只得照办。”

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

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

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

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

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

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

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

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

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

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

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

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

人类:张三
动物:猫

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

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

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

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

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

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

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

构造函数的特点有两个

1 函数体内部使用了 this 关键字,代表了所要生成的对象实例
2 生成对象的时候,必须使用 new 命令

function People(){
    this.name = "张三"
}
var p = new People();
p.name // 张三

new 命令

在这里插入图片描述
new 命令的作用,就是执行构造函数,返回一个实例对象

function People(){
    this.name = "张三"
}
var p = new People();
p.name // 张三

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

function People(name) {
    this.name = name;
}
var p1 = new People("张三");
var p2 = new People("李四");
p1.name // 张三
p2.name // 李四

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

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); //true
console.log(p1.color === p2.color); // true

p1p2 共享了原型属性和方法

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

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

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

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

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

实例、静态方法与属性

实例方法和静态方法
在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);

__ 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__ 属性只有浏览器才需要部署,其他环境可以没有这个属性

constructor 属性

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

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

在这里插入图片描述
constructor 属性的作用是什么呢
通俗的讲,就是为了将实例原型对象暴露出来, 比如你写了一个插件,别人得到的都是你实例化后的对象, 如果别人想扩展下对象,就可以用constructor.prototype 去修改或扩展原型对象

var person;
(function(){
 function Person(){
        this.name = "张三";
        this.age = 20;
   }
    Person.prototype.getName = function(){
        console.log(this.name);
   }
    person = new Person();
})();
// 通过实例对象扩展方法
person.constructor.prototype.getAge =
function(){
    console.log(this.age);
}
person.getName();
person.getAge();

原型链

在这里插入图片描述
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()

instanceof 运算符

在这里插入图片描述
instanceof 运算符返回一个布尔值,表示对象是否为某个构造函数的实例

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

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

var d = new Date();
d instanceof Date // true
d 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

Object 对象的相关方法

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

var arr = [];
console.log(Object.getPrototypeOf(arr));

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

var sxt = {};
var itbaizhan = { teacher: "iwen" };
Object.setPrototypeOf(sxt, itbaizhan);
sxt.teacher // iwen

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 方法返回一个布尔值,用于判断某个属性定义在对象自身,还是定义在原型链上

Date.hasOwnProperty('length') // true
Date.hasOwnProperty('toString') // false

对象的继承

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

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

温馨提示
ES6版本已经提供了 class 语法,我们会在讲解ES6的时候单独讲解使用方式

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, major) {
    //调用父类的构造函数
    Person.call(this, name, age);
    this.major = major;
}
// 继承父类原型中的方法
for (var p in Person.prototype) {
    Student.prototype[p] = Person.prototype[p];
}
Student.prototype.showMajor = function () {
    console.log(this.major);
}
Student.prototype.getName = function(){
    console.log("Student:" + this.name);
}
var student = new Student("itbaizhan", "20","it");
student.showMajor();
student.getName();
student.getAge();

多重继承

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

function Sxt() {
    this.hello = 'hello';
}
function Itbaizhan() {
    this.world = 'world';
}
function Sum() {
    Sxt.call(this);
    Itbaizhan.call(this);
}
// 继承 Sxt
Sum.prototype = Object.create(Sxt.prototype);
// 继承链上加入 Itbaizhan
Object.assign(Sum.prototype, Sum.prototype);
// 指定构造函数
Sum.prototype.constructor = Sum;
var s = new Sum();
console.log(s.hello); // 'hello'
console.log(s.world); // 'world'

严格模式

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

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

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

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

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

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

'use strict';

可以放在两个位置:

1 use strict 放在脚本文件的第一行,整个脚本都将以严格模式运行
2 use strict放在函数体的第一行,则整个函数以严格模式运行

<script>
  'use strict';
  console.log('这是严格模式');
</script>
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; // 语法错误
}

实操-选项卡-回顾

<!DOCTYPE html>
<html>
<head>
    <style>
        
        #tabBox input {
            background: #f1f1f1;
            border: 1px solid #FF0000;
       }
        #tabBox .active {
            background: #E9D4D4;
       }
        #tabBox div {
            width:300px;
            height:250px;
            display:none;
            padding: 10px;
            background: #E9D4D4;
            border: 1px solid #FF0000;
       }
    </style>
    <meta charset="utf-8" />
    <title>选项卡</title>
    <script>
        window.onload=function(){
            var tabBox = document.getElementById('tabBox');
            var tabBtn = tabBox.getElementsByTagName('input');
            var tabDiv = tabBox.getElementsByTagName('div');
   			for(var i=0;i<tabBtn.length;i++)
			{
                tabBtn[i].index = i;
                tabBtn[i].onclick = function(){
                    for(var j=0;j<tabBtn.length;j++){
                      
				tabBtn[j].className='';
                      
				tabDiv[j].style.display='none';
                   }
                    this.className='active';
                  
					tabDiv[this.index].style.display='block';
               };
           }
       };
    </script>
</head>
<body>
    <div id="tabBox">
        <input type="button" value="web"class="active" />
        <input type="button" value="Java" />
        <input type="button" value="Python"/>
        <div style="display:block;">React、Vue</div>
        <div>SpringBoot、SpringMVC</div>
        <div>Flask、Django</div>
    </div>
</body>
</html>

实操-选项卡-面向对象

<!DOCTYPE html>
<html>
<head>
    <style>
        .tab input {
            background: #F1F1F1;
            border: 1px solid #FF0000;
       }
        .tab .active {
            background: #E9D4D4;
       }
        .tab div {
        width:300px;
            height:250px;
            display:none;
            padding: 10px;
            background: #E9D4D4;
            border: 1px solid #FF0000;
       }
    </style>
    <meta charset="utf-8" />
    <title>选项卡</title>
    <script>
        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';
           }
            btn.className='active';
          
			this.tabDiv[btn.index].style.display='block';
       };
    </script>
    <script>
        window.onload = function(){
            var tab1 = new Tab("tabBox1");
            var tab2 = new Tab("tabBox2");
       }
    </script>
</head>
<body>
    <div id="tabBox1" class="tab">
        <input type="button" value="web"class="active" />
        <input type="button" value="Java" />
        <input type="button" value="Python"/>
        <div style="display:block;">React、Vue</div>
        <div>SpringBoot、SpringMVC</div>
        <div>Flask、Django</div>
    </div>
    <br />
    <div class="tab" id="tabBox2">
        <input type="button" value="技术"class="active" />
        <input type="button" value="工具" />
        <input type="button" value="网站" />
        <div style="display:block;">Java、WEB</div>
        <div>vsCode、WebStorm</div>
        <div>百战程序员、尚学堂</div>
    </div>
</body>
</html>

实操-选项卡-面向对象-继承

function ItbaizhanTab(id, effect) {
    Tab.call(this, id);
    this.effect = effect
}
for (var p in Tab.prototype) {
 ItbaizhanTab.prototype[p] = Tab.prototype[p];
}
ItbaizhanTab.prototype.sayEffect = function() {
 console.log(this.effect);
}
window.onload = function () {
    var tab1 = new ItbaizhanTab("tabBox1","专业");
    var tab2 = new ItbaizhanTab("tabBox2","参考");
    tab1.sayEffect();
    tab2.sayEffect();
}

正则对象

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值