推荐文档https://www.tslang.cn/docs/home.html
一台电脑的组装是有不同的零部件组成的。再组成一个新的主体电脑。
回到我们写代码,我们之前的写法是一行一行写得代码,一点点去实现。
现在呢,面向对象的写法就是我们先去实现一点点的零部件,然后实现一个总的功能,乃至一个大整体。
游戏引擎: 游戏引擎是指包含游戏必备的一些功能框架以及工具的集合,是决定一款游戏质量高低的核心因素。这里就不列举市面上的一些引擎了。
有了游戏引擎之后,游戏实现变得简单,只需要调用API就可以很快实现,很多功能已经做成了模块等待你去使用。
2008年2月,Python版Cocos2D诞生
2008年6月,Objective-C版Cocos2D for iphone诞生,将Cocos推上高峰。
之后出现了各种语言的cocos版本,如:
Cocos2D-x,Cocos2D-js,Cocos2d-android,Cocos2d-net等等。
在这些版本中,最有历史意义的就是Cocos2d-x,可以轻易做出跨平台版本。之后,从Cocos2D-x诞生出两个分支,一个是给wp系统用的Cocos2D-xna,还一个是Cocos2D-HTML5。
CocosCreator的诞生是为了将Cocos2D-x的纯代码编辑,分解成可视化,脚本化等特点,让更多新人轻松入手。
搜索Ts在线编辑器:[TypeScript][https://www.typescriptlang.org/play/],前期我们的学习都是在这里验证。这个网页可以轻松转换Ts和Js。
Ts和Js都可以支持,并且Ts是Js的超集,Ts包含了Js的所有功能,同时Ts包含了新特性。
Ts新特性:
Js数据类型是弱类型(即可以随意赋值),Ts数据类型添加了强类型(更明确的类型)。
Ts具有面向对象性。
// 在控制台上输出HelloWorld!
document.write("HelloWorld!");
用let 或者var 关键字来声明变量
// 我们想要修改输出内容,就要使用变量
// 变量相当于我们开辟了一块内存去存储内容
// 在我们调用的时候就是取得这个地址存储的值
let personName="我是一个善变的变量";
document.write(personName);
标识符命名规则:
标识符是使用字母,数字,下划线组成的。
标识符首位只能使用字母或者下划线。
我们在声明变量的时候,最好使用[驼峰命名法(Camel-Case)][https://https://baike.baidu.com/item/骆驼命名法 ]。
使用const常量关键字来声明常量,常量的内容只能在声明的时候赋值。
const tmp="我是一个不会变的常量"
限定类型:
在声明的时候,变量名后添加:加数据类型,可以限制这个变量的类型。
var personName:string="我被限制成了字符串";
Ts可以在你没有限定变量类型的时候,会自动与推断它的类型。
undefined:变量未赋值的情况下,它的值就是这个
null:空值,就是空值,是我们希望它为空,主动赋值给它为空。
number:数值型,用来存储数字的类型。
string:字符串类型,用来存储一段话或者一行字母的类型。字符串只能为一行的,如果我们想要换行(不能使用\n)要使用模板。
我们使用`来换行。
${变量}来写有格式的内容
document.write("`我被换行了`");
let tmp=3;
document.write("我的值是tmp的值是${tmp}");
boolean:布尔值,表示是否。
any:任何类型,它可以是任何类型。
在声明变量的时候,变量名后添加[]来声明数组变量。
let tmp:number []=[1,2,3,4,5,6];
数组变量的类型可以是任何类型。
值得注意的是,索引是从0开始计数的。
let tmp:number []=[1,2,3,4,5,6];
document.write("这个数字是${tmp[2]}");
// 输出内容应该为3
在限制变量的时候使用|来再添加一种类型,这样可以让一个变量支持两种类型。
let tmp:number|string;
// 现在这个tmp支持数值和字符串类型。
// 可以在使用中当做对应的类型。
// 如果使用时是未联合的类型,那就会报错
使用枚举来定义属于我们自己的类型。
我们使用关键字enum定义枚举
enum Color
{
red,
green,
yellow
}
我们可以用typeof来确定对应的类型。
document.write(typeof tmp);
// 输出打印的就是tmp的类型
// 这样可以把常用的类型再起个别名,但是时机还是原来的类型名
type NewNumber=number;
// (判断条件) ?{语句1}:{语句2}
// 判断条件如果为真,执行语句1
// 判断条件如果为假,执行语句2
算数运算符:
算术运算符计算出对应的结果,跟我们生活中的算数相等。
求余计算得出来的是先除法运算,然年后不计算小数位,直接返回余数。
// + - * / %
// 加 减 乘 除 取余
比较运算符:
比较运算符返回布尔值
// > < == <= >=
// 大于 小于 等于 小于等于 大于等于
// 这些都是值的比较,不比较类型
// ===
// 这个比== 更严苛,值相同,还得类型相等。
// !=
// 不等于,这个也是不比较类型的
// !==
// 这个比 != 更严苛,值不相同,还得类型不相等。
逻辑运算符:
// && ||
// 与 或
// 与 一假必假
// 或 一真必真
// !
// 非
// 把后面的布尔值取反
赋值运算符:
// += -= *= /=
// 先跟运算符右边的值计算,再赋值给左边的变量
// ++ --
// 自加 自减
注意自加和自减要注意位置,放在变量前面,先计算后输出,放在变量后面,先输出再计算。
let a=3;
let b=3;
let c=3;
let d=3;
document.write(a++);
// 输出3
document.write(++b);
// 输出4
document.write(c--);
// 输出3
document.write(--d);
// 输出2
// if(条件){执行语句}
// 如果条件为真,那就执行后面的执行语句
// 否则不执行
// if(条件){执行语句1}else{执行语句2}
// 如果条件为真,那就执行执行语句1
// 否则执行执行语句2
// if(条件1){执行语句1}else if(条件2){执行语句2}
// 如果条件1为真,那就执行执行语句1
// 否则执行判断条件2,如果为真执行语句2
// 否则不执行
enum Color
{
red,
green,
yellow
}
Color color=Color.red;
switch(color)
{
case(Color.red):
break;
// 当color等于Color.redw,就执行这里
case(Color.green):
break;
// 当color等于Color.green,就执行这里
case(Color.yellow):
// 当color等于Color.yellow,就执行这里
break;
default:
// 其他状态,指上面的也不通过条件,就执行这里
break;
}
// While(条件){执行语句}
// 当条件为真,那就执行执行语句,执行完毕后,再判断条件。
// 以此进行循环,注意死循环情况,要能跳出。
// for(初始化;条件;控制跳出)
// {
// 执行语句
// }
// 初始化只在最初进入循环进行一次
// 然后每次循环前都判断条件,是否能进行
// 控制跳出则是在每次执行循环后执行
// Example:
let max=5;
for(let i=0;i
// break 结束最近循环,
// 跳过当前这一次的循环,直接进入下一次的循环
// 关键字 函数名 参数列表
// 函数相当于流水线,我们输入参数,得到返回值
// Example 这个例子没有返回值
function func(char :string)
{
let arr:string[]={"a","b","c"};
for(let i=0;i<5;i++)
{
if(char==arr[i])
{
document.write("当前是第"+i+"个字符");
}
}
}
// Example 这个例子有返回值
function add(num1:number,num2:number):number// 这里控制返回类型
{
// return后会直接返回函数,后面的不会执行
return num;
}
// Example 特殊的写法,这个add既可以当变量赋值,也可以当方法名
let add2=fubction()
{
}
//Example
let add3=()=>{}
// 调用语句
func('a');
let test=add(3,4);
add2();
类:相当于模具,是一个抽象的概念。这是由程序员自己定义的。
成员属性:类中的睡醒,需要从类中去访问。抽象解释,这个类的属性,比如人的身性别。
成员方法:类中的方法,需要从类中去访问。抽象解释,这个类的功能,比如人会说话。
对象:实体,具体的实例,需要使用关键字new来声明。
声明类关键字:Class
面向对象的三种特性:继承、多态、封装。
继承不能写成**:,要使用extends**关键字。
类中的可访问属性和方法,在类外访问都要用**.**的形式去访问。
使用关键字constructor
constructor(){}这是无参的构造方法。
也可以根据需求来添加参数。
构造方法不能被调用,默认是在类在被实例化时自动调用。
静态属性虽然可以可以写在类里,但是实际上并不属于对象,静态的属性或者方法,都是在程序刚运行时就开始静态初始化了。只能通过类名.的形式来调用。
只要添加static,那么他就是静态的。
class Person
{
// 静态属性
static des:string="这是一个person类";
// 默认值,也叫缺省值
name :string="默认";
age:number=0;
// 无参构造方法
constructor()
{
}
// 有参构造方法
constructor(name :string, age :number)
{
this.name=name;
this.age=age;
}
say()
{
document.write(this.name);
}
}
// 实例化方法
let a=new Person()
{
a.name="我的名字";
a.age=20;
a.say();
}
// 继承
class Person
{
name:string="";
say()
{
docyment.write("我是人类,叫做"+this.name);
}
}
// 学生类继承了人类
class Student extends Person
{
// 这个学生继承了人类的所有属性和方法
// 现在这里创建的都是学生独有的属性
num:number=0;
score:number=0;
// 如果我们写得新方法跟父类重名,则会覆盖掉父类
// 如果我们不想覆盖,想对方法进行扩充
say()
{
// 这里super关键字代表父类
super.say();
docyment.write("我是学生,叫做"+this.name);
}
}
let a=new Student();
a.name-"aaa";
//
a.say();
继承自抽象类的子类,必须重写父类的抽象方法。
抽象类关键字:abstract
// 抽象类本身不能被实例化为对象,只能用作继承。
abstract class Person
{
name :string=”“;
// 抽象类中可以存在正常方法
run()
{}
// 抽象方法在声明时不能实现
abstract say();
}
class Student extents Person
{
// 重写抽象方法
say();
}
这是面向对象非常重要的一点
let a:Person=new Student();
a.say();
因为TS只能单继承自一个类,所以我们要实现接口。多对多会导致出错,接口就是为此产生的。
接口时用来实现的,继承接口关键字implements
// 人类
class Person{
name:string;
}
// 狼接口
interface IWolf
{
attack();
}
interface IDog
{
}
// 狼人
class wolfMan extends Person implements IWolf,IDog
{
attac()
{}
eat()
{}
}
我们不希望用户直接调用我们的字段,我们为了安全性,以及更多的可操作性。我们使用属性寄存器
// 属性寄存器
class Person
{
_hp:number=100;
// 取值
get hp()
{
return this._hp;
}
// 赋值
set hp(value)
{
if(value<0)
{
this._hp=0;
}
else
{
this._hp=value;
}
}
}
let a=new Person();
a.hp -=180;
document.write(a.hp+"");
同名的类和方法,或者多人合作,在编译时的解决方案。
通过命名空间,来统一管理。相当于在我们写得代码先加上一个标记
关键字namespace
namespace aa{
class Person
{
name:string;
}
}
namespace bb
{
class Person{}
}
我们的类想要在命名空间外使用,就需要使用export关键字。
namespace aa{
export class Person
{
name:string;
}
}
namespace bb
{
export class Person{
name:string;
}
}
let person=new aa.Person();
let person2=new bb.Person();
我们在写代码时可能这个方法要的参数类型,我们并不确定,但是在方法里面的代码是需要确定类型的。
/*
// 这种是我们之前的写法
function add(num:any):any{
if(typeof num =="number")
{
num++;
}
return num;
}
*/
// 现在使用泛型
function add(num:T):T
{
if(typeof num =="number")
{
num++;
}
return num;
}
document.write(add+"");
两种方法的比较,第一种虽然也是实现了需求,但是很有可能造成传进去的参数和传出来的值类型不相同,这太随意了。而泛型则确定了传进去的是我们自定义的类型,传出来的也是我们要用的类型。尽量使用泛型来满足这一类需求。
元组不限定类型,我们可以使用它来传回一串数据。
let hero:[string,number]=["超人",100];
hero[0]=”蝙蝠侠“;
document.write(hero[0]);
// 打印输出蝙蝠侠
// 数组
let array1:number[]=[1,2,3];
let array2:Array =new Array();
// 长度
document.write(array1.length+"");
// 在数组后面追加元素
array1.push(4);
arrat1.unshift(0);
// 删除最后面的元素
array1.pop();
// 从第几位开始删除参数到第几位
array1.splice(0,2);
// 删除最前面的
array1.shift();
// 合并两个数组
array1=array1.concat(array2);
// 查找元素位置
let index=array1.indexof(3);
// 数字排序
array1.sort();// 正序 升序
array.reverse(); // 倒序 降序
我们自定义键的类型,也能自定义值
let dic:{[key:string]:string}={
"name":"王小虎",
"name2":"李逍遥"
};
// 如果我们给的键是空的,那么自动填充。
dic["name3"]="灵儿";
// 实现回调
function func(value:function)
{
// ...
value();
}
function test()
{
document.write("test");
}
func(test);
func(function(){
document.write("这是匿名函数");
})
func(()=>{ document.write("这也是匿名函数");});
网上搜索在线正则表达式
正则表达式使用单个字符串来描述、匹配符合某个句法规则的字符串。在很多文本编辑器中,正则表达式通常用来检索、替换、那些符合某个模式的文本。
正常的文本字符
具有特殊意义的专用字符,是代替正常文本字符的字符。
. 匹配除换行符以外的任意字符
\w 匹配字母或者数字或者下划线或汉字
\s 匹配任意的空白符
\d 匹配数字
\b 匹配单词的开始或结束
^ 匹配字符串的开始
$ 匹配字符串的结束
[a-z] 限定小写字符a到z
[A-Z]限定大写字符A到Z
[0-9]限定数字0-9
如果多个限制条件直接
[a-zA-Z]大小写都支持
限定匹配的数量或特殊条件
{n} 重复n次
{n,} 重复大于等于n次
{n,m} 重复n次到m次
***** 重复大于等于0次
+ 重复大于等于1次
? 重复0次或1次
常用功能建议百度,因为网上版本可能写得比你好
// 在代码中使用正则表达式
let reg=/\d{4}-\d{7}g;
let str="03450-1234567";
let res=reg.exec(str);
document.write(res.length+"");
// 匹配到的内容
res.forEach(function(value,index))
{
document.write(value+""+index);
}
公开的
私有的
受到保护的
使用方法,在类中声明属性或者方法时,在方法名前添加访问修饰符。
单例模式,也叫单子模式,是一种常用的软件设计模式,属于创建型模式的一种。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。
话不多说,单例模式大家应该很熟悉了,所以我们直接写单例
// 第一种写法 饥汉法
class SoundManager{
static Instance =new SoundManager;
private static instance:SoundManager;
private constructor ()
{}
}
// 我们直接调用属性就好了
SoundManger.Instance;
// 第二种写法
// 懒汉
class SoundManager{
private static instance:SoundManager;
static Instance()
{
if(!SoundManager.instance)
{
SoundManager.instance=new SoundManager();
}
return instance;
}
}
// 我们调用方法
SoundManager.Instance();
代理模式: 代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。
实际场景
实体创建比较费时:在等待期间给出提示;
本体创建出来占用内存过大: 等到用到这个实体的时候再去创建。
系统的权限控制: 用来过滤请求。
代理模式的结构
// Sample Example
// 抽象角色
interface ICalc
{
calc(num1,num2):number;
}
// 代理
class Npc1 implements ICalc
{
calc(num1,num2){
return num1 +num2;
}
}
// 代理
class Npc2 implements ICalc
{
clac(num1,num2)
{
return num1 -num2;
}
}
// 真实角色
class Person
{
delegate:Icale;
// 计算数字
GetNum(num1,num2){
// 拿到num1和num2计算后的结果
let num=this.delegate.cale(num1,num2);
document.write(num+"");
}
}
let person=new Person();
// 设定一个代理
person.delegate=new Npc2();
person.GetNum(3,4);
代理模式和装饰者模式的区别
代理模式和装饰者模式都是对真实对象进行修饰。
代理模式一般不会添加额外的方法,最多会加一些权限校验的方法。而装饰者模式就是为了对真实对象扩展而存在的。
代理模式的利弊
利:代理模式可以推迟大内存对象的创建到其他元素加载完毕之后,这往往能给用户带来一种速度大幅提升的感觉。
在较长时间的操作增加“正在加载等提示”。将权限系统的权限判断和实际操作分离开。
弊:将大对象推迟创建后,用户在第一次使用时会感觉很慢而大吃一惊。在不恰当的场合使用会增加无谓的复杂性,还不如直接访问本体轻松。
很多对象去监听同一个物体,当那个物体发生改变,需要通知所有的对象。
观察者模式
观察者模式: 观察者模式是定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
实际场景
观察者模式的结构
// Example
interface IObserver
{
nameChanged(newName);
}
class Person
{
private _name:string;
// 所有的观察者
observers:Array=new Array();
set name(value)
{
this._name=value;
// 发生变化
// 遍历所有的观察者,给所有的观察者发消息
for(let observer of this.observers)
{
observer.nameChange(this._name);
}
}
get name()
{
return this._name;
}
}
class Test implements IOserver{
nameChanged(newName)
{
document.write("监听到变化,名称变为"+newName);
}
}
let person=new Person;
person.name="嘎嘎";
Test test=new Test();
简单工厂模式其实并不算是一种设计模式,更多的时候是一种编程习惯。
定义
定义一个工厂类,根据传入的参数不同返回不同的实例,被创建的实例具有共同的父类或接口。
适用场景:
其实由定义也大概能推测出其使用场景,首先由于只有一个工厂类,所以工厂类中创建的对象不能太多,否则工厂类的业务逻辑就太复杂了,其次由于工厂类封装了对象的创建过程,所以客户端应该不关心对象的创建。总结一下适用场景:
(1)需要创建的对象较少。
(2)客户端不关心对象的创建过程。
// Example
enmu CarType{
Bmw,
Audi,
Benz
}
// 父类
class Car
{
name:string;
// 工厂方法
static Create(carType:CarType):Car
{
let car:Car;
switch(carType)
{
case CarType.Bmw:
car=new Bmw();
break;
case carType:CarType.Audi:
car=new Audi();
break;
case CarType.Benz:
car=new Benz();
break;
}
}
}
class Bmw extends Car{}
class Benz extends Car{}
class Audi extends Car{}
let car =Car.Creat(carType.Bmw);
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。 链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。 每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。
简单来说,就是在物理存储上并不是连续,有顺序的,我们通过逻辑将其串联起来。
遍历起来,数组最快。修改起来,链表最快。
// 范例
class Person
{
name :string;
next:Person;
constructor(name)
{
this.name=name;
}
}
let person=new Person("李逍遥");
person.next=new Person("王小虎");
person.next.next=new Person("赵灵儿");
// 删除
person.next=person.next.next;
// 添加
let tmpPerson=new Person("赵四");
tmpPerson.next=new Person("柳如烟");
tmpPerson.next=person.next;
while(person)
{
dpcument.write(person.name);
// 让节点后移
person=person.next;
}