前言

  不少开发对JavaScript实现面向对象编程存在一知半解,并且不少的在项目实践中写的都是面向过程编程的代码,因此,希望能从零入手介绍面向对象的一些概念到实现简单的面向对象的例子让大家包括我自己加深对面向对象的认知。硬文一篇,希望能对大家有帮助 ^v^

对象基础

  概念

  对象是一个包含相关数据和方法的集合,是通过变量和函数组成,通常被我们说成属性和方法,常用对象字面量的形式表示。

  创建方法

  1.初始化对象

var person={}

  2.添加属性(变量)和方法(函数)

var person={
name:'aaron',
say:function(){
alert('hello')
}
}

  3.获取属性和执行方法

person.name
person.say()

  备注:获取属性和执行方法有两种方法,就是说我上面列举的其一:点表示法,还有一种就是括号表示法。如下:

person['name']
person['say']()

  因此,有时对象也被叫做关联数组,即对象做了字符串到值的映射,而数组做的是数字到值的映射。

  4.运行截图

  5.设置对象成员

  备注:有一点需要了解到的是,括号表示法能做到通过定义变量名的方式去设置对象成员,而这一点是点表示法没法实现的。

  6.“this”的含义

  this的指向其实是一直都让开发者头大的问题了,尤其是后端写JS时。其实说白了this就是指向了当前代码运行时的对象。
  例如:


  由于对象字面量执行的是当前对象,所以this指向了person。而像创建的构造函数等this的指向就是构造函数实例对象了   

  优点   

  一目了然,对象字面量创建的对象的好处可以有效的把对象关联的属性和方法统一了起来,也减少了全局变量的污染,得到一定程度的安全(减少了定义全变量覆盖对象属性的危险)。

面向对象--构造函数

  了解OOP思想

  例如从现实世界的某个实例出发,对于一个人(person)来说,我们能在他们身上获取到很多信息(他们的住址,身高,鞋码等等),然后我们会基于这些信息介绍关于他们,并需要他们做出回应。而在面向对象的语言中,我们就可以通过类(class)的概念去描述一个对象,而这个类就是定义对象特质的模板。通过创建的class,我们就可以基于它来创建一些拥有class中属性和方法的对象,即实例对象。而这些实例对象一般是具体的人,例如老师,学生。在OOP中,我们也可以基于这个class,创建其他的新类,而这些新的子类(例如家长)可以继承它们父类的属性(数据)和方法(功能),来使用父对象共有的功能。

  因此,通过对泛指人到具体的某个学生/老师的关系,我们就可以总结到面向对象的三个基本特性:封装,继承,多态。

  引入概念

  通过了解面向对象编程(OOP)的基本概念,什么是对象和对象的属性,方法,并了解实现面向对象编程的基本特性。也了解常用的创建对象方法--对象字面量,我们已经对对象的基本概念有了了解。但是,通过对象字面量来创建的只是单一的实体类,并不能实现通用对象(现实模型)的封装,即真正的实现面向对象。
JavaScript是通过构建函数的方式来定义对象和特征的,而构建的实例对象也有通过原型链的方式来继承某些特性。

  从实例中学习构建函数和对象实例

  1.Person()构造函数,创建实例对象并访问属性和方法:

  2.其他创建对象实例的姿势

  1.Object()构造函数

var person1=new Object();
person1.name='ace';
person1.age=30;
person1.greeting=function(){
alert('Hi! I\'m ' + this.name + '.'')
}

  2.使用create():这样就可以基于person1创建与person1具有相同属性和方法的对象。

var person2=Object.create(person1); 

面向对象--对象原型

  引入概念

  JavaScript的继承机制是有别于其他经典的面向对象编程语言的,是通过原型来实现从其他对象继承功能特性的。

  因此,JavaScript常被描述为基于原型的语言--每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性,原型对象也有可能拥有原型,从中继承方法和属性,以此类推。而这种关系被称为原型链。
  我们需要知道的是,这些属性和方法是定义在实例的构造函数上的prototype属性,当然,实例对象也__proto__属性,是从构造函数的prototype属性派生的,即实例对象.__proto===构造函数.prototype。

  理解原型对象


  从截图我们看到,person1实例除了具有Person()构造器中的属性方法外,还具有其他属性和方法,而这些则是Person()构造器原型对象Object上的成员。


  
通过调用valueOf,因此,我们也了解到了调用方法的过程:

    1.浏览器首先检查,person1 对象是否具有可用的 valueOf() 方法。

    2.如果没有,则浏览器检查 person1 对象的原型对象(即 Person)是否具有可用的 valueof() 方法。     

    3.如果也没有,则浏览器检查 Person() 构造器的原型对象(即 Object)是否具有可用的 valueOf() 方法。Object 具有这个方法,于是该方法被调用。

  prototype属性

   通过对valueOf方法的调用过程,我们也就了解到了那些能被继承的属性和方法(对象中存在不能被继承的属性方法,例is()/keys())是定义在prototype属性上的。因此,在构造函数是需要被子类继承的属性方法需要定义在prototype上。

  constructor属性

  每个实例对象都有constructor属性,它是指向创建该实例的构造函数。

  而我们也可以通过在constructor后添加()形式实现创建新实例。

  修改原型


  通过截图我们可以了解到了,虽然已经创建了实例对象person1,当时之后再像构造器Person()prototype中添加方法,person1还是能调用,这就说明了函数调用会通过上溯原型链,从上游对象中调用方法。


  
如图,若在prototype上定义属性的话,则this的当前执行环境为全局,返回的为undefined。并且,在对象继承上看,一般的做法是在构造器中定义属性,在prototype属性中定义方法。

小demo--实例理解JavaScript中的继承

  1.创建构造器Person并在构造器上定义方法

function Person(first, last, age, gender, interests) {
this.name = {
first,
last
};
this.age = age;
this.gender = gender;
this.interests = interests;
};
Person.prototype.bio = function() {
alert(this.name.first + ' ' + this.name.last + ' is ' + this.age + ' years old. He likes ' + this.interests[0] + ' and ' + this.interests[1] + '.');
};
Person.prototype.greeting = function() {
alert('Hi! I\'m ' + this.name.first + '.');
};

  2.创建Teacher类Teacher构造器,并继承Person的所有成员,同时具备自有属性和方法:subject,greeting()

 function Teacher(first, last, age, gender, interests, subject) {
Person.call(this, first, last, age, gender, interests);
this.subject = subject;
}

  3.设置Teacher()的原型和构造器引用


  如图,创建的新的Teacher()构造器只有一个空的原型属性,则需从Person()的原型prototype中继承方法:

Teacher.prototype = Object.create(Person.prototype);


  如图,我们又遇到了个问题,由于我们创建Teacher的prototype的方式,Teacher()构造器的prototype属性执行了Person(),因此,我们需要设置指向Teacher:

Teacher.prototype.constructor = Teacher;

  通过这样,我们就能实现了需要继承的方法都定义在了构造器的prototype属性内,这样才不会打乱了类继承结构。

  4.向Teacher()添加新的greeting方法

Teacher.prototype.greeting = function() {
var prefix; if(this.gender === 'male' || this.gender === 'Male' || this.gender === 'm' || this.gender === 'M') {
prefix = 'Mr.';
} else if(this.gender === 'female' || this.gender === 'Female' || this.gender === 'f' || this.gender === 'F') {
prefix = 'Mrs.';
} else {
prefix = 'Mx.';
} alert('Hello. My name is ' + prefix + ' ' + this.name.last + ', and I teach ' + this.subject + '.');
};

  5.最后运行创建teacher实例,我们就能得到了从Person()构造器继承的属性和方法,并具有只有Teacher()的构造器才有的属性方法。

  6.对象成员

  通过学习,我们可以知道一般对象所具有的对象成员包括三类:

    1.那些定义在构造器函数中的、用于给予对象实例的。一般为对象属性。

    2.那些直接在构造函数上定义、仅在构造函数上可用的。这些通常仅在内置的浏览器对象中可用,并通过被直接链接到构造函数而不是实例来识别。 例如Object.keys()。

    3.那些在构造函数原型上定义、由所有实例和对象类继承的。一般为对象方法。

深入--设计模式原则

  当然,通过上述的方法我们可以实现了基本的面向对象的编程,但是,要实现更高级的类库封装和框架实现,则需要能对设计模式有很好的认知。

  在这,我就列举下设计模式原则,希望大家包括我自己有学习的方向。一般的设计原则为:

    1.单一职责原则

    2.里氏替换原则

    3.依赖倒置原则

    4.接口隔离原则

    5.迪米特原则

    6.开闭原则

  当然,还有关于面向对象的设计模式(23种),则需要深入了解了,其实这已经是有深入到我们自己的代码中了,只是我们对它的认知并不深。这个就后续了解了~~~

实战:构建对象--弹跳球(ES6实现)

  背景

  通过对面向对象基本概念,面向对象的编程思想和构造器,原型实现方法实现一个简单的弹跳球小游戏。

  介绍

  主要通过ES6,class语法糖,通过canvas绘制背景并控制evil的上下左右,吃掉小球。

  上代码

  1.index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Bouncing balls</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<canvas></canvas>
<script src="mainUpgrade.js"></script>
</body>
</html>

  2.mainUpgrade.js

const canvas=document.querySelector('canvas');
const ctx=canvas.getContext('2d'); const width=canvas.width=window.innerWidth;
const height=canvas.height=window.innerHeight; //create a random number between min and max.
random=(min,max)=>{
let num=Math.floor(Math.random()*(max-min+1))+min;
return num;
}; //create constructor for ball
class Shape{
constructor(x,y,velX,velY,exists){
this.x=x;
this.y=y; //坐标
this.velX=velX;
this.velY=velY; //水平和竖直速度
this.exists=exists; //是否存在
}
} class Ball extends Shape{
constructor(x,y,velX,velY,exists,color,size){
super(x,y,velX,velY,exists);
this.color=color;
this.size=size;
} // draw ball
draw (){
ctx.beginPath();
ctx.fillStyle=this.color;
ctx.arc(this.x,this.y,this.size,0,2*Math.PI); // arc()绘制圆弧
ctx.fill();
} //update ball location
update (){
if((this.x + this.size)>=width){
this.velX=-(this.velX)
}
if((this.x - this.size)<= 0){
this.velX=-(this.velX)
}
if((this.y + this.size)>= height){
this.velY=-(this.velY)
}
if((this.y - this.size)<= 0){
this.velY=-(this.velY)
}
this.x+=this.velX;
this.y+=this.velY;
} //spy collision
collisionDetect (){
for(let j=0;j<balls.length;j++){
if(!(this===balls[j])){
const dx=this.x - balls[j].x;
const dy=this.y - balls[j].y;
const distance=Math.sqrt(dx*dx + dy*dy); if(distance<this.size + balls[j].size){
balls[j].color=this.color='rgb('+random(0,255)+','+random(0,255)+','+random(0,255)+')';
}
} }
}
} //create evil circle
class EvilCircle extends Shape{
constructor(x,y,exists){
super(x,y,exists);
this.color='white';
this.size=10;
this.velX=20;
this.velY=20;
}
draw(){
ctx.beginPath();
ctx.strokeStyle=this.color;
ctx.lineWidth=3;
ctx.arc(this.x,this.y,this.size,0,2*Math.PI);
ctx.stroke();
}
//check evil location
checkBounds(){
if((this.x + this.size)>width){
this.x-=this.size
}
if((this.y + this.size)>height){
this.y-=this.size
}
if((this.x - this.size)<0){
this.x+=this.size;
}
if((this.y - this.size)<0){
this.y+=this.size;
}
}
setControls(){
window.onkeydown=(e)=>{
if(e.keyCode===38){
this.y-=this.velY
}
else if(e.keyCode===40){
this.y+=this.velY;
}
else if(e.keyCode===37){
this.x-=this.velX
}
else if(e.keyCode===39){
this.x+=this.velX
}
}
}
collisionDetect(){
for(let i=0;i<balls.length;i++){
if(balls[i].exists){
const dx=this.x-balls[i].x;
const dy=this.y-balls[i].y;
const distance=Math.sqrt(dx*dx+dy*dy); if(distance<this.size+balls[i].size){
balls[i].exists=false;
}
}
}
}
} let balls=[]; const evil=new EvilCircle(
random(0,width),
random(0,height),
true
); loop=()=>{
ctx.fillStyle='rgba(0,0,0,0.25)';
ctx.fillRect(0,0,width,height); while (balls.length < 25){
const ball=new Ball(
random(0,width),
random(0,height),
random(-7,7),
random(-7,7),
true,
'rgb('+ random(0,255)+','+random(0,255)+','+random(0,255)+')',
random(10,20)
);
balls.push(ball);
} for(let i=0;i<balls.length;i++){
if(balls[i].exists){
balls[i].draw();
balls[i].update();
balls[i].collisionDetect();
}
} evil.draw();
evil.checkBounds();
evil.setControls();
evil.collisionDetect(); window.requestAnimationFrame(loop) //执行动画并请求浏览器在下一次重绘之前调用指定的函数来更新动画
} loop();

  运行截图

总结

  通过该博文,希望能让大家对了解JavaScript实现面向对象有个基本的了解和概念。如有描述不当的地方望能指出,谢谢。

JavaScript对象入门指南的更多相关文章

  1. AngularJS快速入门指南07:Http对象

    $http是AngularJS提供的一个服务,用来从远程服务器读取数据. 提供数据 下面的数据由Web服务器提供: { "records": [ { "Name" ...

  2. JavaScript对象属性的基础教程指南

    JavaScript是使用“对象化编程”的,或者叫“面向对象编程”的.所谓“对象化编程”,意思是把JavaScript能涉及的范围划分成大大小小的对象,对象下面还继续划分对象直至非常详细为止,所有的编 ...

  3. TypeScript入门指南(JavaScript的超集)

    TypeScript入门指南(JavaScript的超集)   你是否听过 TypeScript? TypeScript 是 JavaScript 的超集,TypeScript结合了类型检查和静态分析 ...

  4. 深入理解javascript对象系列第二篇——属性操作

    × 目录 [1]查询 [2]设置 [3]删除[4]继承 前面的话 对于对象来说,属性操作是绕不开的话题.类似于“增删改查”的基本操作,属性操作分为属性查询.属性设置.属性删除,还包括属性继承.本文是对 ...

  5. Webpack 入门指南 - 2.模块

    这一次我们谈谈模块问题. 通常我们希望这个项目可以分为多个独立的模块,比如,上一次提高的 hello 函数,如果我们定义为一个模块,其它模块引用之后,直接调用就好了.在前端怎么使用模块呢?这可说来话长 ...

  6. AngularJS快速入门指南19:示例代码

    本文给出的大部分示例都可以直接运行,通过点击运行按钮来查看结果,同时支持在线编辑代码. <div ng-app=""> <p>Name: <input ...

  7. AngularJS快速入门指南16:Bootstrap

    thead>tr>th, table.reference>tbody>tr>th, table.reference>tfoot>tr>th, table ...

  8. AngularJS快速入门指南15:API

    thead>tr>th, table.reference>tbody>tr>th, table.reference>tfoot>tr>th, table ...

  9. Vue 入门指南 JS

    Vue 入门指南 章节导航 英文:http://vuejs.org/guide/index.html 介绍 vue.js 是用来构建web应用接口的一个库 技术上,Vue.js 重点集中在MVVM模式 ...

随机推荐

  1. Selenium里可以自行封装与get_attribute对应的set_attribute方法

    我们在做UI自动化测试的过程中,某些情况会遇到,需要操作WebElement属性的情况. 假设现在我们需要获取一个元素的title属性,我们可以先找到这个元素,然后利用get_attribute方法获 ...

  2. R语言︱贝叶斯网络语言实现及与朴素贝叶斯区别(笔记)

    每每以为攀得众山小,可.每每又切实来到起点,大牛们,缓缓脚步来俺笔记葩分享一下吧,please~ --------------------------- 一.贝叶斯网络与朴素贝叶斯的区别 朴素贝叶斯的 ...

  3. JLINK 10针J和20针JTAG接口连接方法

    我的JLINK终于用上了,哈哈,好开心,终于不用考虑是不是要借用别人的PC机了,昨天到城隍庙电子市场忙活了一下午,终于算是满载而归,呵呵,好了,下面说一下接法,其实根本不需要什么转接板什么的,直接把相 ...

  4. l【linux】linux rpm包命名规范

    RPM包的一般格式为:name-version-arch.rpmname-version-arch.src.rpm name:软件包名称.version:带有主.次和修订的软件包版本.arch:硬件平 ...

  5. HighCharts之2D带Label的折线图

    HighCharts之2D带Label的折线图 1.HighCharts之2D带Label的折线图源码 LineLabel.html: <!DOCTYPE html> <html&g ...

  6. STM32F4 输入输出(GPIO)模式理解

    stm32的GPIO的配置模式有好几种,包括: 1. 模拟输入: 2. 浮空输入: 3. 上拉输入: 4. 下拉输入: 5. 开漏输出: 6. 推挽输出: 7. 复用开漏输出: 8. 复用推挽输出   ...

  7. freemarker报错之一

    freemarker 1.错误描述 java.io.FileNotFoundException: Template user.ftl not found. at freemarker.template ...

  8. Netty的并发编程实践5:不要依赖线程优先级

    当有多个线程同时运行的时候,由线程调度器来决定哪些线程运行.哪些等待以及线程切换的时间点,由于各个操作系统的线程调度器实现大相径庭,因此,依赖JDK自带的线程优先级来设置线程优先级策略的方法是错误和非 ...

  9. 从1.5K到18K,一个程序员的5年成长之路

    原文地址:点击打开链接 168楼朋友批评的很有道理, 虚心接受. 我自己是开始学的时候已经错过了基础课的学习, 现在也是深受其苦的, 面临技术上的瓶颈, 需要花更多的时间补充这些知识. 希望看到此文的 ...

  10. SQL语句异常导致项目报错

    1.错误描述 严重:Exception occurred during processing request:Statement Callback;SQL[   ];OALL8处于不一致状态; nes ...