欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

前端笔记之JavaScript面向对象(三)初识ES6&underscore.js&EChart.js&设计模式&贪吃蛇开发

程序员文章站 2022-05-07 19:33:56
一、ES6语法 ES6中对数组新增了几个函数:map()、filter()、reduce() ES5新增的forEach()。 都是一些语法糖。 1.1 forEach()遍历数组 forEach()方法用来循环遍历数组,方法中的function回调函数接收3个参数 参数1是遍历的数组内容(item ......

一、es6语法

es6中对数组新增了几个函数:map()filter()reduce()

es5新增的foreach()

都是一些语法糖。

1.1 foreach()遍历数组

foreach()方法用来循环遍历数组,方法中的function回调函数接收3个参数

参数1是遍历的数组内容(item);参数2是对应的数组索引(index),参数3是是数组本身(array)

[].foreach(function(item,index,array){
...
})
var arr = ["白板","幺鸡","红中","发财","八万"];
arr.foreach(function(item,index,array){
   console.log(item,index,array)
})

前端笔记之JavaScript面向对象(三)初识ES6&underscore.js&EChart.js&设计模式&贪吃蛇开发

foreach函数没有返回值

for今后是创建数组,遍历或操作数组可以交给foreach方法。


1.2 map()映射

map方法的作用,“映射”也就是原数组被“映射”成对应的新数组

[].map(function(item,index,array){
...
})

var arr = ["白板","幺鸡","红中","发财","八万"];
arr.map(function(item,index,array){
   console.log(item,index,array)
})

 前端笔记之JavaScript面向对象(三)初识ES6&underscore.js&EChart.js&设计模式&贪吃蛇开发

 

案例:比如创建一个新数组,新数组的每一项是原数组的两倍

var arr1 = [6,8,10,88,100,200];
var arr2 = arr1.map(function(item){
   return item * 2;
})
console.log(arr1)
console.log(arr2)

前端笔记之JavaScript面向对象(三)初识ES6&underscore.js&EChart.js&设计模式&贪吃蛇开发

map()函数本质是依次遍历原数组中的每一项,将每一项都执行一遍函数中的语句,返回一个新的数组。

提示:

函数需要有return值,如果没有,数组所有想都被映射成undefined

l map返回的数组一定和原数组长度一样。


1.3 filter()过滤

filter为“过滤”、“筛选”之意,指从原数组中filter某些项后,返回过滤后的新数组,用法和map相似。

 

案例:从原数组中,挑选所有的偶数,返回新的数组。

var arr1 = [6,8,10,77,88,100,200,1,3,5];
var arr2 = arr1.filter(function(item){
   return item % 2 == 0;
});
console.log(arr1)
console.log(arr2)

前端笔记之JavaScript面向对象(三)初识ES6&underscore.js&EChart.js&设计模式&贪吃蛇开发

概述:arr中的每一项依次的执行函数,filter的回调函数需要返回布尔值,true则将值返回到新数组中,false则舍弃,进入下一次循环。

 

filtermap相同:都会遍历数组每一项。

filtermap不相同:map返回数组不会少项,filter可能会少项。


1.4取整运算符

console.log(~~11.5);
console.log(~~"11.5");
console.log(~~"10px");
console.log(~~true);
console.log(~~false);

二、underscore.js

jquerydom之王,那么underscore就是数学之王(擅长计算)。

underscore一个javascript实用库,提供了一整套函数式编程的实用功能,但是没有扩展任何javascript内置对象。

underscore提供了100多个函数,包括常用的: map, filter, invoke 当然还有更多专业的辅助函数,:函数绑定, javascript模板功能,创建快速索引, 强类型相等测试, 等等.

underscore不依赖环境,不限制使用场景,可以加载到html中在浏览器运行,也可以中nodejs服务器环境中使用。封装了一堆实用函数,这些函数基本都是针对:数组、对象、函数的。

官网:

中文文档:

cdn公共资源库:

 

生成0~100的随机数:
_.random(0,100); //生成0~100的随机数
创建一个范围整数数组:
_.range(1,10) //[1, 2, 3, 4, 5, 6, 7, 8, 9]
取数组中的最大和最小值:
var num = [10, 5, 100, 2, 1000];
console.log(_.min(num));
console.log(_.max(num));
把数组转成对象:
_.object(['a', 'b', 'c'], [10, 20, 30]); //{ a: 10, b: 20, c: 30 }
each()遍历方法,对集合循环操作,可以遍历数组、类数组元素,arguments
_.each(['小王','大王','鬼王'],function(item, index){
    console.log(item,index)
});
json遍历:
_.each({'小王':'100','大王':'200','鬼王':'300'},function(item, index){
    console.log(item,index)
});
map(): 对集合以map方式遍历,产生一个新数组
var arr3 = _.map({a: 1, b: 2, c: 3}, function(item, key){
    return item * 3;
});
console.log(arr3); //[3, 6, 9]
filter(): 过滤集合中符合条件的元素
var arr4 = _.filter([1, 2, 3, 4, 5, 6], function(item){ 
return item % 2 == 0; 
});
console.log(arr4) //[ 2, 4, 6 ]
sortby() 自定义比较方法
var sort = _.sortby([3, 4, 2, 1 , 6 ,88], function(item){
    return math.max(item);
})
console.log(sort)

三、模板引擎

3.1 underscore模板引擎

template()方法可接受三个参数:

参数1:是必须的参数是模版字符串,你可以通过<%=  %> 来插入变量,还可以通过<%  %>来插入js代码,也可以通过<%-  %>来进行html转义,如果变量很多,可以使用<% print() %>来简化。

参数2:是传入模版的数据,如果不传第二个参数,那么这个方法会返回一个模版函数,这个模版函数可以传入数据返回完成的模版,如果传入data参数则会直接返回一个已完成的模版。

参数3:是设置,比如这个方法默认是寻找<%  %>来进行替换,可以通过设置它来改变具体的方法。

_.template 支持以下三种模板:
<% %>  执行一些代码
<%= %> 在模板中打印或者说成输出一些值
<%- %> 打印一些html转义的值

解释:

<% %> 里包裹的是一些可执行的 javascript 语句,比如 if-else 语句,for 循环语句等等。

<%= %> 会打印传入数据相应的 key 的值,

<%- %> 和前者相比,多了步 html 实体编码的过程,可以有效防止 xss 攻击。

 

//模板
var str = "我很<%= xinqing %>啊!买了一个<%= dongxi%>,花了<%= price%>元";
//通过move字符串生成一个数据绑定函数
var compile = _.template(str);
//数据
var obj = {
   xinqing:"高兴",
   dongxi:"iphone手机",
   price:8888
}
//字符串和数据进行绑定生成
str = compile(obj);
console.log(str)

 

还可以将html作为模板,将js的数据,注入进去,将模板中“写死”的内容都用模板的标记代替。

 

<head>
    <meta charset="utf-8" />
    <title>document</title>
    <style type="text/css">
        .bgcolor{
            background: red;
        }
    </style>
</head>
<body>
    <table id="table">
        <tr>
            <td>学号</td>
            <td>姓名</td>
            <td>年龄</td>
            <td>性别</td>
        </tr>
    </table>
</body>
<!-- 我们使用一个故意写错的type的标签存放模板 -->
<script type="text/template" id="template">
    <tr class="<%= leiming %>">
        <td><%= id %></td>
        <td><%= name %></td>
        <td><%= age %></td>
        <td><%= sex %></td>
    </tr>
</script>
<script type="text/javascript" src="js/jquery-3.2.1.min.js"></script>
<script type="text/javascript" src="js/underscore-min.js"></script>
<script type="text/javascript">
     //通过模板字符串生成一个数据绑定函数
     var compile = _.template($("#template").html());

     //ajax读取数据
     $.get("data/student.json", function(data){
        //遍历数据
        _.each(data.result, function(obj){
            obj.leiming = obj.age >= 18 ? "" : "bgcolor";
            //数据绑定,得到dom字符串
            var str = compile(obj);
            // 上树
            $("table").append(str);
        })
     })
</script>

 


 

 

3.2模板引擎原理(js

拼接字符串很不爽,容易出错。

所以就有工程师在大量的实战中,提出模板引擎的概念,就是在一个完整的字符串中,把未定的量用特殊的语法来表示

@xinqing@

然后把这些数据替换成标记,这个操作叫数据绑定。

<script type="text/javascript">
     //模板
     var str = "我很@xinqing@啊!买了一个@dongxi@,花了@price@元";

     //数据
     var obj = {
        xinqing:"高兴",
        dongxi:"iphone手机",
        price:8888
     }

     //封装数据绑定方法
     function complie(tplstr,tplobj){
        tplstr = tplstr.replace(/\@([a-za-z]+)\@/g, function(match,$1){
            return tplobj[$1];
        })
        return tplstr;
     }
     //调用数据绑定函数
     str = complie(str, obj)
     console.log(str)
</script>

 

 使用ajax

<body>
    <table id="table">
        <tr>
            <td>学号</td>
            <td>姓名</td>
            <td>年龄</td>
            <td>性别</td>
        </tr>
    </table>
</body>
<!-- 我们使用一个故意写错的type的标签存放模板 -->
<script type="text/template" id="template">
    <tr class="@leiming@">
        <td>@id@</td>
        <td>@name@</td>
        <td>@age@</td>
        <td>@sex@</td>
    </tr>
</script>
<script type="text/javascript" src="js/jquery-3.2.1.min.js"></script>
<script type="text/javascript">
     //通过模板字符串生成一个数据绑定函数
     var str = $("#template").html();
     //ajax读取数据
     $.get("data/student.json", function(data){
        data.result.foreach(function(item){
            var domstr = complie(str, item);
            $("#table").append(domstr)
        })
     })

     //封装数据绑定方法
     function complie(tplstr,tplobj){
        tplstr = tplstr.replace(/\@([a-za-z]+)\@/g, function(match,$1){
            return tplobj[$1];
        })
        return tplstr;
     }
</script>

 


四、echart.js(前端数据可视化)

官网:

api配置项:

第一步:引入js文件:
<script type="text/javascript" src="js/echarts.min.js"></script>

第二步:准备一个放图表的容器
<div id="main" style="width:600px;height:400px;"></div>

第三步:设置参数,初始化图表

注意:这里案例是最基础,但在使用echarts时一定要配置xaxis,yaxis,series这三个参数。如果不想设置也要初始化,将它设置为空json即可,要不然会报错。同时要保证在echarts.init之前的对象是有宽高的,要不然也会报错。

// 基于准备好的dom,初始化echarts实例
var mychart = echarts.init(document.getelementbyid('main'));
// 指定图表的配置项和数据
var option = {
    title: {
        text: '数据统计'
    },
    tooltip: {}, //悬浮提示
    legend: {
        data:['访问量']
    },
    xaxis: {
        data: ["android","ios","pc","ohter"]
    },
    yaxis: {},
    series: [{
        name: '访问量',  //nam == legend.data的时候才能显示图例
        type: 'bar',  //这里可以改成line或pie
        data: [500, 200, 360, 100]
    }]
};
// 使用刚指定的配置项和数据显示图表。
mychart.setoption(option);

前端笔记之JavaScript面向对象(三)初识ES6&underscore.js&EChart.js&设计模式&贪吃蛇开发

简单的统计图表就出来了,官网使用的是柱状图,可以改成其他形状

 

//基于准备好的dom,初始化echarts实例
var mychart = echarts.init(document.getelementbyid('main'));
// 指定图表的配置项和数据
var option = {
   title: {
       text: 'echarts数据统计'
   },
   tooltip: {}, //悬浮提示
   legend: {
       data:['占有率']
   },
   xaxis: {
       data: ["android","ios","pc","other"]
   },
   yaxis: {},
   series: [{
       name: '占有率', //name==legend.data相等的时候才能显示图例
       type: 'line',
       data: [50, 120, 36, 100]
   }]
};
// 使用刚指定的配置项和数据显示图表。
mychart.setoption(option);

前端笔记之JavaScript面向对象(三)初识ES6&underscore.js&EChart.js&设计模式&贪吃蛇开发

饼状图和折线图、柱状图有一点区别,主要是在参数和数据绑定上。饼状图没有xy轴坐标,数据绑定也是采用valuename对应的形式。

 

var option = {
    title:{
        text:'周销量统计',
    subtext:'虚拟数据'
    },
tooltip:{
    formatter:'系列名:{a}<br />类目:{b}<br />数值:{c}'
},
    legend:{
        data:['购买金额','销售金额']
    },
    xaxis:{
        data:["周一","周二","周三","周四","周五","周六","周日"]
    },
    yaxis:{},
    series:[{
        name:'购买金额',
        type:'bar',
        data:[200,312,431,241,175,275,369],
        markpoint: {
            data: [
                {type: 'max', name: '最大值'},
                {type: 'min', name: '最小值'}
            ]
        },
        markline:{
            data:[
                {type:'average',name:'平均值',itemstyle:{
                    normal:{color:'green'}
                }}
            ]
        }
    },{
        name:'销售金额',
        type:'line',
        data:[321,432,543,376,286,298,400],
        markpoint: {
            data: [
                {type: 'max', name: '最大值'},
                {type: 'min', name: '最小值'}
            ]
        },
        markline:{
            data:[
                {type:'average',name:'平均值',itemstyle:{
                    normal:{color:'blue'}
                }}
            ]
        }
    }]
};

 

前端笔记之JavaScript面向对象(三)初识ES6&underscore.js&EChart.js&设计模式&贪吃蛇开发


五、js设计模式

到目前为止,我们每个案例都是一个构造函数:ballon类、girl类等等,单打独斗。此时要学习多个类之间一起配合工作,最重要的就是信息的传递(就是类和类之间的数据的传递)。

 

什么是设计模式?

在大的项目中,一定有很多个类一起协作完成一个事,工程师们经过多年的经验,写了很多类和类之间的配合和协调工作的一些套路,这些套路我们叫“设计模式”。

设计模式的定义就是在面向对象的开发、设计过程中,针对特定的问题的简洁而又优雅的解决方案,通俗的就是说给程序的思想起了一个名字 “设计模式”。

 

设计模式一共23种:

图所示23种设计模式,实际上被分为了三个大种类。现在要学习的两种设计模式,是最著名的设计模式。

前端笔记之JavaScript面向对象(三)初识ES6&underscore.js&EChart.js&设计模式&贪吃蛇开发

 

比如现在有两个类:老师类和学生类,老师类有留作业方法(liuzuoye),要求调用这个方法后,每个学生都拥有zuoye这个属性,初学者往往写出这样的代码:

//老师类
function teacher(){
}
teacher.prototype.liuzuoye = function(content){
   alert("老师留了作业:" + content);
   xiaoming.zuoye = content; //动用了其他类的实例的名字在此,耦合性高
   xiaohong.zuoye = content;
}
//学生类
function student(){
   this.zuoye = '';
}
var zhulaoshi = new teacher();
var xiaoming = new student();
var xiaohong = new student();
zhulaoshi.liuzuoye("完成贪吃蛇!");
alert(xiaoming.zuoye)
alert(xiaohong.zuoye)

而软件设计(尤其是面向对象开发时)讲究的是“高内聚、低耦合”。耦合性就是类尽量不要用到其他类的实例,如果其他类改名了,你的类就错了。

为了降低耦合性,经过大量实践,总结了很多设计模式,降低耦合性。


 

5.1观察者模式

观察者模式(observer也叫发布-订阅模式(publish-subscribe。它定义了对象间的一种1n的依赖关系。当一个对象的状态发生改变时,所有“订阅”了它的对象都将得到通知。

刚刚老师发布作业的案例,老师就是发布者(publisher),学生就是订阅者、观察者(subscriber)。老师是1,学生是n发布者(老师)要维护自己的订阅者(学生)列表,自己有一个属性students存放着所有订阅自己的列表(实例数组),当发布作业时,用for循环数组清单,分别调用每个订阅者相应的方法即可。

 

精髓:发布者维持一个订阅自己的数组,当自己要发布信息的时候,循环遍历自己的数组调用订阅者的方法。

 

//老师类
function teacher(){
   //维护自己订阅者的列表
   this.students = [];
}
//提供一个注册(关注订阅)方法
teacher.prototype.regist = function(obj){
   this.students.push(obj)
}
teacher.prototype.liuzuoye = function(content){
    alert("老师留了作业:" + content );
    //遍历所有学生,分别调用它们的listen监听作业方法,把信息通过实参传递过去
    for(var i = 0;i < this.students.length;i++){
       this.students[i].listen(content)
    }
}
//学生类
function student(teacher){
   // 去注册成为指定老师的学生
   teacher.regist(this)
}
student.prototype.listen = function(zuoye){
   this.zuoye = zuoye;
}
//实例化
var zhulaoshi = new teacher(); //发布者要先实例化
var kaola = new teacher(); //发布者要先实例化
var xiaoming = new student(zhulaoshi); //注册成为zhulaoshi的学生
var xiaohong = new student(zhulaoshi); //注册成为zhulaoshi的学生
var xiaogang = new student(kaola); //注册成为kaola的学生
var xiaobai = new student(kaola); //注册成为kaola的学生
//老师留作业
zhulaoshi.liuzuoye("完成贪吃蛇");
kaola.liuzuoye("写代码");
alert(xiaoming.zuoye)
alert(xiaohong.zuoye)
alert(xiaogang.zuoye)
alert(xiaobai.zuoye)

 

设计模式的好处在于程序足够大的时候使用


 

案例:汇率转换小程序

人民币换美元、欧元、日元、英镑、泰铢

当用户输入人民币的时候,需要实时显示对应的外币的数值。

人民币类(rmb)就是发布者,外币类waibi就是订阅者

 

//人民币类
function rmb(){
   //维护自己订阅者的列表
   this.listen = [];
   this.init();
   this.bindevent();
}
rmb.prototype.init = function(){
   this.p = document.createelement('p');
   this.p.innerhtml = "人民币:";
   this.input = document.createelement('input');
   this.p.appendchild(this.input)
   document.body.appendchild(this.p);
}
//提供一个注册(关注订阅)方法,可以将某一个币种添加到自己的数组中
rmb.prototype.regist = function(obj){
   this.listen.push(obj)
}
//监听发布者改变时的状态
rmb.prototype.bindevent = function(){
   //告诉用户输入人民币金额时,遍历自己所有的订阅者,调用他们的监听方法,将数值告诉他们
   //“告诉”是通过调用他们的方法实现,通过实参把数据传递给他们
   var self = this;
   this.input.oninput = function(){
       for(var i = 0;i < self.listen.length;i++){
           self.listen[i].listen(this.value);
       }
   }
}
//订阅者,外币类
function waibi(name, huilv){
   this.name = name;
   this.huilv = huilv;
   //观察者模式要求,就是去发布者哪里注册自己
   //订阅人民币类,订阅者就是要订阅发布者
   rmb.regist(this);
   this.init();
}
waibi.prototype.init = function(){
   this.p = document.createelement('p');
   this.p.innerhtml = this.name + ":";
   this.input = document.createelement('input');
   this.input.disabled = true;
   this.p.appendchild(this.input)
   document.body.appendchild(this.p);
}
//收听人民币的最新数值,此时改变自己dom中的数据
//订阅者有一个listen监听发布者的方法,用来接收响应发布者的最新消息
waibi.prototype.listen = function(content){
   this.input.value = content / this.huilv;
}
var rmb = new rmb();
new waibi("美元", 6.8894);
new waibi("韩元", 0.0061);
new waibi("港币", 0.8799);
new waibi("英镑", 8.9542);
new waibi("日元", 0.0609);

 

 

 


5.2中介者模式

观察者模式的精髓在于“主动通知”,当老师的状态改变的时候,能够实时通知学生,通过调用学生的方法来实现的。中介者模式简单一点,不能主动通知。

老师要发布作业,此时发布到qq群里;学生看作业去qq群看就行了!qq群就是中介者,相当于全局变量。

 前端笔记之JavaScript面向对象(三)初识ES6&underscore.js&EChart.js&设计模式&贪吃蛇开发

后面制作一些复杂的dom程序,中介者模式是使用最多的。99%dom效果都是中介者模式来制作的。

//********中介者qq群类*********
function qqqun(){
   this.zuoye = '';
}
//老师类
function teacher(){
}
teacher.prototype.liuzuoye = function(content){
   qqqun.zuoye = content; //发布作业到qq群
}
//学生类
function student(){
}
student.prototype.xiezuoye = function(){
   alert("我要写作业啦!"+ qqqun.zuoye)
}
var qqqun = new qqqun(); //先实例化中介者
//实例化老师
var zhulaoshi = new teacher();
zhulaoshi.liuzuoye("完成贪吃蛇!");
//实例化学生
var xiaoming = new student();
var xiaohong = new student();
xiaoming.xiezuoye()
xiaohong.xiezuoye()
两个模式的区别:
观察者模式能主动推送消息,每个收听者能够实时得到发布者的信息;
中介者模式不主动推送消息,当学生要写作业,需要作业信息时,主动去找中介者拿,适合时效性不强的信息。

六、贪吃蛇游戏-中介者模式

有的时候,项目中的类很多,没有所谓的1:n1n)的关系,它们感觉“互为信息源”,此时中介者模式是最简单的。比如蛇需要食物,食物也要蛇的信息。

 

用贪吃蛇来举例:

游戏有三个类:游戏类(game)、蛇类(snake)、食物类(food

游戏类其实就是中介者,蛇和食物都需要通过game来交换获取信息。

 

game类是中介者类,snakefood类是普通类,被game管理。

注意:

中介者必须有唯一的实例化对象,这个对象的名字不能更改,比如群号码不能更改。在贪吃蛇游戏中,我们就要:

var game = new game();
此时game变量名一旦确定就不要改了。

 

除了中介者之外,其他所有的对象,都需要由中介者来实例化,在贪吃蛇游戏中,snake蛇、food食物类由game类来实例化,它们都是game类的子属性。

function game(){
this.snake = new snake();
this.food = new food();
}

 

foodsnake之间如果互相要用信息,比如蛇要知道食物的位置,用中介者:

function snake(){
console.log(game.food.x)
}

这是我们第一次将一个类单独写在js中,每个js文件就是一个类

第一步:创建index文件,创建game.js

<!doctype html>
<html>
<head>
    <meta charset="utf-8" />
    <title>贪吃蛇</title>
</head>
<body>
    <div id="app">

    </div>
</body>
<script type="text/javascript" src="js/game.js"></script>
<script type="text/javascript" src="js/food.js"></script>
<script type="text/javascript" src="js/snake.js"></script>
<script type="text/javascript">
     var game = new game(); //唯一的中介者
</script>
</html>

 

第二步:创建表格,在游戏中,表格是game类的dom属性,写game类的init方法,创建行和列的个数属性。

注意:所有的代码都写在闭包中,利用window对象暴露唯一的game游戏类即可。

(function(){
    window.game = function(){
        console.log(this)
    }
})();
(function(){
    // 注意:所有的代码都写在闭包中,利用window对象暴露唯一的game游戏类即可。
    window.game = function(){
        this.rowamount = 16; //行数
        this.colamount = 20; //列数
        this.init(); //初始化ui界面,创建dom表格
    }

    //初始化ui界面,创建dom表格
    game.prototype.init = function(){
        this.dom = document.createelement('table');
        document.getelementbyid("app").appendchild(this.dom);
        var tr,td;
        for(var i = 0; i < this.rowamount;i++){
            tr = document.createelement('tr'); //遍历插入行
            this.dom.appendchild(tr); //tr上树
            for(var j = 0; j < this.colamount;j++){
                td = document.createelement('td');//遍历插入列
                tr.appendchild(td); //td上树
            }
        }
    }
})();

 

第三步:创建蛇类,创建snake.js文件(每一个类都是单独一个js文件)

蛇类有自己的身体属性,有render渲染方法。

(function(){
    window.snake = function(){
        //蛇的身体
        this.body = [
            {"row" : 4, "col":7},
            {"row" : 4, "col":6},
            {"row" : 4, "col":5},
            {"row" : 4, "col":4},
            {"row" : 4, "col":3}
        ];
    }
    //渲染蛇的身体方法
    snake.prototype.render = function(){
        for(var i = 0;i < this.body.length; i++){
            //这里写违反了高内聚低耦合的原则,改一改东西的属性应该要调用人家提供的方法
       //game.dom.getelementsbytagname('tr')[this.body[i].row].getelementsbytagname('td')[this.body[i].col].style.background = 'red';
            game.setcolor(this.body[i].row,this.body[i].col,'red');
        }
    }
})();

 

 

 

 game类实例化蛇类,同时提供一个setcolor方法:

(function(){
    // 注意:所有的代码都写在闭包中,利用window对象暴露唯一的game游戏类即可。
    window.game = function(){
        this.rowamount = 16; //行数
        this.colamount = 20; //列数
        this.init(); //初始化ui界面,创建dom表格
        //实例化蛇类
        this.snake = new snake();
        //渲染蛇方法
        this.snake.render();
    }

    //初始化ui界面,创建dom表格
    game.prototype.init = function(){
      ...
}
//设置蛇身的颜色
    game.prototype.setcolor = function(row,col,color){
        this.dom.getelementsbytagname('tr')[row].getelementsbytagname('td')[col]
.style.background = color;
    }
})();

此时会发现报错了:

 前端笔记之JavaScript面向对象(三)初识ES6&underscore.js&EChart.js&设计模式&贪吃蛇开发

原因是在game类的构造函数中,使用game这个实例的名字,此时是undefined,因为构造函数还没执行完毕,还没执行完四步走。

 

解决方法两种:

方法1:在game类中new snake(this)传入上下文,this表示即将返回的game对象。

window.game = function(){
    this.rowamount = 16; //行数
    this.colamount = 20; //列数
    this.init(); //初始化ui界面,创建dom表格
    //实例化蛇类
    this.snake = new snake(this);
    //渲染蛇方法
    this.snake.render();
}

 

snake类中接收this

window.snake = function(mediator){
    //接收game类当做子属性(中介者)
    this.mediator = mediator;
    //蛇的身体,可以让蛇运动起来,头增尾删
    this.body = [
        {"row" : 3, "col":8},
        {"row" : 3, "col":7},
        {"row" : 3, "col":6},
        {"row" : 3, "col":5},
        {"row" : 3, "col":4}
    ];
}
//渲染蛇的身体方法
snake.prototype.render = function(){
    this.mediator.setcolor(this.body[0].row,this.body[0].col,'green');
    for(var i = 1;i < this.body.length; i++){
        this.mediator.setcolor(this.body[i].row,this.body[i].col,'red');
    }
}

 

方法2:利用定时器解决

定时器20毫秒一帧,第一帧就发生在20毫秒之后,此时构造函数已经执行完毕,game对象就已经返回。

创建定时器,在定时器中,渲染蛇。同时加上清屏的语句,在每一帧都是清屏,重新绘制蛇。

window.game = function(){
    this.rowamount = 16; //行数
    this.colamount = 20; //列数
    this.init(); //初始化ui界面,创建dom表格
    //实例化蛇类
    this.snake = new snake();
    //开启游戏定时器
    this.start();
}
//初始化ui界面,创建dom表格
game.prototype.init = function(){
    this.dom = document.createelement('table');
    document.getelementbyid("app").appendchild(this.dom);
    var tr,td;
    for(var i = 0; i < this.rowamount;i++){
        tr = document.createelement('tr'); //遍历插入行
        this.dom.appendchild(tr); //tr上树
        for(var j = 0; j < this.colamount;j++){
            td = document.createelement('td');//遍历插入列
            tr.appendchild(td); //td上树
        }
    }
}
//设置蛇身的颜色
game.prototype.setcolor = function(row,col,color){
    this.dom.getelementsbytagname('tr')[row].getelementsbytagname('td')[col].style.background = color;
}
//清屏,遍历行和列,设为白色
game.prototype.clear = function(){
    for(var i = 0;i < this.rowamount; i++){
        for (var j = 0; j < this.colamount; j++) {
            this.dom.getelementsbytagname('tr')[i].getelementsbytagname('td')[j].style.background = "#fff";
        };
    }
}
// 游戏开始方法
game.prototype.start = function(){
    var self = this;
    this.f = 0; //帧编号
    setinterval(function(){
        self.f++;
        document.getelementbyid("info").innerhtml = "帧编号:" + self.f;
        //清屏
        self.clear();
        //每隔30帧更新一下
        self.f % 30 == 0 && self.snake.update();
        //渲染蛇方法
        self.snake.render();
    },20);
}

 

 

第五步:让蛇动起来,套路是,每一帧都清屏,然后更新蛇、渲染蛇...

this.body = [
    {"row" : 4, "col" : 7},
    {"row" : 4, "col" : 6},
    {"row" : 4, "col" : 5},
    {"row" : 4, "col" : 4},
    {"row" : 4, "col" : 3}
];

变为:

this.body = [
{"row" : 4, "col" : 8}
    {"row" : 4, "col" : 7},
    {"row" : 4, "col" : 6},
    {"row" : 4, "col" : 5},
    {"row" : 4, "col" : 4},
];

 

snake控制方向:

//更新方法,这个方法最关键
snake.prototype.update = function(){
    this.body.pop(); //尾删
    this.body.unshift({"row":this.body[0].row, "col":this.body[0].col+1});//头插
}
snake.prototype.update = function(){
    this.body.pop(); //尾删
    //根据方向头插
    switch(this.direction){
        case "r":
            var toucha = {"row": this.body[0].row, "col" : this.body[0].col + 1};
            this.body.unshift(toucha);
            break;
        case "l":
            var toucha = {"row": this.body[0].row, "col" : this.body[0].col - 1};
            this.body.unshift(toucha);
            break;
        case "u":
            var toucha = {"row": this.body[0].row - 1, "col" : this.body[0].col};
            this.body.unshift(toucha);
            break;
        case "d":
            var toucha = {"row": this.body[0].row + 1, "col" : this.body[0].col};
            this.body.unshift(toucha);
            break;
    }
}

 

snake.prototype.changedireciton = function(str){
    this.direction = str;
}

 

//绑定键盘监听,调用changedireciton改变方向方法

 

game.prototype.bindevent = function(){
var self = this;
    document.onkeydown = function(e){
        switch(e.keycode){
            case 37:
                //按左键,如果当前往右走,不允许掉头
                if(self.snake.direction == "r") return;
                self.snake.changedirection("l");
                break;
            case 38:
                if(self.snake.direction == "d") return;
                self.snake.changedirection("u");
                break;
            case 39:
                if(self.snake.direction == "l") return;
                self.snake.changedirection("r");
                break;
            case 40:
                if(self.snake.direction == "u") return;
                self.snake.changedirection("d");
                break;
        }
    }
}

 

 

 

第六步:食物类

我们采用每吃到一次食物,就重新new一个食物

l 食物不能随机到蛇的身上(所在的表格中)

我们采用的是每一帧要清除所有小格的html内容,然后重新渲染所有小格

window.game = function(){
    ...
    this.snake = new snake();
    //实例化食物
    this.food = new food(this);
    this.start();//开启游戏定时器
    this.bindevent();//监听事件
}

 

game.js中:

game.prototype.start = function(){
    this.timer = setinterval(function(){
        self.clear();//清除屏幕
        //更新蛇
        self.f % 30 == 0 && self.snake.update();
        self.snake.render();//渲染蛇
        self.food.render();//渲染食物
    }, 20);
}
//设置食物的方法
game.prototype.sethtml = function(row,col,html){
this.dom.getelementsbytagname('tr')[row].getelementsbytagname('td')[col]
.innerhtml = html;
}

 

food.js类:

(function(){
window.food = function(mediator){
    //随机食物位置,不能在蛇的身上
        var self = this;
        do{
            this.row = ~~(math.random() * mediator.rowamount);
            this.col = ~~(math.random() * mediator.colamount);
        }while((function(){ //iife的执行,返回true或fasle
            for(var i = 0;i < mediator.snake.body.length;i++){
                if(mediator.snake.body[i].row == self.row && mediator.snake.body[j].col == self.col){
                    return true; //食物随机到蛇身上,重新随机一次
                }
                return false; //如果食物不在蛇身上,终止循环
            }
        })());
    }
    food.prototype.render = function(){
        game.sethtml(this.row,this.col,"♥");
    }
})();

 

 

以下在snake.js写:

第七步:吃到食物蛇身变长

// 食物判断
if(toucha.row == game.food.row && toucha.col == game.food.col){
    //当你吃到食物的时候,不用删尾巴,而且需要重新new一个食物
    game.food = new food(game); //传上下文,要中介者(game)
    game.f = 0;
}else{
    //当没有吃到食物时候,删除尾巴一项
    this.body.pop(); //尾删
}

 

第八步:死亡判定,撞墙和撞自己

//撞墙判断
if(toucha.row<0 ||toucha.col<0 || toucha.col > game.colamount-1 || toucha.row > game.rowamount-1){
    alert("你撞墙了,长度是:" + this.body.length);
    this.body.shift(); //撞墙继续头插不合法
    clearinterval(game.timer);
}
// 撞自己判断
for(var i = 1;i < this.body.length;i++){
    if(toucha.row == this.body[i].row && toucha.col == this.body[i].col){
        alert("撞自己啦!傻缺,长度是:" + this.body.length);
        this.body.shift(); //继续头插不合理
        clearinterval(game.timer);
    }
}

更新蛇,蛇越长速度越快

var s = self.snake.body.length < 10 ? 30 : 5;
self.f % s == 0 && self.snake.update();

 

最后,解决bug

snake.prototype.changedirection = function(str){
    //更改未来的方向
    this.willdirection = str;
}
var snake = window.snake = function(){
   //蛇的身体
   ....
   //蛇当前动态的方向
   this.direction = "r";
   //即将设置的方向,这里是为了防止用户按很快出bug。
   this.willdirection = "r";
}
snake.prototype.update = function(){
   //让当前的方向和即将设置的方向一致,这里是为了放置用户按很快出bug。
   this.direction = this.willdirection;
}