热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

js温故而知新7(面向对象编程)——进修廖雪峰的js教程

JavaScript不辨别类和实例的观点,而是经由过程原型(prototype)来完成面向对象编程。原型是指当我们想要建立xiaoming这个详细的门生时,我们并没有一个Stude

Javascript不辨别类和实例的观点,而是经由过程原型(prototype)来完成面向对象编程。

原型是指当我们想要建立xiaoming这个详细的门生时,我们并没有一个Student范例可用。那怎么办?恰好有这么一个现成的对象:

var robot = {
name: 'Robot',
height: 1.6,
run: function () {
console.log(this.name + ' is running...');
}
};

我们看这个robot对象有名字,有身高,还会跑,有点像小明,痛快就依据它来“建立”小明得了!

因而我们把它改名为Student,然后建立出xiaoming:

var Student = {
name: 'Robot',
height: 1.2,
run: function () {
console.log(this.name + ' is running...');
}
};
var xiaoming = {
name: '小明'
};
xiaoming.__proto__ = Student;

注重末了一行代码把xiaoming的原型指向了对象Student,看上去xiaoming似乎是从Student继承下来的:

xiaoming.name; // '小明'
xiaoming.run(); // 小明 is running...

xiaoming有自身的name属性,但并没有定义run()要领。不过,由于小明是从Student继承而来,只需Student有run()要领,xiaoming也能够挪用:

《js温故而知新7(面向对象编程)——进修廖雪峰的js教程》

Javascript的原型链和Java的Class辨别就在,它没有“Class”的观点一切对象都是实例,所谓继承关联不过是把一个对象的原型指向另一个对象罢了

假如你把xiaoming的原型指向其他对象:

var Bird = {
fly: function () {
console.log(this.name + ' is flying...');
}
};
xiaoming.__proto__ = Bird;

如今xiaoming已没法run()了,他已变成了一只鸟:

xiaoming.fly(); // 小明 is flying...

在JavaScrip代码运转时代,你能够把xiaoming从Student变成Bird,或许变成任何对象。

请注重,上述代码仅用于演示目标。在编写Javascript代码时,不要直接用obj.__proto__去转变一个对象的原型,而且,低版本的IE也没法运用__proto__。Object.create()要领能够传入一个原型对象,并建立一个基于该原型的新对象,然则新对象什么属性都没有,因而,我们能够编写一个函数来建立xiaoming:

// 原型对象:
var Student = {
name: 'Robot',
height: 1.2,
run: function () {
console.log(this.name + ' is running...');
}
};
function createStudent(name) {
// 基于Student原型建立一个新对象:
var s = Object.create(Student);
// 初始化新对象:
s.name = name;
return s;
}
var xiaoming = createStudent('小明');
xiaoming.run(); // 小明 is running...
xiaoming.__proto__ === Student; // true

建立对象

Javascript对每一个建立的对象都邑设置一个原型,指向它的原型对象

当我们用obj.xxx接见一个对象的属性时,Javascript引擎先在当前对象上查找该属性,假如没有找到,就到其原型对象上找,假如还没有找到,就一向上溯到Object.prototype对象,末了,假如还没有找到,就只能返回undefined。

比方,建立一个Array对象:

var arr = [1, 2, 3];

其原型链是:

arr ----> Array.prototype ----> Object.prototype ----> null

Array.prototype定义了indexOf()、shift()等要领,因而你能够在一切的Array对象上直接挪用这些要领。

当我们建立一个函数时:

function foo() {
return 0;
}

函数也是一个对象,它的原型链是:

foo ----> Function.prototype ----> Object.prototype ----> null

由于Function.prototype定义了apply()等要领,因而,一切函数都能够挪用apply()要领。

很轻易想到,假如原型链很长,那末接见一个对象的属性就会由于花更多的时候查找而变得更慢,因而要注重不要把原型链搞得太长。

组织函数

除了直接用{ … }建立一个对象外,Javascript还能够用一种组织函数的要领来建立对象。它的用法是,先定义一个组织函数:

function Student(name) {
this.name = name;
this.hello = function () {
alert('Hello, ' + this.name + '!');
}
}

这确实是一个一般函数,然则在Javascript中,能够用关键字new来挪用这个函数,并返回一个对象:

var xiaoming = new Student('小明');
xiaoming.name; // '小明'
xiaoming.hello(); // Hello, 小明!

注重,假如不写new,这就是一个一般函数,它返回undefined。然则,假如写了new,它就变成了一个组织函数,它绑定的this指向新建立的对象,并默许返回this,也就是说,不须要在末了写return this;。

新建立的xiaoming的原型链是:

xiaoming ----> Student.prototype ----> Object.prototype ----> null

也就是说,xiaoming的原型指向函数Student的原型。假如你又建立了xiaohong、xiaojun,那末这些对象的原型与xiaoming是一样的:

xiaoming ↘
xiaohong -→ Student.prototype ----> Object.prototype ----> null
xiaojun ↗

用new Student()建立的对象还从原型上获得了一个constructor属性,它指向函数Student自身:

xiaoming.cOnstructor=== Student.prototype.constructor; // true
Student.prototype.cOnstructor=== Student; // true
Object.getPrototypeOf(xiaoming) === Student.prototype; // true
xiaoming instanceof Student; // true

看晕了吧?用一张图来示意这些杂乱无章的关联就是:
《js温故而知新7(面向对象编程)——进修廖雪峰的js教程》

赤色箭头是原型链。注重,Student.prototype指向的对象就是xiaoming、xiaohong的原型对象,这个原型对象自身另有个属性constructor,指向Student函数自身。
别的,函数Student恰好有个属性prototype指向xiaoming、xiaohong的原型对象,然则xiaoming、xiaohong这些对象可没有prototype这个属性,不过能够用__proto__这个非标准用法来检察。

如今我们就以为xiaoming、xiaohong这些对象“继承”自Student。

不过另有一个小题目,注重视察:

xiaoming.name; // '小明'
xiaohong.name; // '小红'
xiaoming.hello; // function: Student.hello()
xiaohong.hello; // function: Student.hello()
xiaoming.hello === xiaohong.hello; // false

xiaoming和xiaohong各自的name差别,这是对的,不然我们没法辨别谁是谁了。

xiaoming和xiaohong各自的hello是一个函数,但它们是两个差别的函数,虽然函数称号和代码都是雷同的!

假如我们经由过程new Student()建立了许多对象,这些对象的hello函数实际上只须要同享同一个函数就能够了,如许能够节约许多内存。

要让建立的对象同享一个hello函数,依据对象的属性查找准绳,我们只需把hello函数移动到xiaoming、xiaohong这些对象配合的原型上就能够了,也就是Student.prototype:

《js温故而知新7(面向对象编程)——进修廖雪峰的js教程》

修正代码以下:

function Student(name) {
this.name = name;
}
Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
};

用new建立基于原型的Javascript的对象就是这么简朴!

遗忘写new怎么办

假如一个函数被定义为用于建立对象的组织函数,然则挪用时遗忘了写new怎么办?

在strict形式下,this.name =name将报错,由于this绑定为undefined,在非strict形式下,this.name =name不报错,由于this绑定为window,因而无意间建立了全局变量name,而且返回undefined,这个效果更蹩脚。

所以,挪用组织函数万万不要遗忘写new。为了辨别一般函数和组织函数,根据商定,组织函数首字母应该大写,而一般函数首字母应该小写,如许,一些语法搜检东西如jslint将能够帮你检测到漏写的new。
末了,我们还能够编写一个createStudent()函数,在内部封装一切的new操纵。一个经常使用的编程形式像如许:

function Student(props) {
this.name = props.name || '匿名'; // 默许值为'匿名'
this.grade = props.grade || 1; // 默许值为1
}
Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
};
function createStudent(props) {
return new Student(props || {})
}

这个createStudent()函数有几个庞大的长处:一是不须要new来挪用,二是参数异常天真,能够不传,也能够这么传

var xiaoming = createStudent({
name: '小明'
});
xiaoming.grade; // 1

假如建立的对象有许多属性,我们只须要通报须要的某些属性,剩下的属性能够用默许值。由于参数是一个Object,我们无需影象参数的递次。假如恰好从JSON拿到了一个对象,就能够直接建立出xiaoming。

原型继承

先回忆Student组织函数:

function Student(props) {
this.name = props.name || 'Unnamed';
}
Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
}

如今,我们要基于Student扩展出PrimaryStudent,能够先定义出PrimaryStudent:

function PrimaryStudent(props) {
// 挪用Student组织函数,绑定this变量:
Student.call(this, props);
this.grade = props.grade || 1;
}

然则,挪用了Student组织函数不等于继承了Student,PrimaryStudent建立的对象的原型是:

new PrimaryStudent() ----> PrimaryStudent.prototype ----> Object.prototype ----> null

必需想办法把原型链修正为:

new PrimaryStudent() ----> PrimaryStudent.prototype ----> Student.prototype ----> Object.prototype ----> null

如许,原型链对了,继承关联就对了。新的基于PrimaryStudent建立的对象不但能挪用PrimaryStudent.prototype定义的要领,也能够挪用Student.prototype定义的要领。

假如你想用最简朴粗犷的要领这么干:

PrimaryStudent.prototype = Student.prototype;

是不可的!假如如许的话,PrimaryStudent和Student同享一个原型对象,那还要定义PrimaryStudent干啥?
我们必需借助一个中心对象来完成准确的原型链,这个中心对象的原型要指向Student.prototype。为了完成这一点,参考道爷(就是发明JSON的谁人道格拉斯)的代码,中心对象能够用一个空函数F来完成:

// PrimaryStudent组织函数:
function PrimaryStudent(props) {
Student.call(this, props);
this.grade = props.grade || 1;
}
// 空函数F:
function F() {
}
// 把F的原型指向Student.prototype:
F.prototype = Student.prototype;
// 把PrimaryStudent的原型指向一个新的F对象,F对象的原型恰好指向Student.prototype:
PrimaryStudent.prototype = new F();
// 把PrimaryStudent原型的组织函数修复为PrimaryStudent:
PrimaryStudent.prototype.cOnstructor= PrimaryStudent;
// 继承在PrimaryStudent原型(就是new F()对象)上定义要领:
PrimaryStudent.prototype.getGrade = function () {
return this.grade;
};
// 建立xiaoming:
var xiaoming = new PrimaryStudent({
name: '小明',
grade: 2
});
xiaoming.name; // '小明'
xiaoming.grade; // 2
// 考证原型:
xiaoming.__proto__ === PrimaryStudent.prototype; // true
xiaoming.__proto__.__proto__ === Student.prototype; // true
// 考证继承关联:
xiaoming instanceof PrimaryStudent; // true
xiaoming instanceof Student; // true

instanceof

instanceof 用于推断一个变量是不是是某个对象的实例,如 var a=new Array();alert(a instanceof Array); 会返回 true,同时 alert(a instanceof Object) 也会返回 true;这是由于 Array 是 object 的子类。再如:function test(){};var a=new test();alert(a instanceof test) 会返回true.

谈到 instanceof 我们要多插进去一个题目,就是 function 的 arguments,我们人人或许都以为 arguments 是一个 Array,但假如运用 instaceof 去测试会发明 arguments 不是一个 Array 对象,只管看起来很像。

别的:

测试 var a=new Array();if (a instanceof Object) alert(‘Y’);else alert(‘N’);

得’Y’.

但 if (window instanceof Object) alert(‘Y’);else alert(‘N’);

得’N’.

所以,这里的 instanceof 测试的 object 是指 js 语法中的 object,不是指 dom 模子对象。

运用 typeof 会有些辨别.

alert(typeof(window)) 会得 object.

《js温故而知新7(面向对象编程)——进修廖雪峰的js教程》

注重,函数F仅用于桥接,我们仅建立了一个new F()实例,而且,没有转变原有的Student定义的原型链。

假如把继承这个动作用一个inherits()函数封装起来,还能够隐蔽F的定义,并简化代码:

function inherits(Child, Parent) {
var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.cOnstructor= Child;
}
function Student(props) {
this.name = props.name || 'Unnamed';
}
Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
}
function PrimaryStudent(props) {
Student.call(this, props);
this.grade = props.grade || 1;
}
// 完成原型继承链:
inherits(PrimaryStudent, Student);
// 绑定其他要领到PrimaryStudent原型:
PrimaryStudent.prototype.getGrade = function () {
return this.grade;
};

小结

Javascript的原型继承完成体式格局就是:

定义新的组织函数,并在内部用call()挪用愿望“继承”的组织函数,并绑定this;
借助中心函数F完成原型链继承,最好经由过程封装的inherits函数完成;
继承在新的组织函数的原型上定义新要领。

class继承

在上面的章节中我们看到了Javascript的对象模子是基于原型完成的,特点是简朴,瑕玷是明白起来比传统的类-实例模子要难题,最大的瑕玷是继承的完成须要编写大批代码,而且须要准确完成原型链。
有无更简朴的写法?有!

新的关键字class从ES6最先正式被引入到Javascript中。class的目标就是让定义类更简朴。

我们先回忆用函数完成Student的要领:

function Student(name) {
this.name = name;
}
Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
}

假如用新的class关键字来编写Student,能够如许写:

class Student {
constructor(name) {
this.name = name;
}
hello() {
alert('Hello, ' + this.name + '!');
}
}

比较一下就能够发明,class的定义包含了组织函数constructor和定义在原型对象上的函数hello()(注重没有function关键字),如许就避免了Student.prototype.hello = function () {…}如许疏散的代码。

末了,建立一个Student对象代码和前面章节完整一样:

var xiaoming = new Student('小明');
xiaoming.hello();

class继承

用class定义对象的另一个庞大的优点是继承更方便了。想想我们从Student派生一个PrimaryStudent须要编写的代码量。如今,原型继承的中心对象,原型对象的组织函数等等都不须要斟酌了,直接经由过程extends来完成:

class PrimaryStudent extends Student {
constructor(name, grade) {
super(name); // 记得用super挪用父类的组织要领!
this.grade = grade;
}
myGrade() {
alert('I am at grade ' + this.grade);
}
}

注重PrimaryStudent的定义也是class关键字完成的,而extends则示意原型链对象来自Student。子类的组织函数能够会与父类不太雷同,比方,PrimaryStudent须要name和grade两个参数,而且须要经由过程super(name)来挪用父类的组织函数,不然父类的name属性没法一般初始化。

PrimaryStudent已自动获得了父类Student的hello要领,我们又在子类中定义了新的myGrade要领。

ES6引入的class和原有的Javascript原型继承有什么辨别呢?实际上它们没有任何辨别,class的作用就是让Javascript引擎去完成本来须要我们自身编写的原型链代码。简而言之,用class的优点就是极大地简化了原型链代码。

你一定会问,class这么好用,能不能如今就用上?

如今用还早了点,由于不是一切的主流浏览器都支撑ES6的class。假如一定要如今就用上,就须要一个东西把class代码转换为传统的prototype代码,能够尝尝Babel这个东西。


推荐阅读
  • Spring Boot使用AJAX从数据库读取数据异步刷新前端表格
      近期项目需要是实现一个通过筛选选取所需数据刷新表格的功能,因为表格只占页面的一小部分,不希望整个也页面都随之刷新,所以首先想到了使用AJAX来实现。  以下介绍解决方法(请忽视 ... [详细]
  • php三角形面积,335宝石大全
    php三角形面积,335宝石大全 ... [详细]
  • pypy 真的能让 Python 比 C 还快么?
    作者:肖恩顿来源:游戏不存在最近“pypy为什么能让python比c还快”刷屏了,原文讲的内容偏理论,干货比较少。我们可以再深入一点点,了解pypy的真相。正式开始之前,多唠叨两句 ... [详细]
  • 本文详细介绍了 JavaScript 中面向对象编程的基本概念,包括对象的创建、工厂模式、构造函数、原型及其优缺点,并探讨了继承的多种实现方式。 ... [详细]
  • 使用C#构建动态图形界面时钟
    本篇文章将详细介绍如何利用C#语言开发一个具有动态显示功能的图形界面时钟。文章中不仅提供了详细的代码示例,还对可能出现的问题进行了深入分析,并给出了解决方案。 ... [详细]
  • JavaScript 函数详解
    本文详细介绍了 JavaScript 中函数的基本概念和高级用法,包括函数的声明、参数传递、返回值、函数提升、函数表达式、IIFE 即时调用函数表达式以及函数作用域等内容。 ... [详细]
  • 本文探讨了使用 JavaScript 和 HTML5 Canvas 实现经典马里奥游戏克隆过程中遇到的碰撞检测和跳跃问题,并提供了详细的解决方案。 ... [详细]
  • RTThread线程间通信
    线程中通信在裸机编程中,经常会使用全局变量进行功能间的通信,如某些功能可能由于一些操作而改变全局变量的值,另一个功能对此全局变量进行读取& ... [详细]
  • 本打算教一步步实现koa-router,因为要解释的太多了,所以先简化成mini版本,从实现部分功能到阅读源码,希望能让你好理解一些。希望你之前有读过koa源码,没有的话,给你链接 ... [详细]
  • 本文介绍了如何通过 AJAX 发送请求到后端控制器,并将返回的 JSON 数据解析后在前端页面上显示。具体步骤包括发送 AJAX 请求、解析 JSON 字符串和遍历数据。 ... [详细]
  • 使用 Mui.js 获取复选框值的方法
    本文介绍如何使用 Mui.js 框架来获取复选框的值,并通过数组进行处理和展示。 ... [详细]
  • 在项目需要国际化处理时,即支持多种语言切换的功能,通常有两种方案:单个包和多个包。本文将重点讨论单个包的实现方法。 ... [详细]
  • 阿里云 Aliplayer高级功能介绍(八):安全播放
    如何保障视频内容的安全,不被盗链、非法下载和传播,阿里云视频点播已经有一套完善的机 ... [详细]
  • 本文通过一个简单的示例,展示如何使用ASP技术生成HTML文件。示例包括两个页面:首页index.htm和处理页面send.asp。 ... [详细]
  • 本文介绍了在Java中遍历HashMap的三种常见方法:使用entrySet()、keySet()以及Java 8引入的forEach。每种方法都有其特点和适用场景。 ... [详细]
author-avatar
lluuaalulua619
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有