目录
JavaScript 标准参考教程 - JavaScript 语言的历史
0. 复习
基本类型(值类型)
- Undefined
- Null
- Boolean
- Number
- String
JS中String是值类型,和C#、Java不同!
复杂类型(引用类型)
- Object
- Array
- Date
- RegExp
- Function
- 基本包装类型
- Boolean
- Number
- String
- 单体内置对象
- Global
- Math
类型检测
typeof
instanceof
Object.prototype.toString.call()
JavaScript 执行过程
JavaScript 运行分为两个阶段:
- 预解析
- 全局预解析(所有变量和函数声明都会提前;同名的函数和变量函数的优先级高)
- 函数内部预解析(所有的变量、函数和形参都会参与预解析)
- 函数
- 形参
- 普通变量
- 执行
先预解析全局作用域,然后执行全局作用域中的代码,
在执行全局代码的过程中遇到函数调用就会先进行函数预解析,然后再执行函数内代码。
1. 对象
- JS是一个基于对象的语言
- ECMAScript-262 把对象定义为:无序属性的集合,其属性可以包含基本值、对象或者函数。
- 每个对象都是基于一个引用类型创建的,这些类型可以是系统内置的原生类型,也可以是开发人员自定义的类型。
- JS中的面向对象特性继承是通过原型来实现的。
1.1 创建对象的方法
- 字面量
- 使用系统的构造函数
new Object()
- 工厂方法
- 自定义构造方法
// 构造函数创建对象
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
1.2 构造函数创建对象的过程
- 创建一个新对象
- 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象)
- 执行构造函数中的代码
- 返回新对象
// 伪代码
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
2. ★原型prototype
Javascript 规定,每一个构造函数都有一个 prototype
属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。
- 原型的作用:
1. 共享数据,节省内存空间
2. 实现继承
2.1 构造函数和实例对象之间的关系——constructor
function Person(name, age){
this.name = name;
this.age = age;
}
var p1 =new Person("Gedy", 18);
console.dir(p1);
console.dir(Person);
console.log(p1.__proto__.constructor === Person); // true
console.log(p1.constructor === Person); // true
console.log(p1.constructor === Person.prototype.constructor); // true
console.log(Person.prototype.constructor === Person); // true
关系
- 对象的
__proto__.constructor
属性,指向的就是构造函数本身 - 构造函数的
prototype.constructor
属性,指向的也是自己本身
但是判断对象属于什么类型,还是推荐使用
instanceof
2.2 构造函数方法属性的问题——匿名方法内存浪费
function Person(name, age){
this.name = name;
this.age = age;
this.eat = function(){
console.log("哈哈哈");
}
}
上面的构造函数中,每创建一个对象,就会创建一个匿名方法,大量的对象会导致内存浪费。
2.3 ★构造方法、实例对象、原型对象之间的关系
- 构造函数可以实例化对象
- 构造函数中有一个属性叫prototype,是构造函数的原型对象
- 构造函数的原型对象(prototype)中有一个constructor构造器,这个构造器指向的就是自己所在的原型对象所在的构造函数
- 实例对象的原型对象(
__proto__
)指向的是该构造函数的原型对象 - 构造函数的原型对象(
prototype
)中的方法是可以被实例对象直接访问的
2.4 ★使用prototype
添加实例共享方法/属性
-
实例对象中有个属性,
__proto__
,也是对象,叫原型,不是标准的属性,浏览器使用的 -
构造函数中有一个属性,prototype,也是对象,叫原型,是标准属性,程序员使用
- 原型:
__proto__
或者是prototype
,都是原型对象,
- 原型:
案例1:
function Person(name,age) {
this.name=name;
this.age=age;
}
//通过原型来添加方法,解决数据共享,节省内存空间
Person.prototype.eat=function () {
console.log("吃凉菜");
};
var p1=new Person("小明",20);
var p2=new Person("小红",30);
console.log(p1.eat==p2.eat);//true
案例2:
function ChangeStyle(btnObj, dvObj, json) {
this.btnObj = btnObj;
this.dvObj = dvObj;
this.json = json;
}
ChangeStyle.prototype.init = function () {
//点击按钮,改变div多个样式属性值
var that = this;
this.btnObj.onclick = function () {
//按钮
for (var key in that.json) {
that.dvObj.style[key] = that.json[key];
}
};
};
简单的原型使用语法
与其一个一个设置属性,不如将原型对象设置成一整个对象,但是要注意的是,一定要手动加上constructor
属性,否则这个属性就丢失了。
Student.prototype = {
//手动修改构造器的指向
constructor:Student, // 这个一定要手动加上!!
height: "188",
weight: "55kg",
study: function () {
console.log("学习好开心啊");
},
eat: function () {
console.log("我要吃好吃的");
}
};
2.5 ★属性成员的搜索原则:原型链
每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性
- 搜索首先从对象实例本身开始
- 如果在实例中找到了具有给定名字的属性,则返回该属性的值
- 如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性
- 如果在原型对象中找到了这个属性,则返回该属性的值
搜索顺序: 实例对象 --> 原型
案例:
function Person(age,sex) {
this.age=age;//年龄
this.sex=sex;
this.eat=function () {
console.log("构造函数中的吃");
};
}
Person.prototype.sex="女";
Person.prototype.eat=function () {
console.log("原型对象中的吃");
};
var per=new Person(20,"男");
console.log(per.sex);//男,因为先搜索实例对象
per.eat(); // 构造函数中的吃, 因为先搜索实例对象
2.6 原型中的函数互相调用——this.方法名
- 在给原型注册方法的时候,方法中的
this
指代的是实例对象 - 原型中的一个方法访问另一个方法,也是使用
this.方法名
,this
代表实例对象。
function Animal(name,age) {
this.name=name;
this.age=age;
}
//原型中添加方法
Animal.prototype.eat=function () {
console.log("动物吃东西");
this.play();
};
Animal.prototype.play=function () {
console.log("玩球");
this.sleep();
};
Animal.prototype.sleep=function () {
console.log("睡觉了");
};
var dog=new Animal("小苏",20);
dog.eat();
2.7 为内置对象添加方法
直接给Array, String, Date
的prototype
添加方法就行了。
String.prototype.myReverse=function () {
for(var i=this.length-1;i>=0;i--){
console.log(this[i]);
}
};
var str="abcdefg";
str.myReverse();
//为Array内置对象的原型对象中添加方法
Array.prototype.mySort=function () {
for(var i=0;i<this.length-1;i++){
for(var j=0;j<this.length-1-i;j++){
if(this[j]<this[j+1]){
var temp=this[j];
this[j]=this[j+1];
this[j+1]=temp;
}//end if
}// end for
}//end for
};
2.8 ★把局部变量变为全局变量——作为window的属性
(function (win) {
var num=10;//局部变量
// //js是一门动态类型的语言,对象没有属性,点了就有了
win.num=num;
})(window);
console.log(num);
2.9 封装私有函数—— 参考沙箱
3. 贪吃蛇案例
-
没有做的功能
- 何时游戏胜利
- 撞身体会输
- 食物不会出现在蛇身体上
-
注意点:
- 蛇的运动方法一次只执行一步,定时器的运动交给
Game
类来做,因为他要在每次运动之后判断游戏是否结束 - 按键事件是给
document
来注册的 - 在定时器以及 给
document
注册按键时,都需要用bind
来改变函数的this
指向 - 蛇的身体边长一个,只需要增加一个位置,并且设置坐标和前一个一模一样即可,在下次运动的时,它的坐标就会保持不变,这样就能拉开距离。
- 蛇的运动方法一次只执行一步,定时器的运动交给
- 地图的方法:
init
: 设置地图的尺寸和格子数,绘制地图
- 食物的方法:
init
:清除地图上原有的食物,然后随机生成坐标,创建一个食物元素,绘制在地图上clear
:在地图上消除食物
- 蛇的方法:
init
:先清除地图上的蛇,然后根据蛇身体的坐标和颜色,绘制在地图上run
:根据蛇的 方向,计算下一步的坐标grow
:让蛇边长一个身体clear
:在地图上清除蛇
- 游戏对象:
start
:初始化所有对象,调用其他方法开始游戏runGame
:开启定时器来玩蛇,判断是否游戏结束,以及是否吃到食物bindkey
:注册按键事件来改变蛇的方向endGame
:停止游戏定时器,弹出提示
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
</style>
</head>
<body>
<script src="jquery-1.12.4.js"></script>
<script>
$(function () {
// 公共方法
(function (window) {
Util = {
getRandomValue: function (min, max) {
return parseInt(Math.random() * (max - min) + min)
},
};
window.Util = Util;
}(window));
// 地图类
(function (window) {
function Map(widthPixel, heightPixel, widthGrid, heightGrid) {
this.widthPixel = widthPixel || 400;
this.heightPixel = heightPixel || 400;
// 传递一共横竖几个格子
this.widthGrid = widthGrid || 20;
this.heightGrid = heightGrid || 20;
// 地图元素
this.element = null;
// 地图内单个小块的尺寸
this.gridWidthSize = null;
this.gridHeightSize = null;
}
// 初始化方法
Map.prototype.init = function () {
// 创建div
var element = document.createElement("div");
// 设置样式和尺寸
element.className = "map";
element.style.width = this.widthPixel + "px";
element.style.height = this.heightPixel + "px";
element.style.backgroundColor = "grey";
element.style.position = "relative";
// 加到body中
document.body.appendChild(element);
// 计算单个小方块的尺寸
this.gridWidthSize = this<