1、7 种 JS-IIFE(立即执行函数)写法
IIFE(Immediately Invoked Function Expressions)
叫做立即执行表达式,顾名思义,该表达式一被创建就立即执行。
1.对返回结果不进行处理
(function(形参) {
函数体内容;
})(实参);
2.对返回结果不进行处理
(function(形参) {
函数体内容;
})(实参);
3.返回的是一个布尔值,然后进行取反
!(function(形参) {
函数体内容;
})(实参);
4.对于数字返回的是原来的结果,非数字返回 NaN
+(function(形参) {
函数体内容;
})(实参);
5.对于数字返回的是正负符号相反,非数字返回 NaN
-(function(形参) {
函数体内容;
})(实参);
6.对于数字返回的是正负符号相反再减 1,非数字返回-1
~(function(形参) {
函数体内容;
})(实参);
7.返回的结果是 undefined
void (function(形参) {
函数体内容;
})(实参);
2、基于 Canvas 的原创俄罗斯方块
第一次写俄罗斯方块的时候已经是 1 年多前了,也是我刚刚学 js 不久。
为了加强对 js 的理解又加上对游戏的爱好,于是在没有参考他人的思路和代码下,自己用最基本的 js 代码写出了基于 canvas 的俄罗斯方块。

在大三的暑假,我又用了 es6 的语法进行了改进,包含了 class 的语法糖、箭头函数等,进一步增加自己对 es6 的理解,代码有 400+行
想要做这个小游戏,必须先熟悉 H5 的 canvas,js 对数组的处理,键盘事件监听和处理,定时器的使用等,其他的就是基本的逻辑处理了
游戏的规则就是核心,也是我们代码的重中之重
这里的逻辑核心是需要判断方块是否碰撞(当前运动的方块和已经定位好的方块有碰撞以致于当前的运动的方块不能在向下走,因为我们的方块默认是向下走的,如果不能向下走,是视为已经定位好的方块,然后在生成一个新的方块从初始位置继续往下走)
而且这个碰撞还需要应用在方块变形的时候,同样地,如果方块在变形的过程中和其他定位好的方块进行碰撞,则我们应该阻止这个方块进行变形成功
附上代码,欢迎讨论和指正
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>es6-重构俄罗斯方块(基于canvas)</title>
<style type="text/css">
#tetris{ margin: 10px 250px;}
</style>
</head>
<body>
<canvas width="700" height="525" id="tetris"></canvas>
<div id="text" style='color: red;font-size: 30px;'>当前分数:0</div>
<script type="text/javascript">
/**
* [一个完整的俄罗斯方块类 design by magic_xiang]
* @param {number} side [每个方块边长(px),默认35]
* @param {number} width [一行包含的方块数(个),默认20]
* @param {number} height [一列包含的方块数(个),默认15]
* @param {number} speed [方块下落移动速度(ms),默认400]
*/
class tetris{
constructor(side=35, width=20, height=15, speed=400){
this.side = side // 每个方块边长
this.width = width // 一行包含的方块数
this.height = height // 一列包含的方块数
this.speed = speed // 方块下落移动速度
this.num_blcok // 当前方块类型的数字变量
this.type_color // 当前颜色类型的字符串变量
this.ident // setInterval的标识
this.direction = 1 // 方块方向,初始化为1,默认状态
this.grade = 0 // 用来计算分数
this.over = false // 游戏是否结束
this.arr_bX = [] // 存放当前方块的X坐标
this.arr_bY = [] // 存放当前方块的Y坐标
this.arr_store_X = [] // 存放到达底部所有方块的X坐标
this.arr_store_Y = [] // 存放到达底部所有方块的Y坐标
this.arr_store_color = [] // 存放到达底部所有方块的颜色
this.paints = document.getElementById('tetris').getContext('2d')
//获取画笔
self = this
}
// 封装paints方法,让代码更简洁
paintfr(x, y, scale=1){
this.paints.fillRect(x*this.side, y*this.side, scale*this.side, scale*this.side)
}
// 游戏开始
gameStart(){
this.init()
this.run()
}
// 初始化工作
init(){
this.initBackground()
this.initBlock()
}
// 方块自动下落
run(){
this.ident = setInterval("self.down_speed_up()", this.speed)
}
// 初始化地图
initBackground(){
this.paints.beginPath()
this.paints.fillStyle='#000000' //地图填充颜色为黑色
for(let i = 0; i < this.height; i++){
for(let j = 0; j < this.width; j++){
this.paintfr(j, i)
}
}
this.paints.closePath()
}
// 初始化方块的位置和颜色
initBlock(){
this.paints.beginPath()
this.createRandom('rColor') //生成颜色字符串,
this.paints.fillStyle = this.type_color
this.createRandom('rBlock') //生成方块类型数字
this.arr_bX.forEach((item, index) => {
this.paintfr(item, this.arr_bY[index], 0.9)
})
this.paints.closePath()
}
// 利用数组画方块
drawBlock(color){
this.paints.beginPath()
this.paints.fillStyle = color
this.arr_bX.forEach((item, index) => {
this.paintfr(item, this.arr_bY[index], 0.9)
})
this.paints.closePath()
}
// 画已经在定位好的方块
drawStaticBlock(){
this.arr_store_X.forEach((item, index) => {
this.paints.beginPath()
this.paints.fillStyle = this.arr_store_color[index]
this.paintfr(item, this.arr_store_Y[index], 0.9)
this.paints.closePath()
})
}
// 生成随机数返回方块类型或颜色类型
createRandom(type){
let temp = this.width/2-1
if (type == 'rBlock'){ //如果是方块类型
this.num_blcok = Math.round(Math.random()*4+1)
switch(this.num_blcok){
case 1:
this.arr_bX.push(temp,temp-1,temp,temp+1)
this.arr_bY.push(0,1,1,1)
break
case 2:
this.arr_bX.push(temp,temp-1,temp-1,temp+1)
this.arr_bY.push(1,0,1,1)
break
case 3:
this.arr_bX.push(temp,temp-1,temp+1,temp+2)
this.arr_bY.push(0,0,0,0)
break
case 4:
this.arr_bX.push(temp,temp-1,temp,temp+1)
this.arr_bY.push(0,0,1,1)
break
case 5:
this.arr_bX.push(temp,temp+1,temp,temp+1)
this.arr_bY.push(0,0,1,1)
break
}
}
if (type == 'rColor'){ //如果是颜色类型
let num_color = Math.round(Math.random()*8+1)
switch(num_color){
case 1:
this.type_color='#3EF72A'
break
case 2:
this.type_color='yellow'
break
case 3:
this.type_color='#2FE0BF'
break
case 4:
this.type_color='red'
break
case 5:
this.type_color='gray'
break
case 6:
this.type_color='#C932C6'
break
case 7:
this.type_color= '#FC751B'
break
case 8:
this.type_color= '#6E6EDD'
break
case 9:
this.type_color= '#F4E9E1'
break
}
}
}
// 判断方块之间是否碰撞(下),以及变形时是否越过下边界
judgeCollision_down(){
for(let i = 0; i < this.arr_bX.length; i++){
if (this.arr_bY[i] + 1 == this.height){ //变形时是否越过下边界
return false
}
if (this.arr_store_X.length != 0) { //判断方块之间是否碰撞(下)
for(let j = 0; j < this.arr_store_X.length; j++){
if (this.arr_bX[i] == this.arr_store_X[j]) {
if (this.arr_bY[i] + 1 == this.arr_store_Y[j]) {
return false
}
}
}
}
}
return true
}
//判断方块之间是否碰撞(左右),以及变形时是否越过左右边界
judgeCollision_other(num){
for(let i = 0; i < this.arr_bX.length; i++){
if (num == 1) { //变形时是否越过右边界
if (this.arr_bX[i] == this.width - 1)
return false
}
if (num == -1) { //变形时是否越过左边界
if (this.arr_bX[i] == 0)
return false
}
if (this.arr_store_X.length != 0) { //判断方块之间是否碰撞(左右)
for(let j = 0; j < this.arr_store_X.length; j++){
if (this.arr_bY[i] == this.arr_store_Y[j]) {
if (this.arr_bX[i] + num == this.arr_store_X[j]) {
return false
}
}
}
}
}
return true;
}
//方向键为下的加速函数
down_speed_up(){
let flag_all_down = true
flag_all_down = this.judgeCollision_down()
if (flag_all_down) {
this.initBackground()
for(let i = 0; i < this.arr_bY.length; i++){
this.arr_bY[i] = this.arr_bY[i] + 1
}
}
else{
for(let i=0; i < this.arr_bX.length; i++){
this.arr_store_X.push(this.arr_bX[i])
this.arr_store_Y.push(this.arr_bY[i])
this.arr_store_color.push(this.type_color)
}
this.arr_bX.splice(0,this.arr_bX.length)
this.arr_bY.splice(0,this.arr_bY.length)
this.initBlock()
}
this.clearUnderBlock()
this.drawBlock(this.type_color)
this.drawStaticBlock()
this.gameover()
}
//方向键为左右的左移动函数
move(dir_temp){
this.initBackground()
if (dir_temp == 1) { //右
let flag_all_right = true
flag_all_right = this.judgeCollision_other(1)
if (flag_all_right) {
for(let i = 0; i < this.arr_bY.length; i++){
this.arr_bX[i] = this.arr_bX[i]+1
}
}
}
else{
let flag_all_left = true
flag_all_left = this.judgeCollision_other(-1)
if (flag_all_left) {
for(let i=0; i < this.arr_bY.length; i++){
this.arr_bX[i] = this.arr_bX[i]-1
}
}
}
this.drawBlock(this.type_color)
this.drawStaticBlock()
}
//方向键为空格的变换方向函数
up_change_direction(num_blcok){
if (num_blcok == 5) {
return
}
let arr_tempX = []
let arr_tempY = []
//因为不知道是否能够变形成功,所以先存储起来
for(let i = 0;i < this.arr_bX.length; i++){
arr_tempX.push(this.arr_bX[i])
arr_tempY.push(this.arr_bY[i])
}
this.direction++
//将中心坐标提取出来,变形都以当前中心为准
let ax_temp = this.arr_bX[0]
let ay_temp = this.arr_bY[0]
this.arr_bX.splice(0, this.arr_bX.length) //将数组清空
this.arr_bY.splice(0, this.arr_bY.length)
if (num_blcok == 1) {
switch(this.direction%4){
case 1:
this.arr_bX.push(ax_temp,ax_temp-1,ax_temp,ax_temp+1)
this.arr_bY.push(ay_temp,ay_temp+1,ay_temp+1,ay_temp+1)
break
case 2:
this.arr_bX.push(ax_temp,ax_temp-1,ax_temp,ax_temp)
this.arr_bY.push(ay_temp,ay_temp,ay_temp-1,ay_temp+1)
break
case 3:
this.arr_bX.push(ax_temp,ax_temp-1,ax_temp,ax_temp+1)
this.arr_bY.push(ay_temp,ay_temp,ay_temp+1,ay_temp)
break
case 0:
this.arr_bX.push(ax_temp,ax_temp,ax_temp,ax_temp+1)
this.arr_bY.push(ay_temp,ay_temp-1,ay_temp+1,ay_temp)
break
}
}
if (num_blcok == 2) {
switch(this.direction%4){
case 1:
this.arr_bX.push(ax_temp,ax_temp-1,ax_temp-1,ax_temp+1)
this.arr_bY.push(ay_temp,ay_temp,ay_temp-1,ay_temp)
break
case 2:
this.arr_bX.push(ax_temp,ax_temp,ax_temp,ax_temp-1)
this.arr_bY.push(ay_temp,ay_temp-1,ay_temp+1,ay_temp+1)
break
case 3:
this.arr_bX.push(ax_temp,ax_temp-1,ax_temp+1,ax_temp+1)
this.arr_bY.push(ay_temp,ay_temp,ay_temp,ay_temp+1)
break
case 0:
this.arr_bX.push(ax_temp,ax_temp,ax_temp,ax_temp+1)
this.arr_bY.push(ay_temp,ay_temp-1,ay_temp+1,ay_temp-1)
break
}
}
if (num_blcok == 3) {
switch(this.direction%4){
case 1:
this.arr_bX.push(ax_temp,ax_temp-1,ax_temp+1,ax_temp+2)
this.arr_bY.push(ay_temp,ay_temp,ay_temp,ay_temp)
break
case 2:
this.arr_bX.push(ax_temp,ax_temp,ax_temp,ax_temp)
this.arr_bY.push(ay_temp,ay_temp-1,ay_temp+1,ay_temp+2)
break
case 3:
this.arr_bX.push(ax_temp,ax_temp-1,ax_temp+1,ax_temp+2)
this.arr_bY.push(ay_temp,ay_temp,ay_temp,ay_temp)
break
case 0:
this.arr_bX.push(ax_temp,ax_temp,ax_temp,ax_temp)
this.arr_bY.push(ay_temp,ay_temp-1,ay_temp+1,ay_temp+2)
break
}
}
if (num_blcok == 4) {
switch(this.direction%4){
case 1:
this.arr_bX.push(ax_temp,ax_temp-1,ax_temp,ax_temp+1)
this.arr_bY.push(ay_temp,ay_temp,ay_temp+1,ay_temp+1)
break
case 2:
this.arr_bX.push(ax_temp,ax_temp,ax_temp+1,ax_temp+1)
this.arr_bY.push(ay_temp,ay_temp+1,ay_temp,ay_temp-1)
break
case 3:
this.arr_bX.push(ax_temp,ax_temp,ax_temp-1,ax_temp+1)
this.arr_bY.push(ay_temp,ay_temp-1,ay_temp,ay_temp-1)
break
case 0:
this.arr_bX.push(ax_temp,ax_temp,ax_temp+1,ax_temp+1)
this.arr_bY.push(ay_temp,ay_temp-1,ay_temp,ay_temp+1)
break
}
}
if (! (this.judgeCollision_other(-1) && this.judgeCollision_down() && this.judgeCollision_other(1) )) { //如果变形不成功则执行下面代码
this.arr_bX.splice(0, this.arr_bX.length)
this.arr_bY.splice(0, this.arr_bY.length)
for(let i=0; i< arr_tempX.length; i++){
this.arr_bX.push(arr_tempX[i])
this.arr_bY.push(arr_tempY[i])
}
}
this.drawStaticBlock()
}
//一行满了清空方块,上面方块Y坐标+1
clearUnderBlock(){
//删除低层方块
let arr_row=[]
let line_num
if (this.arr_store_X.length != 0) {
for(let j = this.height-1; j >= 0; j--){
for(let i = 0; i < this.arr_store_color.length; i++){
if (this.arr_store_Y[i] == j) {
arr_row.push(i)
}
}
if (arr_row.length == this.width) {
line_num = j
break
}else{
arr_row.splice(0, arr_row.length)
}
}
}
if (arr_row.length == this.width) {
//计算成绩grade
this.grade++
document.getElementById('text').innerHTML = '当前成绩:'+this.grade
for(let i = 0; i < arr_row.length; i++){
this.arr_store_X.splice(arr_row[i]-i, 1)
this.arr_store_Y.splice(arr_row[i]-i, 1)
this.arr_store_color.splice(arr_row[i]-i, 1)
}
//让上面的方块往下掉一格
for(let i = 0; i < this.arr_store_color.length; i++){
if (this.arr_store_Y[i] < line_num) {
this.arr_store_Y[i] = this.arr_store_Y[i]+1
}
}
}
}
//判断游戏结束
gameover(){
for(let i=0; i < this.arr_store_X.length; i++){
if (this.arr_store_Y[i] == 0) {
clearInterval(this.ident)
this.over = true
}
}
}
}
let tetrisObj = new tetris()
tetrisObj.gameStart()
//方向键功能函数
document.onkeydown = (e) => {
if (tetrisObj.over)
return
switch(e.keyCode){
case 40: // 方向为下
tetrisObj.down_speed_up()
break
case 32: // 空格换方向
tetrisObj.initBackground() //重画地图
tetrisObj.up_change_direction(tetrisObj.num_blcok)
tetrisObj.drawBlock(tetrisObj.type_color)
break
case 37: // 方向为左
tetrisObj.initBackground()
tetrisObj.move(-1)
tetrisObj.drawBlock(tetrisObj.type_color)
break
case 39: // 方向为右
tetrisObj.initBackground()
tetrisObj.move(1)
tetrisObj.drawBlock(tetrisObj.type_color)
break
}
}
</script>
</body>
</html>
3、深入理解 JS 中逻辑或(||)和逻辑与(&&)
先来看一个关于逻辑或的例子:
var b = 0;
var a = b++ || b++;
console.log(b); // 2
var c = ++b || ++b;
console.log(a); // 1
console.log(c); // 3
console.log(b); // 3
关于 ++b 和 b++的区别,后++的运算等级没有赋值运算符(=)高, 这里不再作过多的解释,
首先从全局来看,关于的 b 的运算总共执行了 4 次,每次都自身+1,那么按道理来说,如果这些运算都执行了,b 的最后的值正常来说应该是 4,那为什么最后的结果是 3 呢?
关于逻辑或(||) 我自己总结如下:
①、首先如果第一项的值不是 Boolean 值的话,会转为 Boolean 类型的,在数值上,0 和-0 在转为 Boolean 时为 false,其余为 true,在其他值上诸如 null,undefined,NaN,空 string 类型等会转为 false,可以自己尝试一下
console.log(Boolean(NaN)); // false
console.log(Boolean(null)); // false
console.log(Boolean(undefined)); // false
console.log(Boolean("")); // false
console.log(Boolean(" 0")); // true
②、如果第一项的值为 true 时,则后面一项则不执行,返回的是第一项执行运算后的值 网上看到的是说如果第一项为 true,返回的也是 true,这是不对的说法。
③、如果第一项的值为 false 时,则执行后面一项的运算,返回的是第二项执行运算后的值
再来看多一个例子,自己多动手尝试看看就明白了。
var b = 0;
var a = typeof b++ || b++;
console.log(b); //1
var c = ++b || ++b;
console.log(a); // number
console.log(c); // 2
console.log(b); // 2
-------------------------- 来个分割线----------------------------------
看一个关于逻辑与的例子
var b = 0;
var a = typeof b++ && b++;
console.log(b); // 2
var c = ++b && ++b;
console.log(a); // 1
console.log(c); // 4
console.log(b); // 4
关于逻辑与(&&) 我自己总结如下:
①、逻辑与和逻辑或一样,都需要把前后两项进行 Boolean 转换再比较
②、如果第一项的值为 true 时,则执行后面一项的运算,返回的是第二项执行运算后的值
③、如果第一项的值为 false 时,则后面一项则不执行,返回的是第一项执行运算后的值
4、JS 判断数组的六种方法详解
在 JS 中,数组是属于 Object 类型的,也就是属于引用类型(引用类型存放在堆内存中,在栈内存会有一个或者多个地址来指向这个堆内存)。
所以对于引用类型,我们不能 typeof 来判断具体的类型,因为返回的都是‘object’。
接下来,我将介绍六种判断方法,并且对这六种方法进行逐一解析
① instanceof 操作符判断
用法:arr instanceof Array
instanceof 主要是用来判断某个实例是否属于某个对象
function obj() {}
let o1 = new obj();
console.log(o1 instanceof obj); // true
那么我们用 instanceof 来判断数组的方法如下:
let arr = [];
console.log(arr instanceof Array); // true
但是 instanceof 会有一个问题,它的问题在于假定只有一个全局执行的环境。如果网页中包含多个框架,那实际上就存在两个以上不同的全局执行环境,从而存在两个以上不同版本的 Array 构造函数。如果你从一个框架向另一个框架传入一个数组,那么传入的数组与在第二个框架中原生创建的数组分别具有不同的构造函数。
② 对象构造函数的 constructor 判断
用法:arr.constructor === Array
Object 的每个实例都有构造函数 constructor,用于保存着用于创建当前对象的函数
function obj() {}
let o1 = new obj();
console.log(o1.constructor === obj); // true
如上所示,obj 的实例 o1 的 constructor 跟 obj 对象是相等的
那么我们就可以用此来判断数组了
let arr = [];
console.log(arr.constructor === Array); // true
③ Array 原型链上的 isPrototypeOf
用法:Array.prototype.isPrototypeOf(arr)
Array.prototype 属性表示 Array 构造函数的原型
其中有一个方法是 isPrototypeOf() 用于测试一个对象是否存在于另一个对象的原型链上。
let arr = [];
console.log(Array.prototype.isPrototypeOf(arr)); // true
④ Object.getPrototypeOf
用法:Object.getPrototypeOf(arr) === Array.prototype
Object.getPrototypeOf() 方法返回指定对象的原型
所以只要跟 Array 的原型比较即可
let arr = [];
console.log(Object.getPrototypeOf(arr) === Array.prototype); // true
⑤ Object.prototype.toString
用法:Object.prototype.toString.call(arr) === '[object Array]'
虽然 Array 也继承自 Object,但 js 在 Array.prototype 上重写了 toString,而我们通过 toString.call(arr)实际上是通过原型链调用了。
let arr = [];
console.log(Object.prototype.toString.call(arr) === "[object Array]"); // true
⑥ Array.isArray
用法:Array.isArray(arr) ES5 中新增了 Array.isArray 方法,IE8 及以下不支持
Array.isArray ( arg )
isArray 函数需要一个参数 arg,如果参数是个对象并且 class 内部属性是 "Array", 返回布尔值 true;否则它返回 false。采用如下步骤:
如果 Type(arg) 不是 Object, 返回 false。
如果 arg 的 [[Class]] 内部属性值是 "Array", 则返回 true。否则返回 false.
let arr = [];
console.log(Array.isArray(arr)); // true
