javascript高级实战学习

学习目标:
  - 理解面向对象开发思想
  - 掌握 JavaScript 面向对象开发相关模式
  - 掌握在 JavaScript 中使用正则表达式
  - 
typora-copy-images-to media

JavaScript 高级

 

课程介绍

课程大纲

在线地址:JavaScript 高级javascript

目标

  • 理解面向对象开发思想html

  • 掌握 JavaScript 面向对象开发相关模式java

  • 掌握在 JavaScript 中使用正则表达式node

案例演示


基本概念复习

因为 JavaScript 高级仍是针对 JavaScript 语言自己的一个进阶学习,因此在开始以前咱们先对之前所学过的 JavaScript 相关知识点作一个快速复习总结。github

从新介绍 JavaScript

JavaScript 是什么

  • 解析执行:轻量级解释型的,或是 JIT 编译型的程序设计语言正则表达式

  • 语言特色:动态,头等函数 (First-class Function)数据库

    • 又称函数是 JavaScript 中的一等公民编程

  • 执行环境:在宿主环境(host environment)下运行,浏览器是最多见的 JavaScript 宿主环境数组

    • 可是在不少非浏览器环境中也使用 JavaScript ,例如 node.js

  • 编程范式:基于原型、多范式的动态脚本语言,而且支持面向对象、命令式和声明式(如:函数式编程)编程风格

JavaScript 与浏览器的关系

 

JavaScript 的组成

组成部分 说明
Ecmascript 描述了该语言的语法和基本对象
DOM 描述了处理网页内容的方法和接口
BOM 描述了与浏览器进行交互的方法和接口

JavaScript 能够作什么

Any application that can be written in JavaScript, will eventually be written in JavaScript. 凡是能用 JavaScript 写出来的,最终都会用 JavaScript 写出来

JavaScript 发展历史

JavaScript 标准参考教程 - JavaScript 语言的历史

  • JavaScript 的诞生

  • JavaScript 与 Ecmascript 的关系

  • JavaScript 与 Java 的关系

  • JavaScript 的版本

  • JavaScript 周边大事记

小结

基本概念

本小节快速过便可,主要是对学过的内容作知识点梳理。

  • 语法

    • 区分大小写

    • 标识符

    • 注释

    • 严格模式

    • 语句

  • 关键字和保留字

  • 变量

  • 数据类型

    • typeof 操做符

    • Undefined

    • Null

    • Boolean

    • Number

    • String

    • Object

  • 操做符

  • 流程控制语句

  • 函数

JavaScript 中的数据类型

JavaScript 有 5 种简单数据类型:Undefined、Null、Boolean、Number、String 和 1 种复杂数据类型 Object

基本类型(值类型)

  • Undefined

  • Null

  • Boolean

  • Number

  • String

复杂类型(引用类型)

  • Object

  • Array

  • Date

  • RegExp

  • Function

  • 基本包装类型

    • Boolean

    • Number

    • String

  • 单体内置对象

    • Global

    • Math

类型检测

  • typeof

  • instanceof

  • Object.prototype.toString.call()

值类型和引用类型在内存中的存储方式(画图说明)

  • 值类型按值存储

  • 引用类型按引用存储

值类型复制和引用类型复制(画图说明)

  • 值类型按值复制

  • 引用类型按引用复制

值类型和引用类型参数传递(画图说明)

  • 值类型按值传递

  • 引用类型按引用传递

值类型与引用类型的差异

  • 基本类型在内存中占据固定大小的空间,所以被保存在栈内存中

  • 从一个变量向另外一个变量复制基本类型的值,复制的是值的副本

  • 引用类型的值是对象,保存在堆内存

  • 包含引用类型值的变量实际上包含的并非对象自己,而是一个指向该对象的指针

  • 从一个变量向另外一个变量复制引用类型的值的时候,复制是引用指针,所以两个变量最终都指向同一个对象

小结

  • 类型检测方式

  • 值类型和引用类型的存储方式

  • 值类型复制和引用类型复制

  • 方法参数中 值类型数据传递 和 引用类型数据传递

JavaScript 执行过程

JavaScript 运行分为两个阶段:

  • 预解析

    • 全局预解析(全部变量和函数声明都会提早;同名的函数和变量函数的优先级高)

    • 函数内部预解析(全部的变量、函数和形参都会参与预解析)

      • 函数

      • 形参

      • 普通变量

  • 执行

先预解析全局做用域,而后执行全局做用域中的代码,在执行全局代码的过程当中遇到函数调用就会先进行函数预解析,而后再执行函数内代码。


JavaScript 面向对象编程

 

面向对象介绍

什么是对象

Everything is object (万物皆对象)

 

对象究竟是什么,咱们能够从两次层次来理解。

(1) 对象是单个事物的抽象。

一本书、一辆汽车、一我的均可以是对象,一个数据库、一张网页、一个与远程服务器的链接也能够是对象。当实物被抽象成对象,实物之间的关系就变成了对象之间的关系,从而就能够模拟现实状况,针对对象进行编程。

(2) 对象是一个容器,封装了属性(property)和方法(method)。

属性是对象的状态,方法是对象的行为(完成某种任务)。好比,咱们能够把动物抽象为animal对象,使用“属性”记录具体是那一种动物,使用“方法”表示动物的某种行为(奔跑、捕猎、休息等等)。

在实际开发中,对象是一个抽象的概念,能够将其简单理解为:数据集或功能集

ECMAScript-262 把对象定义为:无序属性的集合,其属性能够包含基本值、对象或者函数严格来说,这就至关于说对象是一组没有特定顺序的值。对象的每一个属性或方法都有一个名字,而每一个名字都映射到一个值。

<p class="tip"> 提示:每一个对象都是基于一个引用类型建立的,这些类型能够是系统内置的原生类型,也能够是开发人员自定义的类型。</p>

什么是面向对象

面向对象不是新的东西,它只是过程式代码的一种高度封装,目的在于提升代码的开发效率和可维护性。

 

面向对象编程 —— Object Oriented Programming,简称 OOP ,是一种编程开发思想。它将真实世界各类复杂的关系,抽象为一个个对象,而后由对象之间的分工与合做,完成对真实世界的模拟。

在面向对象程序开发思想中,每个对象都是功能中心,具备明确分工,能够完成接受信息、处理数据、发出信息等任务。所以,面向对象编程具备灵活、代码可复用、高度模块化等特色,容易维护和开发,比起由一系列函数或指令组成的传统的过程式编程(procedural programming),更适合多人合做的大型软件项目。

面向对象与面向过程:

  • 面向过程就是亲力亲为,事无巨细,面面俱到,步步紧跟,有条不紊

  • 面向对象就是找一个对象,指挥得结果

  • 面向对象将执行者转变成指挥者

  • 面向对象不是面向过程的替代,而是面向过程的封装

面向对象的特性:

  • 封装性

  • 继承性

  • [多态性]

扩展阅读:

程序中面向对象的基本体现

在 JavaScript 中,全部数据类型均可以视为对象,固然也能够自定义对象。自定义的对象数据类型就是面向对象中的类( Class )的概念。

咱们以一个例子来讲明面向过程和面向对象在程序流程上的不一样之处。

假设咱们要处理学生的成绩表,为了表示一个学生的成绩,面向过程的程序能够用一个对象表示:

var std1 = { name: 'Michael', score: 98 }
var std2 = { name: 'Bob', score: 81 }

而处理学生成绩能够经过函数实现,好比打印学生的成绩:


function printScore (student) {
 console.log('姓名:' + student.name + ' ' + '成绩:' + student.score)
}

若是采用面向对象的程序设计思想,咱们首选思考的不是程序的执行流程,而是 Student 这种数据类型应该被视为一个对象,这个对象拥有 namescore 这两个属性(Property)。若是要打印一个学生的成绩,首先必须建立出这个学生对应的对象,而后,给对象发一个 printScore 消息,让对象本身把本身的数据打印出来。

抽象数据行为模板(Class):

function Student (name, score) {
 this.name = name
 this.score = score
}

Student.prototype.printScore = function () {
 console.log('姓名:' + this.name + ' ' + '成绩:' + this.score)
}

根据模板建立具体实例对象(Instance):


var std1 = new Student('Michael', 98)
var std2 = new Student('Bob', 81)

实例对象具备本身的具体行为(给对象发消息):


std1.printScore() // => 姓名:Michael 成绩:98
std2.printScore() // => 姓名:Bob 成绩 81

面向对象的设计思想是从天然界中来的,由于在天然界中,类(Class)和实例(Instance)的概念是很天然的。Class 是一种抽象概念,好比咱们定义的 Class——Student ,是指学生这个概念,而实例(Instance)则是一个个具体的 Student ,好比, Michael 和 Bob 是两个具体的 Student 。

因此,面向对象的设计思想是:

  • 抽象出 Class

  • 根据 Class 建立 Instance

  • 指挥 Instance 得结果

面向对象的抽象程度又比函数要高,由于一个 Class 既包含数据,又包含操做数据的方法。

建立对象

简单方式

咱们能够直接经过 new Object() 建立:


var person = new Object()
person.name = 'Jack'
person.age = 18

person.sayName = function () {
 console.log(this.name)
}

每次建立经过 new Object() 比较麻烦,因此能够经过它的简写形式对象字面量来建立:


var person = {
 name: 'Jack',
 age: 18,
 sayName: function () {
   console.log(this.name)
}
}

对于上面的写法当然没有问题,可是假如咱们要生成两个 person 实例对象呢?


var person1 = {
 name: 'Jack',
 age: 18,
 sayName: function () {
   console.log(this.name)
}
}

var person2 = {
 name: 'Mike',
 age: 16,
 sayName: function () {
   console.log(this.name)
}
}

经过上面的代码咱们不难看出,这样写的代码太过冗余,重复性过高。

简单方式的改进:工厂函数

咱们能够写一个函数,解决代码重复问题:


function createPerson (name, age) {
 return {
   name: name,
   age: age,
   sayName: function () {
     console.log(this.name)
  }
}
}

而后生成实例对象:


var p1 = createPerson('Jack', 18)
var p2 = createPerson('Mike', 18)

这样封装确实爽多了,经过工厂模式咱们解决了建立多个类似对象代码冗余的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)。

构造函数

内容引导:

  • 构造函数语法

  • 分析构造函数

  • 构造函数和实例对象的关系

    • 实例的 constructor 属性

    • instanceof 操做符

  • 普通函数调用和构造函数调用的区别

  • 构造函数的返回值

  • 构造函数的静态成员和实例成员

    • 函数也是对象

    • 实例成员

    • 静态成员

  • 构造函数的问题

更优雅的工厂函数:构造函数

一种更优雅的工厂函数就是下面这样,构造函数:


function Person (name, age) {
 this.name = name
 this.age = age
 this.sayName = function () {
   console.log(this.name)
}
}

var p1 = new Person('Jack', 18)
p1.sayName() // => Jack

var p2 = new Person('Mike', 23)
p2.sayName() // => Mike

解析构造函数代码的执行

在上面的示例中,Person() 函数取代了 createPerson() 函数,可是实现效果是同样的。这是为何呢?

咱们注意到,Person() 中的代码与 createPerson() 有如下几点不一样之处:

  • 没有显示的建立对象

  • 直接将属性和方法赋给了 this 对象

  • 没有 return 语句

  • 函数名使用的是大写的 Person

而要建立 Person 实例,则必须使用 new 操做符。以这种方式调用构造函数会经历如下 4 个步骤:

  1. 建立一个新对象

  2. 将构造函数的做用域赋给新对象(所以 this 就指向了这个新对象)

  3. 执行构造函数中的代码

  4. 返回新对象

下面是具体的伪代码:


function Person (name, age) {
 // 当使用 new 操做符调用 Person() 的时候,实际上这里会先建立一个对象
 // var instance = {}
 // 而后让内部的 this 指向 instance 对象
 // this = instance
 // 接下来全部针对 this 的操做实际上操做的就是 instance

 this.name = name
 this.age = age
 this.sayName = function () {
   console.log(this.name)
}

 // 在函数的结尾处会将 this 返回,也就是 instance
 // return this
}

构造函数和实例对象的关系

使用构造函数的好处不只仅在于代码的简洁性,更重要的是咱们能够识别对象的具体类型了。在每个实例对象中的_proto_中同时有一个 constructor 属性,该属性指向建立该实例的构造函数:


console.log(p1.constructor === Person) // => true
console.log(p2.constructor === Person) // => true
console.log(p1.constructor === p2.constructor) // => true

对象的 constructor 属性最初是用来标识对象类型的,可是,若是要检测对象的类型,仍是使用 instanceof 操做符更可靠一些:


console.log(p1 instanceof Person) // => true
console.log(p2 instanceof Person) // => true

总结:

  • 构造函数是根据具体的事物抽象出来的抽象模板

  • 实例对象是根据抽象的构造函数模板获得的具体实例对象

  • 每个实例对象都具备一个 constructor 属性,指向建立该实例的构造函数

    • 注意: constructor 是实例的属性的说法不严谨,具体后面的原型会讲到

  • 能够经过实例的 constructor 属性判断实例和构造函数之间的关系

    • 注意:这种方式不严谨,推荐使用 instanceof 操做符,后面学原型会解释为何

构造函数的问题

使用构造函数带来的最大的好处就是建立对象更方便了,可是其自己也存在一个浪费内存的问题:


function Person (name, age) {
 this.name = name
 this.age = age
 this.type = 'human'
 this.sayHello = function () {
   console.log('hello ' + this.name)
}
}

var p1 = new Person('lpz', 18)
var p2 = new Person('Jack', 16)

在该示例中,从表面上好像没什么问题,可是实际上这样作,有一个很大的弊端。那就是对于每个实例对象,typesayHello 都是如出一辙的内容,每一次生成一个实例,都必须为重复的内容,多占用一些内存,若是实例对象不少,会形成极大的内存浪费。


console.log(p1.sayHello === p2.sayHello) // => false

对于这种问题咱们能够把须要共享的函数定义到构造函数外部:


function sayHello = function () {
 console.log('hello ' + this.name)
}

function Person (name, age) {
 this.name = name
 this.age = age
 this.type = 'human'
 this.sayHello = sayHello
}

var p1 = new Person('lpz', 18)
var p2 = new Person('Jack', 16)

console.log(p1.sayHello === p2.sayHello) // => true

这样确实能够了,可是若是有多个须要共享的函数的话就会形成全局命名空间冲突的问题。

你确定想到了能够把多个函数放到一个对象中用来避免全局命名空间冲突的问题:


var fns = {
 sayHello: function () {
   console.log('hello ' + this.name)
},
 sayAge: function () {
   console.log(this.age)
}
}

function Person (name, age) {
 this.name = name
 this.age = age
 this.type = 'human'
 this.sayHello = fns.sayHello
 this.sayAge = fns.sayAge
}

var p1 = new Person('lpz', 18)
var p2 = new Person('Jack', 16)

console.log(p1.sayHello === p2.sayHello) // => true
console.log(p1.sayAge === p2.sayAge) // => true

至此,咱们利用本身的方式基本上解决了构造函数的内存浪费问题。可是代码看起来仍是那么的格格不入,那有没有更好的方式呢?

小结

  • 构造函数语法

  • 分析构造函数

  • 构造函数和实例对象的关系

    • 实例的 constructor 属性

    • instanceof 操做符

  • 构造函数的问题

原型

内容引导:

  • 使用 prototype 原型对象解决构造函数的问题

  • 分析 构造函数、prototype 原型对象、实例对象 三者之间的关系

  • 属性成员搜索原则:原型链

  • 实例对象读写原型对象中的成员

  • 原型对象的简写形式

  • 原生对象的原型

    • Object

    • Array

    • String

    • ...

  • 原型对象的问题

  • 构造的函数和原型对象使用建议

更好的解决方案: prototype

Javascript 规定,每个构造函数都有一个 prototype 属性,指向另外一个对象。这个对象的全部属性和方法,都会被构造函数的实例继承。

这也就意味着,咱们能够把全部对象实例须要共享的属性和方法直接定义在 prototype 对象上。


function Person (name, age) {
 this.name = name
 this.age = age
}

console.log(Person.prototype)

Person.prototype.type = 'human'

Person.prototype.sayName = function () {
 console.log(this.name)
}

var p1 = new Person(...)
var p2 = new Person(...)

console.log(p1.sayName === p2.sayName) // => true

这时全部实例的 type 属性和 sayName() 方法,其实都是同一个内存地址,指向 prototype 对象,所以就提升了运行效率。

构造函数、实例、原型三者之间的关系

 

任何函数都具备一个 prototype 属性,该属性是一个对象。


function F () {}
console.log(F.prototype) // => object

F.prototype.sayHi = function () {
 console.log('hi!')
}

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


console.log(F.constructor === F) // => true

经过构造函数获得的实例对象内部会包含一个指向构造函数的 prototype 对象的指针 __proto__


var instance = new F()
console.log(instance.__proto__ === F.prototype) // => true

<p class="tip"> __proto__ 是非标准属性。</p>

实例对象能够直接访问原型对象成员。


instance.sayHi() // => hi!

总结:

  • 任何函数都具备一个 prototype 属性,该属性是一个对象

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

  • 经过构造函数获得的实例对象内部会包含一个指向构造函数的 prototype 对象的指针 __proto__

  • 全部实例都直接或间接继承了原型对象的成员

属性成员的搜索原则:原型链

了解了 构造函数-实例-原型对象 三者之间的关系后,接下来咱们来解释一下为何实例对象能够访问原型对象中的成员。

每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具备给定名字的属性

  • 搜索首先从对象实例自己开始

  • 若是在实例中找到了具备给定名字的属性,则返回该属性的值

  • 若是没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具备给定名字的属性

  • 若是在原型对象中找到了这个属性,则返回该属性的值

也就是说,在咱们调用 person1.sayName() 的时候,会前后执行两次搜索:

  • 首先,解析器会问:“实例 person1 有 sayName 属性吗?”答:“没有。

  • ”而后,它继续搜索,再问:“ person1 的原型有 sayName 属性吗?”答:“有。

  • ”因而,它就读取那个保存在原型对象中的函数。

  • 当咱们调用 person2.sayName() 时,将会重现相同的搜索过程,获得相同的结果。

而这正是多个对象实例共享原型所保存的属性和方法的基本原理。

总结:

  • 先在本身身上找,找到即返回

  • 本身身上找不到,则沿着原型链向上查找,找到即返回

  • 若是一直到原型链的末端尚未找到,则返回 undefined

实例对象读写原型对象成员

读取:

  • 先在本身身上找,找到即返回

  • 本身身上找不到,则沿着原型链向上查找,找到即返回

  • 若是一直到原型链的末端尚未找到,则返回 undefined

值类型成员写入(实例对象.值类型成员 = xx):

  • 当实例指望重写原型对象中的某个普通数据成员时实际上会把该成员添加到本身身上

  • 也就是说该行为实际上会屏蔽掉对原型对象成员的访问

引用类型成员写入(实例对象.引用类型成员 = xx):

  • 同上

复杂类型修改(实例对象.成员.xx = xx):

  • 一样会先在本身身上找该成员,若是本身身上找到则直接修改

  • 若是本身身上找不到,则沿着原型链继续查找,若是找到则修改

  • 若是一直到原型链的末端尚未找到该成员,则报错(实例对象.undefined.xx = xx

更简单的原型语法

咱们注意到,前面例子中每添加一个属性和方法就要敲一遍 Person.prototype为减小没必要要的输入,更常见的作法是用一个包含全部属性和方法的对象字面量来重写整个原型对象:


function Person (name, age) {
 this.name = name
 this.age = age
}

Person.prototype = {
 type: 'human',
 sayHello: function () {
   console.log('我叫' + this.name + ',我今年' + this.age + '岁了')
}
}

在该示例中,咱们将 Person.prototype 重置到了一个新的对象。这样作的好处就是为 Person.prototype 添加成员简单了,可是也会带来一个问题,那就是原型对象丢失了 constructor 成员。

因此,咱们为了保持 constructor 的指向正确,建议的写法是:


function Person (name, age) {
 this.name = name
 this.age = age
}

Person.prototype = {
 constructor: Person, // => 手动将 constructor 指向正确的构造函数
 type: 'human',
 sayHello: function () {
   console.log('我叫' + this.name + ',我今年' + this.age + '岁了')
}
}

原生对象的原型

<p class="tip"> 全部函数都有 prototype 属性对象。</p>

  • Object.prototype

  • Function.prototype

  • Array.prototype

  • String.prototype

  • Number.prototype

  • Date.prototype

  • ...

练习:为数组对象和字符串对象扩展原型方法。

原型对象的问题

  • 共享数组

  • 共享对象

若是真的但愿能够被实例对象之间共享和修改这些共享数据那就不是问题。可是若是不但愿实例之间共享和修改这些共享数据则就是问题。

一个更好的建议是,最好不要让实例之间互相共享这些数组或者对象成员,一旦修改的话会致使数据的走向很不明确并且难以维护。

原型对象使用建议

  • 私有成员(通常就是非函数成员)放到构造函数中

  • 共享成员(通常就是函数)放到原型对象中

  • 若是重置了 prototype 记得修正 constructor 的指向

案例:随机方块


面向对象游戏案例:贪吃蛇

案例相关源码以上传到 GitHub :https://github.com/lipengzhou/new-snake

案例介绍

游戏演示

在线演示地址:贪吃蛇

案例目标

游戏的目的是用来体会js高级语法的使用 不须要具有抽象对象的能力,使用面向对象的方式分析问题,须要一个漫长的过程。

功能实现

搭建页面

放一个容器盛放游戏场景 div#map,设置样式


#map {
 width: 800px;
 height: 600px;
 background-color: #ccc;
 position: relative;
}

分析对象

  • 游戏对象

  • 蛇对象

  • 食物对象

建立食物对象

  • Food

    • 属性

      • x

      • y

      • width

      • height

      • color

    • 方法

      • render 随机建立一个食物对象,并输出到map上

  • 建立Food的构造函数,并设置属性


var position = 'absolute';
var elements = [];
function Food(x, y, width, height, color) {
 this.x = x || 0;
 this.y = y || 0;
 // 食物的宽度和高度(像素)
 this.width = width || 20;
 this.height = height || 20;
 // 食物的颜色
 this.color = color || 'green';
}
  • 经过原型设置render方法,实现随机产生食物对象,并渲染到map上


Food.prototype.render = function (map) {
 // 随机食物的位置,map.宽度/food.宽度,总共有多少分food的宽度,随机一下。而后再乘以food的宽度
 this.x = parseInt(Math.random() * map.offsetWidth / this.width) * this.width;
 this.y = parseInt(Math.random() * map.offsetHeight / this.height) * this.height;

 // 动态建立食物对应的div
 var div = document.createElement('div');
 map.appendChild(div);
 div.style.position = position;
 div.style.left = this.x + 'px';
 div.style.top = this.y + 'px';
 div.style.width = this.width + 'px';
 div.style.height = this.height + 'px';
 div.style.backgroundColor = this.color;
 elements.push(div);
}
  • 经过自调用函数,进行封装,经过window暴露Food对象


window.Food = Food;

建立蛇对象

  • Snake

  • 属性

    • width 蛇节的宽度 默认20

    • height 蛇节的高度 默认20

    • body 数组,蛇的头部和身体,第一个位置是蛇头

    • direction 蛇运动的方向 默认right 能够是 left top bottom

  • 方法

    • render 把蛇渲染到map上

  • Snake构造函数


var position = 'absolute';
var elements = [];
function Snake(width, height, direction) {
 // 设置每个蛇节的宽度
 this.width = width || 20;
 this.height = height || 20;
 // 蛇的每一部分, 第一部分是蛇头
 this.body = [
  {x: 3, y: 2, color: 'red'},
  {x: 2, y: 2, color: 'red'},
  {x: 1, y: 2, color: 'red'}
];
 this.direction = direction || 'right';
}
  • render方法


Snake.prototype.render = function(map) {
 for(var i = 0; i < this.body.length; i++) {
   var obj = this.body[i];
   var div = document.createElement('div');
   map.appendChild(div);
   div.style.left = obj.x * this.width + 'px';
   div.style.top = obj.y * this.height + 'px';
   div.style.position = position;
   div.style.backgroundColor = obj.color;
   div.style.width = this.width + 'px';
   div.style.height = this.height + 'px';
}
}
  • 在自调用函数中暴露Snake对象


window.Snake = Snake;

建立游戏对象

游戏对象,用来管理游戏中的全部对象和开始游戏

  • Game

    • 属性

      • food

      • snake

      • map

    • 方法

      • start 开始游戏(绘制全部游戏对象)

 

  • 构造函数


function Game(map) {
 this.food = new Food();
 this.snake = new Snake();
 this.map = map;
}
  • 开始游戏,渲染食物对象和蛇对象


Game.prototype.start = function () {
 this.food.render(this.map);
 this.snake.render(this.map);
}

游戏的逻辑

写蛇的move方法

  • 在蛇对象(snake.js)中,在Snake的原型上新增move方法

  1. 让蛇移动起来,把蛇身体的每一部分往前移动一下

  2. 蛇头部分根据不一样的方向决定 往哪里移动


Snake.prototype.move = function (food, map) {
 // 让蛇身体的每一部分往前移动一下
 var i = this.body.length - 1;
 for(; i > 0; i--) {
   this.body[i].x = this.body[i - 1].x;
   this.body[i].y = this.body[i - 1].y;
}
 // 根据移动的方向,决定蛇头如何处理
 switch(this.direction) {
   case 'left':
     this.body[0].x -= 1;
     break;
   case 'right':
     this.body[0].x += 1;
     break;
   case 'top':
     this.body[0].y -= 1;
     break;
   case 'bottom':
     this.body[0].y += 1;
     break;
}
}
  • 在game中测试


this.snake.move(this.food, this.map);
this.snake.render(this.map);

让蛇本身动起来

  • 私有方法

    什么是私有方法?
      不能被外部访问的方法
    如何建立私有方法?
      使用自调用函数包裹
    

  • 在game.js中 添加runSnake的私有方法,开启定时器调用蛇的move和render方法,让蛇动起来

  • 判断蛇是否撞墙


function runSnake() {
 var timerId = setInterval(function() {
   this.snake.move(this.food, this.map);
   // 在渲染前,删除以前的蛇
   this.snake.render(this.map);

   // 判断蛇是否撞墙
   var maxX = this.map.offsetWidth / this.snake.width;
   var maxY = this.map.offsetHeight / this.snake.height;
   var headX = this.snake.body[0].x;
   var headY = this.snake.body[0].y;
   if (headX < 0 || headX >= maxX) {
     clearInterval(timerId);
     alert('Game Over');
  }

   if (headY < 0 || headY >= maxY) {
     clearInterval(timerId);
     alert('Game Over');
  }

}.bind(that), 150);
}
  • 在snake中添加删除蛇的私有方法,在render中调用


function remove() {
 // 删除渲染的蛇
 var i = elements.length - 1;
 for(; i >= 0; i--) {
   // 删除页面上渲染的蛇
   elements[i].parentNode.removeChild(elements[i]);
   // 删除elements数组中的元素
   elements.splice(i, 1);
}
}
  • 在game中经过键盘控制蛇的移动方向


function bindKey() {
 document.addEventListener('keydown', function(e) {
   switch (e.keyCode) {
     case 37:
       // left
       this.snake.direction = 'left';
       break;
     case 38:
       // top
       this.snake.direction = 'top';
       break;
     case 39:
       // right
       this.snake.direction = 'right';
       break;
     case 40:
       // bottom
       this.snake.direction = 'bottom';
       break;
  }
}.bind(that), false);
}
  • 在start方法中调用


bindKey();

判断蛇是否吃到食物


// 在Snake的move方法中

// 在移动的过程当中判断蛇是否吃到食物
// 若是蛇头和食物的位置重合表明吃到食物
// 食物的坐标是像素,蛇的坐标是几个宽度,进行转换
var headX = this.body[0].x * this.width;
var headY = this.body[0].y * this.height;
if (headX === food.x && headY === food.y) {
 // 吃到食物,往蛇节的最后加一节
 var last = this.body[this.body.length - 1];
 this.body.push({
   x: last.x,
   y: last.y,
   color: last.color
})
 // 把如今的食物对象删除,并从新随机渲染一个食物对象
 food.render(map);
}

其它处理

把html中的js代码放到index.js中

避免html中出现js代码

自调用函数的参数


(function (window, undefined) {
 var document = window.document;

}(window, undefined))
  • 传入window对象

未来代码压缩的时候,能够吧 function (window) 压缩成 function (w)

  • 传入undefined

在未来会看到别人写的代码中会把undefined做为函数的参数(当前案例没有使用)由于在有的老版本的浏览器中 undefined能够被从新赋值,防止undefined 被从新赋值

整理代码

如今的代码结构清晰,谁出问题就找到对应的js文件便可。经过自调用函数,已经防止了变量命名污染的问题

可是,因为js文件数较多,须要在页面上引用,会产生文件依赖的问题(先引入那个js,再引入哪一个js)未来经过工具把js文件合并并压缩。如今手工合并js文件演示

  • 问题1


// 若是存在多个自调用函数要用分号分割,不然语法错误
// 下面代码会报错
(function () {
}())

(function () {
}())
// 因此代码规范中会建议在自调用函数以前加上分号
// 下面代码没有问题
;(function () {
}())

;(function () {
}())
  • 问题2


// 当自调用函数 前面有函数声明时,会把自调用函数做为参数
// 因此建议自调用函数前,加上;
var a = function () {
 alert('11');
}
   
(function () {
 alert('22');
}())

继承

什么是继承

  • 现实生活中的继承

  • 程序中的继承

构造函数的属性继承:借用构造函数


function Person (name, age) {
 this.type = 'human'
 this.name = name
 this.age = age
}

function Student (name, age) {
 // 借用构造函数继承属性成员
 Person.call(this, name, age)
}

var s1 = Student('张三', 18)
console.log(s1.type, s1.name, s1.age) // => human 张三 18

构造函数的原型方法继承:拷贝继承(for-in)


function Person (name, age) {
 this.type = 'human'
 this.name = name
 this.age = age
}

Person.prototype.sayName = function () {
 console.log('hello ' + this.name)
}

function Student (name, age) {
 Person.call(this, name, age)
}

// 原型对象拷贝继承原型对象成员
for(var key in Person.prototype) {
 Student.prototype[key] = Person.prototype[key]
}

var s1 = Student('张三', 18)

s1.sayName() // => hello 张三

另外一种继承方式:原型继承


function Person (name, age) {
 this.type = 'human'
 this.name = name
 this.age = age
}

Person.prototype.sayName = function () {
 console.log('hello ' + this.name)
}

function Student (name, age) {
 Person.call(this, name, age)
}

// 利用原型的特性实现继承
Student.prototype = new Person()

var s1 = Student('张三', 18)

console.log(s1.type) // => human

s1.sayName() // => hello 张三

函数进阶

函数的定义方式

  • 函数声明

  • 函数表达式

  • new Function

函数声明


function foo () {

}

函数表达式


var foo = function () {

}

函数声明与函数表达式的区别

  • 函数声明必须有名字

  • 函数声明会函数提高,在预解析阶段就已建立,声明先后均可以调用

  • 函数表达式相似于变量赋值

  • 函数表达式能够没有名字,例如匿名函数

  • 函数表达式没有变量提高,在执行阶段建立,必须在表达式执行以后才能够调用

下面是一个根据条件定义函数的例子:


if (true) {
 function f () {
   console.log(1)
}
} else {
 function f () {
   console.log(2)
}
}

以上代码执行结果在不一样浏览器中结果不一致。

不过咱们可使用函数表达式解决上面的问题:


var f

if (true) {
 f = function () {
   console.log(1)
}
} else {
 f = function () {
   console.log(2)
}
}

函数的调用方式

  • 普通函数

  • 构造函数

  • 对象方法

函数内 this 指向的不一样场景

函数的调用方式决定了 this 指向的不一样:

调用方式 非严格模式 备注
普通函数调用 window 严格模式下是 undefined
构造函数调用 实例对象 原型方法中 this 也是实例对象
对象方法调用 该方法所属对象 紧挨着的对象
事件绑定方法 绑定事件对象  
定时器函数 window  

这就是对函数内部 this 指向的基本整理,写代码写多了天然而然就熟悉了。

函数也是对象

  • 全部函数都是 Function 的实例

call、apply、bind

那了解了函数 this 指向的不一样场景以后,咱们知道有些状况下咱们为了使用某种特定环境的 this 引用,这时候时候咱们就须要采用一些特殊手段来处理了,例如咱们常常在定时器外部备份 this 引用,而后在定时器函数内部使用外部 this 的引用。然而实际上对于这种作法咱们的 JavaScript 为咱们专门提供了一些函数方法用来帮咱们更优雅的处理函数内部 this 指向问题。这就是接下来咱们要学习的 call、apply、bind 三个函数方法。

call

call() 方法调用一个函数, 其具备一个指定的 this 值和分别地提供的参数(参数的列表)。

<p class="danger"> 注意:该方法的做用和 apply() 方法相似,只有一个区别,就是 call() 方法接受的是若干个参数的列表,而 apply() 方法接受的是一个包含多个参数的数组。</p>

语法:


fun.call(thisArg[, arg1[, arg2[, ...]]])

参数:

  • thisArg

    • 在 fun 函数运行时指定的 this 值

    • 若是指定了 null 或者 undefined 则内部 this 指向 window

  • arg1, arg2, ...

    • 指定的参数列表

apply

apply() 方法调用一个函数, 其具备一个指定的 this 值,以及做为一个数组(或相似数组的对象)提供的参数。

<p class="danger"> 注意:该方法的做用和 call() 方法相似,只有一个区别,就是 call() 方法接受的是若干个参数的列表,而 apply() 方法接受的是一个包含多个参数的数组。</p>

语法:


fun.apply(thisArg, [argsArray])

参数:

  • thisArg

  • argsArray

apply()call() 很是类似,不一样之处在于提供参数的方式。apply() 使用参数数组而不是一组参数列表。例如:


fun.apply(this, ['eat', 'bananas'])

bind

bind() 函数会建立一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具备相同的函数体(在 ECMAScript 5 规范中内置的call属性)。当目标函数被调用时 this 值绑定到 bind() 的第一个参数,该参数不能被重写。绑定函数被调用时,bind() 也接受预设的参数提供给原函数。一个绑定函数也能使用new操做符建立对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。

语法:


fun.bind(thisArg[, arg1[, arg2[, ...]]])

参数:

  • thisArg

    • 当绑定函数被调用时,该参数会做为原函数运行时的 this 指向。当使用new 操做符调用绑定函数时,该参数无效。

  • arg1, arg2, ...

    • 当绑定函数被调用时,这些参数将置于实参以前传递给被绑定的方法。

返回值:

返回由指定的this值和初始化参数改造的原函数拷贝。

示例1:


this.x = 9;
var module = {
 x: 81,
 getX: function() { return this.x; }
};

module.getX(); // 返回 81

var retrieveX = module.getX;
retrieveX(); // 返回 9, 在这种状况下,"this"指向全局做用域

// 建立一个新函数,将"this"绑定到module对象
// 新手可能会被全局的x变量和module里的属性x所迷惑
var boundGetX = retrieveX.bind(module);
boundGetX(); // 返回 81

示例2:


function LateBloomer() {
 this.petalCount = Math.ceil(Math.random() * 12) + 1;
}

// Declare bloom after a delay of 1 second
LateBloomer.prototype.bloom = function() {
 window.setTimeout(this.declare.bind(this), 1000);
};

LateBloomer.prototype.declare = function() {
 console.log('I am a beautiful flower with ' +
   this.petalCount + ' petals!');
};

var flower = new LateBloomer();
flower.bloom();  // 一秒钟后, 调用'declare'方法

小结

  • call 和 apply 特性同样

    • 都是用来调用函数,并且是当即调用

    • 可是能够在调用函数的同时,经过第一个参数指定函数内部 this 的指向

    • call 调用的时候,参数必须以参数列表的形式进行传递,也就是以逗号分隔的方式依次传递便可

    • apply 调用的时候,参数必须是一个数组,而后在执行的时候,会将数组内部的元素一个一个拿出来,与形参一一对应进行传递

    • 若是第一个参数指定了 null 或者 undefined 则内部 this 指向 window

  • bind

    • 能够用来指定内部 this 的指向,而后生成一个改变了 this 指向的新的函数

    • 它和 call、apply 最大的区别是:bind 不会调用

    • bind 支持传递参数,它的传参方式比较特殊,一共有两个位置能够传递

        1. 在 bind 的同时,以参数列表的形式进行传递

        1. 在调用的时候,以参数列表的形式进行传递

      • 那到底以谁 bind 的时候传递的参数为准呢仍是以调用的时候传递的参数为准

      • 二者合并:bind 的时候传递的参数和调用的时候传递的参数会合并到一块儿,传递到函数内部

函数的其它成员

  • arguments

    • 实参集合

  • caller

    • 函数的调用者

  • length

    • 形参的个数

  • name

    • 函数的名称


function fn(x, y, z) {
 console.log(fn.length) // => 形参的个数
 console.log(arguments) // 伪数组实参参数集合
 console.log(arguments.callee === fn) // 函数自己
 console.log(fn.caller) // 函数的调用者
 console.log(fn.name) // => 函数的名字
}

function f() {
 fn(10, 20, 30)
}

f()

高阶函数

  • 函数能够做为参数

  • 函数能够做为返回值

做为参数


function eat (callback) {
 setTimeout(function () {
   console.log('吃完了')
   callback()
}, 1000)
}

eat(function () {
 console.log('去唱歌')
})

做为返回值


function genFun (type) {
 return function (obj) {
   return Object.prototype.toString.call(obj) === type
}
}

var isArray = genFun('[object Array]')
var isObject = genFun('[object Object]')

console.log(isArray([])) // => true
console.log(isArray({})) // => true

函数闭包


function fn () {
 var count = 0
 return {
   getCount: function () {
     console.log(count)
  },
   setCount: function () {
     count++
  }
}
}

var fns = fn()

fns.getCount() // => 0
fns.setCount()
fns.getCount() // => 1

做用域、做用域链、预解析

  • 全局做用域

  • 函数做用域

  • 没有块级做用域


{
 var foo = 'bar'
}

console.log(foo)

if (true) {
 var a = 123
}
console.log(a)

做用域链示例代码:


var a = 10

function fn () {
 var b = 20

 function fn1 () {
   var c = 30
   console.log(a + b + c)
}

 function fn2 () {
   var d = 40
   console.log(c + d)
}

 fn1()
 fn2()
}
  • 内层做用域能够访问外层做用域,反之不行

什么是闭包

闭包就是可以读取其余函数内部变量的函数,因为在 Javascript 语言中,只有函数内部的子函数才能读取局部变量,所以能够把闭包简单理解成 “定义在一个函数内部的函数”。因此,在本质上,闭包就是将函数内部和函数外部链接起来的一座桥梁。

闭包的用途:

  • 能够在函数外部读取函数内部成员

  • 让函数内成员始终存活在内存中

一些关于闭包的例子

示例1:


var arr = [10, 20, 30]
for(var i = 0; i < arr.length; i++) {
 arr[i] = function () {
   console.log(i)
}
}

示例2:


console.log(111)

for(var i = 0; i < 3; i++) {
 setTimeout(function () {
   console.log(i)
}, 0)
}
console.log(222)

示例3:投票

示例4:判断类型

示例5:沙箱模式

闭包的思考题

思考题 1:


var name = "The Window";
var object = {
 name: "My Object",
 getNameFunc: function () {
   return function () {
     return this.name;
  };
}
};

console.log(object.getNameFunc()())

思考题 2:


var name = "The Window";  
var object = {    
 name: "My Object",
 getNameFunc: function () {
   var that = this;
   return function () {
     return that.name;
  };
}
};
console.log(object.getNameFunc()())

小结

函数递归

递归执行模型


function fn1 () {
 console.log(111)
 fn2()
 console.log('fn1')
}

function fn2 () {
 console.log(222)
 fn3()
 console.log('fn2')
}

function fn3 () {
 console.log(333)
 fn4()
 console.log('fn3')
}

function fn4 () {
 console.log(444)
 console.log('fn4')
}

fn1()

举个栗子:计算阶乘的递归函数


function factorial (num) {
 if (num <= 1) {
   return 1
} else {
   return num * factorial(num - 1)
}
}

递归应用场景

  • 深拷贝

  • 菜单树

  • 遍历 DOM 树


正则表达式

  • 了解正则表达式基本语法

  • 可以使用JavaScript的正则对象

正则表达式简介

什么是正则表达式

正则表达式:用于匹配规律规则的表达式,正则表达式最初是科学家对人类神经系统的工做原理的早期研究,如今在编程语言中有普遍的应用。正则表一般被用来检索、替换那些符合某个模式(规则)的文本。正则表达式是对字符串操做的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。

正则表达式的做用

  1. 给定的字符串是否符合正则表达式的过滤逻辑(匹配)

  2. 能够经过正则表达式,从字符串中获取咱们想要的特定部分(提取)

  3. 强大的字符串替换能力(替换)

正则表达式的特色

  1. 灵活性、逻辑性和功能性很是的强

  2. 能够迅速地用极简单的方式达到字符串的复杂控制

  3. 对于刚接触的人来讲,比较晦涩难懂

正则表达式的测试

  • 在线测试正则

  • 工具中使用正则表达式

    • sublime/vscode/word

    • 演示替换全部的数字

正则表达式的组成

  • 普通字符

  • 特殊字符(元字符):正则表达式中有特殊意义的字符

示例演示:

  • \d 匹配数字

  • ab\d 匹配 ab一、ab2

元字符串

经过测试工具演示下面元字符的使用

经常使用元字符串

元字符 说明
\d 匹配数字
\D 匹配任意非数字的字符
\w 匹配字母或数字或下划线
\W 匹配任意不是字母,数字,下划线
\s 匹配任意的空白符
\S 匹配任意不是空白符的字符
. 匹配除换行符之外的任意单个字符
^ 表示匹配行首的文本(以谁开始)
$ 表示匹配行尾的文本(以谁结束)

限定符

限定符 说明
* 重复零次或更屡次
+ 重复一次或更屡次
? 重复零次或一次
{n} 重复n次
{n,} 重复n次或更屡次
{n,m} 重复n到m次

其它

[] 字符串用中括号括起来,表示匹配其中的任一字符,至关于或的意思
[^]  匹配除中括号之内的内容
\ 转义符
| 或者,选择二者中的一个。注意|将左右两边分为两部分,而无论左右两边有多长多乱
() 从两个直接量中选择一个,分组
   eg:gr(a|e)y匹配gray和grey
[\u4e00-\u9fa5]  匹配汉字


案例

验证手机号:


^\d{11}$

验证邮编:


^\d{6}$

验证日期 2012-5-01


^\d{4}-\d{1,2}-\d{1,2}$

验证邮箱 xxx@itcast.cn


^\w+@\w+\.\w+$

验证IP地址 192.168.1.10


^\d{1,3}\(.\d{1,3}){3}$

JavaScript 中使用正则表达式

建立正则对象

方式1:


var reg = new Regex('\d', 'i');
var reg = new Regex('\d', 'gi');

方式2:


var reg = /\d/i;
var reg = /\d/gi;

参数

标志 说明
i 忽略大小写
g 全局匹配
gi 全局匹配+忽略大小写

正则匹配


// 匹配日期
var dateStr = '2015-10-10';
var reg = /^\d{4}-\d{1,2}-\d{1,2}$/
console.log(reg.test(dateStr));

匹配正则表达式

// console.log(/./.test("除了回车换行觉得的任意字符"));//true// console.log(/.*/.test("0个到多个"));//true// console.log(/.+/.test("1个到多个"));//true// console.log(/.?/.test("哈哈"));//true// console.log(/[0-9]/.test("9527"));//true// console.log(/[a-z]/.test("what"));//true// console.log(/[A-Z]/.test("Are"));//true// console.log(/[a-zA-Z]/.test("干啥子"));//false// console.log(/[0-9a-zA-Z]/.test("9ebg"));//true// console.log(/b|(ara)/.test("abra"));//true// console.log(/[a-z]{2,3}/.test("arfsf"));//true

    console.log(/\d/.test("998"));//true
    console.log(/\d*/.test("998"));//true
    console.log(/\d+/.test("998"));//true
    console.log(/\d{0,}/.test("998"));//true
    console.log(/\d{2,3}/.test("998"));//true
    console.log(/\D/.test("eat"));//true
    console.log(/\s/.test("  "));//true
    console.log(/\S/.test("嘎嘎"));//true
    console.log(/\w/.test("_"));//true
    console.log(/\W/.test("_"));//true


正则表达式案例

1.验证密码强弱2.验证邮箱:[0-9a-zA-Z.-]+[@][0-9a-zA-Z.-]+(.+){1,2}3.验证中文名字[\u4e00-\u9fa5]

正则提取


// 1. 提取工资
var str = "张三:1000,李四:5000,王五:8000。";
var array = str.match(/\d+/g);
console.log(array);

// 2. 提取email地址
var str = "123123@xx.com,fangfang@valuedopinions.cn 286669312@qq.com 二、emailenglish@emailenglish.englishtown.com 286669312@qq.com...";
var array = str.match(/\w+@\w+\.\w+(\.\w+)?/g);
console.log(array);

// 3. 分组提取  
// 3. 提取日期中的年部分 2015-5-10
var dateStr = '2016-1-5';
// 正则表达式中的()做为分组来使用,获取分组匹配到的结果用Regex.$1 $2 $3....来获取
var reg = /(\d{4})-\d{1,2}-\d{1,2}/;
if (reg.test(dateStr)) {
 console.log(RegExp.$1);
}

// 4. 提取邮件中的每一部分
var reg = /(\w+)@(\w+)\.(\w+)(\.\w+)?/;
var str = "123123@xx.com";
if (reg.test(str)) {
 console.log(RegExp.$1);
 console.log(RegExp.$2);
 console.log(RegExp.$3);
}

正则替换


// 1. 替换全部空白
var str = "   123AD asadf   asadfasf adf ";
str = str.replace(/\s/g,"xx");
console.log(str);

// 2. 替换全部,|,
var str = "abc,efg,123,abc,123,a";
str = str.replace(/,|,/g, ".");
console.log(str);

案例:表单验证


QQ号:<input type="text" id="txtQQ"><span></span><br>
邮箱:<input type="text" id="txtEMail"><span></span><br>
手机:<input type="text" id="txtPhone"><span></span><br>
生日:<input type="text" id="txtBirthday"><span></span><br>
姓名:<input type="text" id="txtName"><span></span><br>

//获取文本框
var txtQQ = document.getElementById("txtQQ");
var txtEMail = document.getElementById("txtEMail");
var txtPhone = document.getElementById("txtPhone");
var txtBirthday = document.getElementById("txtBirthday");
var txtName = document.getElementById("txtName");

//
txtQQ.onblur = function () {
 //获取当前文本框对应的span
 var span = this.nextElementSibling;
 var reg = /^\d{5,12}$/;
 //判断验证是否成功
 if(!reg.test(this.value) ){
   //验证不成功
   span.innerText = "请输入正确的QQ号";
   span.style.color = "red";
}else{
   //验证成功
   span.innerText = "";
   span.style.color = "";
}
};

//txtEMail
txtEMail.onblur = function () {
 //获取当前文本框对应的span
 var span = this.nextElementSibling;
 var reg = /^\w+@\w+\.\w+(\.\w+)?$/;
 //判断验证是否成功
 if(!reg.test(this.value) ){
   //验证不成功
   span.innerText = "请输入正确的EMail地址";
   span.style.color = "red";
}else{
   //验证成功
   span.innerText = "";
   span.style.color = "";
}
};

表单验证部分,封装成函数:


var regBirthday = /^\d{4}-\d{1,2}-\d{1,2}$/;
addCheck(txtBirthday, regBirthday, "请输入正确的出生日期");
//给文本框添加验证
function addCheck(element, reg, tip) {
 element.onblur = function () {
   //获取当前文本框对应的span
   var span = this.nextElementSibling;
   //判断验证是否成功
   if(!reg.test(this.value) ){
     //验证不成功
     span.innerText = tip;
     span.style.color = "red";
  }else{
     //验证成功
     span.innerText = "";
     span.style.color = "";
  }
};
}

经过给元素增长自定义验证属性对表单进行验证:


<form id="frm">
QQ号:<input type="text" name="txtQQ" data-rule="qq"><span></span><br>
邮箱:<input type="text" name="txtEMail" data-rule="email"><span></span><br>
手机:<input type="text" name="txtPhone" data-rule="phone"><span></span><br>
生日:<input type="text" name="txtBirthday" data-rule="date"><span></span><br>
姓名:<input type="text" name="txtName" data-rule="cn"><span></span><br>
</form>

// 全部的验证规则
var rules = [
{
   name: 'qq',
   reg: /^\d{5,12}$/,
   tip: "请输入正确的QQ"
},
{
   name: 'email',
   reg: /^\w+@\w+\.\w+(\.\w+)?$/,
   tip: "请输入正确的邮箱地址"
},
{
   name: 'phone',
   reg: /^\d{11}$/,
   tip: "请输入正确的手机号码"
},
{
   name: 'date',
   reg: /^\d{4}-\d{1,2}-\d{1,2}$/,
   tip: "请输入正确的出生日期"
},
{
   name: 'cn',
   reg: /^[\u4e00-\u9fa5]{2,4}$/,
   tip: "请输入正确的姓名"
}];

addCheck('frm');


//给文本框添加验证
function addCheck(formId) {
 var i = 0,
     len = 0,
     frm =document.getElementById(formId);
 len = frm.children.length;
 for (; i < len; i++) {
   var element = frm.children[i];
   // 表单元素中有name属性的元素添加验证
   if (element.name) {
     element.onblur = function () {
       // 使用dataset获取data-自定义属性的值
       var ruleName = this.dataset.rule;
       var rule =getRuleByRuleName(rules, ruleName);

       var span = this.nextElementSibling;
       //判断验证是否成功
       if(!rule.reg.test(this.value) ){
         //验证不成功
         span.innerText = rule.tip;
         span.style.color = "red";
      }else{
         //验证成功
         span.innerText = "";
         span.style.color = "";
      }
    }
  }
}
}

// 根据规则的名称获取规则对象
function getRuleByRuleName(rules, ruleName) {
 var i = 0,
     len = rules.length;
 var rule = null;
 for (; i < len; i++) {
   if (rules[i].name == ruleName) {
     rule = rules[i];
     break;
  }
}
 return rule;
}

补充

伪数组和数组

在JavaScript中,除了5种原始数据类型以外,其余全部的都是对象,包括函数(Function)。

对象与数组的关系

在说区别以前,须要先提到另一个知识,就是 JavaScript 的原型继承。全部 JavaScript 的内置构造函数都是继承自 Object.prototype在这个前提下,能够理解为使用 new Array()[] 建立出来的数组对象,都会拥有 Object.prototype 的属性值。


var obj = {};// 拥有 Object.prototype 的属性值
var arr = [];
//使用数组直接量建立的数组,因为 Array.prototype 的属性继承自 Object.prototype,
//那么,它将同时拥有 Array.prototype 和 Object.prototype 的属性值

能够获得对象和数组的第一个区别:对象没有数组 Array.prototype 的属性值。

什么是数组

数组具备一个最基本特征:索引,这是对象所没有的,下面来看一段代码:


var obj = {};
var arr = [];

obj[2] = 'a';
arr[2] = 'a';

console.log(obj[2]); // => a
console.log(arr[2]); // => a
console.log(obj.length); // => undefined
console.log(arr.length); // => 3
  • obj[2]输出'a',是由于对象就是普通的键值对存取数据

  • 而arr[2]输出'a' 则不一样,数组是经过索引来存取数据,arr[2]之因此输出'a',是由于数组arr索引2的位置已经存储了数据

  • obj.length并不具备数组的特性,而且obj没有保存属性length,那么天然就会输出undefined

  • 而对于数组来讲,length是数组的一个内置属性,数组会根据索引长度来更改length的值

  • 为何arr.length输出3,而不是1

    • 在给数组添加元素时,并无按照连续的索引添加,因此致使数组的索引不连续,那么就致使索引长度大于元素个数

什么是伪数组

  1. 拥有 length 属性,其它属性(索引)为非负整数(对象中的索引会被当作字符串来处理,这里你能够当作是个非负整数串来理解)

  2. 不具备数组所具备的方法

伪数组,就是像数组同样有 length 属性,也有 0、一、二、3 等属性的对象,看起来就像数组同样,但不是数组,好比:


var fakeArray = {
 "0": "first",
 "1": "second",
 "2": "third",
 length: 3
};

for (var i = 0; i < fakeArray.length; i++) {
 console.log(fakeArray[i]);
}

Array.prototype.join.call(fakeArray,'+');

常见的伪数组有:

  • 函数内部的 arguments

  • DOM 对象列表(好比经过 document.getElementsByTags 获得的列表)

  • jQuery 对象(好比 $("div")

伪数组是一个 Object,而真实的数组是一个 Array。

伪数组存在的意义,是可让普通的对象也能正常使用数组的不少方法,好比:


var arr = Array.prototype.slice.call(arguments);

Array.prototype.forEach.call(arguments, function(v) {
 // 循环arguments对象
});

// push
// some
// every
// filter
// map
// ...

以上在借用数组的原型方法的时候均可以经过数组直接量来简化使用:


var obj = {
 0: 'a',
 1: 'b',
 2: 'c',
 length: 3
}

;[].push.call(obj, 'd')

console.log([].slice.call(obj))

;[].forEach.call(obj, function (num, index) {
 console.log(num)
})

小结

  • 对象没有数组 Array.prototype 的属性值,类型是 Object ,而数组类型是 Array

  • 数组是基于索引的实现, length 会自动更新,而对象是键值对

  • 使用对象能够建立伪数组,伪数组能够正常使用数组的大部分方法

JavaScript 垃圾回收机制

JavaScript 运行机制:Event Loop

Object

静态成员

  • Object.assign()

  • Object.create()

  • Object.keys()

  • Object.defineProperty()

实例成员

  • constructor

  • hasOwnProperty()

  • isPrototypeOf

  • propertyIsEnumerable()

  • toString()

  • valueOf()


附录

A 代码规范

代码风格

校验工具

B Chrome 开发者工具

C 文档相关工具

2019-08-0318:11:32

做者:何秀好

相关文章
相关标签/搜索