TypeScript

视频

一,TypeScript简介

  • 以JavaScript为基础构建的语言
  • 可以在任何支持JavaScript的平台中执行
  • 一个JavaScript的超集
  • TypeScript扩展了JavaScript,并添加了类型
  • TS不能被JS解析器直接执行

在这里插入图片描述

TypeScript增加了什么?

  • 类型
  • 添加ES不具有的新特性
  • 支持ES的新特性
  • 丰富的配置选项
  • 强大的开发工具

编译/webstorm,vscode

console.log('hello ts');

在这里插入图片描述

二,TS的类型说明

//声明一个变量a,同时指定它的类型为number
let a: number;

//a 的类型设置为了number,在之后的使用过程中a的值只能是数字
a = 10;
a = 33;
//a = 'hello';	//此行代码会报错,因为变量a的类型是number,不能赋值字符串

//声明变量直接进行赋值
let c: boolean = true;

//如果变量的声明和赋值是同时进行的,TS可以自动对变量进行类型检测
let c = false;

c = true;


fuction sum(a: number, b: number): number{
    return a + b;
}

let result = sum(123, 456);

三,字面量

//直接使用字面两进行类型声明

//赋值后不能修改
let a: 10;

//可以使用 | 来连接多个类型(联合类型)
let b: "male" | "female";
b = "male";
b = "female";

let c: boolean | string;
c = true;
c = 'hello'

//any 表示的是任意类型,一个变量类型为any后相当于对该变量关闭了 TS 的类型检测
//使用TS时,不建议使用any类型
//let d: any;

//声明变量如果不知道类型,则TS解析器会自动判断变量的类型为any (隐式的any)
let d;
d = 10;
d = 'hello';
d = true;

//unknow 表示位置类型的值
let e: unknown;
e = 10;
e = "hello";
e = true;

let s:string;
//d的类型是any,它可以赋值给任意变量
s = d;

e = 'hello';
//报错
//s = e;

// unknown 实际上就是一个类型安全的any
// unknown 类型的变量,不要直接赋值给其他变量
if(typeof e === "string"){
    s = e;
}


//类型断言:可以用来告诉解析器变量的实际类型
/*
语法:
	变量 as 类型
	<类型>变量
*/
s = e as string;
s = <string>e;

//void 用来表示空,以函数为例,则表示没有返回值的函数
function fn(): void{
}

//never 表示永远不会返回结果
function fn2(): never{
    throw new Error('报错了!');
}

//object 表示js对象
let a :object;
a = {};
a = function (){  
};

// {} 用来指定对象中可以包含哪些属性
// 语法:{属性名:属性值,属性名:属性值}
// 在属性名后加上?,表示属性是可选的
let b: {name: string, age?:number};
b = {name: '孙悟空'};

//[propName: string]: any 表示任意类型的属性
let c:{name: string, [propName: string]: any};
c = {name:'猪八戒', age: 18, gender: '男'};

/*
设置函数结果的类型声明:
	语法:(形参:类型,形参:类型...)=>返回值
*/
let d: (a:number ,b:number)=>number;
//报错
/*
d = function (n1:string, n2:string): number{
    return 10;
}
*/

/*
数组的类型声明:
	类型[]
	Array<类型>
*/
//string[] 表示字符串数组
let e: string[];
e = ['a','b','c'];

//number[] 表示数值数组
let f: number[];

let g: Array<number>;
g = [1,2,3];

/*
元祖:元祖就是固定长度的数组
语法:[类型,类型,类型]
*/
let h: [string, number];
h = ['hello',123];

/*
枚举
*/
enum Gender{
    Male,
    Female
}
let i: {name: string, gender: Gender};
i = {
    name: '孙悟空',
    gender: Gender.Male //'male'
}
console.log(i.gender === Gender.Male)

//&表示同时
let j: { name: string } & {age:number};
j = { name: '孙悟空', age:18};

//类型的别名
type myType = 1 | 2 | 3 | 4 | 5;
let k: myType;
let l: myType;
let m: myType;

k = 2;

四,TS编译选项

  1. tsc app.ts -w 监视文件变化,一旦变化,重新编译

  2. tsc重新编译目录下所有tsc文件(前提必须有配置文件)

新建 tsconfig.json File

内容:
{

}

同时 tsc app.ts -w也可以直接监视所有文件的改变

  1. tsconfig.json文件介绍:
{
/*
	tsconfig.json是ts编译器的配置文件,ts编译器可以根据它的信息来对代码进行编译
	"include" 用来指定哪些ts文件需要被编译
		路径:** 表示任意目录
			 *  表示任意文件
	"exclude": 不需要被编译的文件目录
		默认值:["node_modules", "bower_components", "jspm_packages"]
*/
    "include": [
        "./src/**/*"
    ],
    "exclude": {
        "./src/hello/**/*"
    }
    
    /*
    	compilerOptions 编译器的选项
    */
    
    "compilerOptions": {
        
        // target 用来指定ts被编译为的ES的版本
        // es3,es5,es6,es2015,es2016,es2017,es2018,es2019,es2020,esnext
        "target": "es2015"
    
        // module 指定要是有的模块化的规范
        // 'none', 'commonjs', 'amd', 'system', 'umd', 'es6', 'es2015', 'es2020','esnext'
        "module": "es2015"
    
        //lib 用来指定项目中要使用的库,默认就是浏览器的运行环境
        // "lib": ["dom"]
    
    	//outDir 用来指定编译后文件所在的目录
    	"outDir": "./dist",
    
    	//将代码合并为一个文件
    	//设置outFile后,所有的全局作用域中的代码会合并到同一个文件中,如果要合并模块,模块化规范应该改成 amd 和 system
    	"outFile": "./dist/app.js"
    
    	//是否对js文件进行编译,默认是false
    	"allowJs": true,
    	
    	//是否检查js代码是否符号语法规范,默认值是false
    	“checkJs": true,
    
    	//是否移除注释
    	"removeComments": true,
    
    	//不生成编译后的文件
    	//"noEmit": true,
    
    	//当有错误时不生成编译后的文件
    	"noEmitOnError": true,
    
        //所有严格检查的总开关,设为true,以下全部打开,下面不用写了,建议true
    	"strict": true,
    
    	//用来设置编译后的文件是否使用严格模式,默认false
    	//js文件中有import,export默认进入严格模式
    	"alwaysStrict": true,
    
    	//不允许隐式的any类型
    	"noImplicitAny": true,
    
    	//不允许不明确类型的this
    	"noImplicitThis": true,
    
    	//严格的检查控制
    	"strictNullChecks": true,
    }
}

五,webpack

npm init -y  //生成package.json,管理项目

cnpm i -D webpack webpack-cli typescript ts-loader

  • 新建 webpack.config.js
  • 新建 tsconfig.json
  • 编辑 package.json "build": "webpack"
npm run build
cnpm i -D html-webpack-plugin  //自动生成html文件,改webpack.config.js
cnpm i -D webpack-dev-server  //内置服务器,自动刷新,改packge.json  ---  "start": "webpack serve --open"
cnpm i -D clean-webpack-plugin  //清除dist目录,改webpack.config.js

版本兼容

cnpm i -D @babel/core @babel/preset-env babel-loader core-js
//修改 webpack.config.js
 use: [
          //配置babel
          {
            //指定加载器
            loader: "babel-loader",
            //配置bebel
            options: {
              //设置预定义的环境
              presets: [
                [
                  //指定环境的插件
                  "@babel/preset-env",
                  //配置信息
                  {
                    //要兼容的目标浏览器
                    targets: {
                      "chrome": "88",
                      "ie":"11"
                    },
                    //指定corejs的版本
                    "corejs": "3",
                    //使用corejs的方式 “usage” 表示按需加载
                    "useBuiltIns":"usage"
                  }
                ]
              ]
            }
          },
          'ts-loader' //本来配置号的
        ],
      
        
    //告诉webpack不使用箭头
    environment: {
      arrowFunction: false
    }

六,面向对象

  • 操作浏览器要使用window对象
  • 操作网页要使用document对象
  • 操作控制台要使用console对象

程序之中所有的操作都需要通过对象来操作。

1.类(class)

//使用class关键字来定义一个类
/*
对象中主要包含了两个部分:
  属性
  方法
*/
class Person{


  /**
   * 直接定义的属性是实例属性,需要通过对象的实例去访问
   * const per = new Person();
   * per.name
   * 
   * 使用static开头的属性是静态属性(类属性),可以直接通过类名.访问
   * Person.age
   * 
   * readonly开头的属性表示一个只读的属性无法修改
   */

  // 定义实例属性
  // readonly name: string = '孙悟空';

  // 在属性前使用static关键字可以定义类属性(静态属性)
  // static readonly age: number = 18;

  name = '孙悟空';
  static age = 18;

  //定义方法 如果方法以static开头则方法就是类方法,可以直接通过类去调用
  static sayHello() {
    console.log('Hello 大家好!');
  }
}

const per = new Person();

console.log(per);

console.log(Person.age);

// Person.age = 33;

// per.sayHello();

Person.sayHello();

2. 构造函数

class Dog{
  name : string;
  age : number;

  // constructor 被称为构造函数
  // 构造函数会在在对象创建时调用
  constructor(name: string, age: number) {
    //在实例方法中,this就表示当前的实例
    //在构造函数中当前对象就是当前新建的那个对象
    //可以通过this向新建的对象中添加属性
    // console.log(this)
    this.name = name;
    this.age = age;
  }

  bark() {
    // alert('hihihi!');
    // 在方法中可以通过this来表示当前调用方法的对象
    console.log(this.name);
  }
}

const dog = new Dog('lucky',4);
// console.log(dog)
dog.bark();

3. 继承

(function () {
  
  //定义一个Animal类
  class Animal{
    name: string;
    age: number;

    constructor(name: string, age: number) {
      this.name = name;
      this.age = age
    }

    sayHello() {
      console.log('动物在叫');
    }
  }

  /**
   * Dog extends Animal
   *  - 此时,Animal1被称为父类,Dog被称为子类
   *  - 使用继承后,子类将会拥有父类所有的方法和属性
   *  - 通过继承可以将多个类中共有的代码写在一个父类中
   *      这样只需要写一次即可让所有的子类都同时拥有父类的属性和方法
   *      如果希望在子类中添加一些父类中没有的属性或方法直接添加即可
   *  - 如果在子类中添加了和父类相同的方法,则子类方法会覆盖掉父类的方法
   *      这种子类覆盖掉父类方法的形式,我们称为方法重写
   */

  //定义一个表示狗的类
  //使Dog类继承Animal类
  class Dog extends Animal{
    
    run() {
      console.log(`${this.name}在跑~~`);
    }

    sayHello(): void {
      console.log('汪汪汪');
    }
    
  }

  //定义一个表示猫的类
  //使Cat类继承Animal类
  class Cat extends Animal{

  }

  const dog = new Dog('旺财', 5);
  console.log(dog)
  dog.sayHello();
  dog.run();


})();

4. super关键字

(function () {
  class Animal {
    name: string;
    constructor(name: string) {
      this.name = name;
    }
    sayHello() {
      console.log('动物在叫~~~')
    }
  }

  class Dog extends Animal{

    age: number;
    constructor(name:string, age: number) {
      //如果在子类中写了构造函数,在子类构造函数中必须对父类的构造函数进行调用
      super(name);//調用父类的构造函数
      this.age = age;
    }

    sayHello(): void {
      //在类的方法中 super就表示当前类的父类
      super.sayHello();
    }
  }

  const dog = new Dog("lucky",3);
  dog.sayHello();

})();

5. 抽象类

(function () {

  /**
   * 以abstract开头的类是抽象类
   *    抽象类和其他类区别不大,只是不能用来创建对象
   *    抽象类就是专门用来被继承的类
   * 
   *    抽象类中可以添加抽象方法
   */
  abstract class Animal {

    name: string;

    constructor(name: string) {
      this.name = name;
    }


    //定义一个抽象方法
    //抽象方法使用abstract开头,没有方法体
    //抽象方法只能定义在抽象类中,子类必须对抽象方法进行重写
    abstract sayHello():void;
  }

  class Dog extends Animal{

    sayHello(): void {
      //在类的方法中 super就表示当前类的父类
      // super.sayHello();
      console.log('汪汪汪')
    }
  }

  const dog = new Dog("lucky");
  dog.sayHello();

  // const an = new Animal();

})();

6. 接口

(function () {
  
  //描述一个对象的类型
  type myType = {
    name: string,
    age: number
  }

  /**
   * 接口用来定义一个类结构,用来定义一个类中应该包含哪些属性和方法
   *    同时接口也可以当成类型声明去使用
   */
  interface myInterface{
    name: string;
    age: number;
  }

  interface myInterface{
    gender: string;
  }

  // const obj: myInterface = {
  //   name: 'sss',
  //   age: 111,
  //   gender: '男'
  // }

  /**
   * 接口可以在定义类的时候去现在类的结构,
   *  接口中的所有的属性都不能有实际的值
   *  接口只定义对象的结构,而不考虑实际值
   *    在接口中所有的方法都是抽象方法
   */

  interface myInter{
    name: string;

    sayHello(): void;
  }

  /**
   * 定义类是,可以使类去实现一个接口
   *    实现接口就是使类满足接口的要求
   */
  class MyClass implements myInter{
    name: string;

    constructor(name: string) {
      this.name = name;
    }
    
    sayHello(): void {
      console.log('大家好')
    }

  }

})();

7. 属性的封装

(function () {
  //定义一个表示人的类
  /**
   * public 修饰的属性可以在任意位置访问(修改)默认值
   *    包括子类
   * private 私有属性,私有属性只能在类内部进行访问(修改)
   *    通过在类中添加方法使得私有属性可以被外部访问
   * protect 受保护的属性,只能在当前类和当前类的子类中访问(修改)
   */
  class Person{
    private _name: string;
    private _age: number;

    constructor(name: string, age: number) {
      this._name = name;
      this._age = age;
    }

    /**
     * 
     * getter方法用来读取属性
     * setter方法用来设置属性
     *    - 他们被称为属性的存取器
     */

    //定义方法,用来获取name属性
    getName() {
      return this._name;
    }


    //定义方法,用来设置name属性
    setName(value: string) {
      this._name = value;
    }

    setAge(value: number) {
      //判断年龄是否合法
      if (value >= 0) {
        this._age = value
      }
    }

    //TS设置getter方法的方式
    get name() {
      return this._name;
    }

    set name(value: string) {
      this._name = value
    }

  }

  const per = new Person('孙悟空', 18);

  /**
   * 现在属性是在对象中设置的,属性可以任意的被修改
   *  属性可以任意被修改将会导致对象中的数据变得非常不安全
   */
  // per._name = '猪八戒';
  // per._age = 38;

  per.setName('猪八戒');

  console.log(per.getName());


  per.name = '123';
  console.log(per.name);

  class C{
    //可以直接将属性定义在构造函数中
    constructor(public name:string,public age: number){}
  }

  const c = new C('xxx', 111);
  console.log(c)

})();

8. 泛型

// function fn(a: any): any{
//   return a;
// }

/**
 * 在定义函数或者类时,如果遇到类型不明确就可以属于泛型
 * 
 */

function fn<T>(a: T): T{
  return a;
}

//可以直接调用具有泛型的函数
let result = fn(10);//不知道泛型,TS可以自动对类型进行推断
let result2 = fn<string>('hello');//指定泛型

//泛型可以同时指定多个
function fn2<T, K>(a: T, b: K): T{
  console.log(b);
  return a;
}
fn2<number, string>(123, 'hello');

interface Inter{
  length: number;
}

//表示泛型T必须是Inter实现类(子类)
function fn3<T extends Inter>(a: T): number{
  return a.length;
}

fn3('123')
fn3({length:3})


class MyClass<T>{
  name: T;
  constructor(name: T) {
    this.name = name;
  }
}

const mc = new MyClass<string>('孙悟空');

七,贪吃蛇项目

npm i -D less less-loader css-loader style-loader
npm i -D postcss postcss-loader postcss-preset-env

在这里插入图片描述

index.ts

//引入样式
import './style/index.less';
// import Food from './moduls/Food';

// const food = new Food();
// console.log(food.X, food.Y);
// food.change();
// console.log(food.X, food.Y);

import GameControl from './moduls/GameControl';

const gameControl = new GameControl();

// setInterval(() => {
//   console.log(gameControl.direction);
// }, 1000);

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>贪吃蛇</title>
  </head>
  <body>
    <!-- 创建游戏的主容器 -->
    <div id="main">
      <!-- 设置游戏的舞台 -->
      <div id="stage">
        <!-- 设置蛇 -->
        <div id="snake">
          <!-- snake内部div 表示蛇的各部分 -->
          <div></div>
        </div>

        <!-- 设置食物 -->
        <div id="food">
          <div></div>
          <div></div>
          <div></div>
          <div></div>
        </div>
      </div>

      <!-- 设置有效的积分牌 -->
      <div id="score-panel">
        <div>SCORE:<span id="score">0</span></div>
        <div>level:<span id="level">1</span></div>
      </div>
    </div>
  </body>
</html>

index.less

//设置变量
@bg-color: #b7d4a8;

//清除默认样式
*{
  margin: 0;
  padding: 0;
  //改变盒子模型的计算方式
  box-sizing: border-box;
}

body{
  font:bold 20px "Courier";
}

//设置主窗口的样式
#main{
  width: 360px;
  height: 420px;
  background-color: @bg-color;
  margin: 100px auto;
  border: 10px solid black;
  border-radius: 40px;


  display: flex;
  flex-flow: column;
  //侧轴
  align-items: center;
  //主轴
  justify-content: space-around;
  

  //游戏舞台
  #stage{
    width: 304px;
    height: 304px;
    border: 2px solid black;
    position: relative;

    //设置蛇的样式
    #snake{
      &>div{
        width: 10px;
        height: 10px;
        background-color: #000;
        border: 1px solid @bg-color;
        position: absolute;
      }

    }

    #food{
      width: 10px;
      height: 10px;
      position: absolute;
      display: flex;
      flex-flow: row wrap;
      justify-content: space-between;
      align-content: space-between;

      left: 40px;
      top: 100px;

      &>div{
        width: 4px;
        height: 4px;
        background-color: black;
        transform: rotate(45deg);
      }
    }
  }

  //记分牌
  #score-panel{
    width: 300px;
    display: flex;
    justify-content: space-between;
  }
}

Snake.ts

class Snake{

  head: HTMLElement;
  bodies: HTMLCollection;
  element: HTMLElement;

  constructor() {
    this.element = document.getElementById('snake')!;
    this.head = document.querySelector('#snake > div') as HTMLElement;//断言有!的功能
    this.bodies = this.element.getElementsByTagName('div');
  }

  get X() {
    return this.head.offsetLeft;
  }
  get Y() {
    return this.head.offsetTop;
  }
  set X(value) {
    //如果新值和旧值相同,则直接返回不再修改
    if (this.X === value) {
      return;
    }
    
    if (value < 0 || value > 290) {
      throw new Error('蛇撞墙了');
    }

    //修改x时,是在修改水平坐标,蛇在左右移动,蛇在向左移动时,不能向右掉头,反之亦然
    if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === value) {
      if (value > this.X) {
        value = this.X - 10;
      } else {
        value = this.X + 10;
      }
    }

    //移动身体
    this.moveBody();

    this.head.style.left = value + 'px';

    this.checkHeadBody();
  }
  set Y(value) {
    //如果新值和旧值相同,则直接返回不再修改
    if (this.Y === value) {
      return;
    }

    if (value < 0 || value > 290) {
      throw new Error('蛇撞墙了');
    }

    //修改x时,是在修改垂直坐标,蛇在上下移动,蛇在向上移动时,不能向下掉头,反之亦然
    if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value) {
      if (value > this.Y) {
        value = this.Y - 10;
      } else {
        value = this.Y + 10;
      }
    }

    //移动身体
    this.moveBody();

    this.head.style.top = value + 'px';

    this.checkHeadBody();
  }

  addBody() {
    this.element.insertAdjacentHTML("beforeend", "<div></div>");
  }

  //添加一个蛇身体移动的方法
  moveBody() {
    /**
     * 将后边的身体设置为前边身体的位置
     * 举例:
     *    第3节 = 第2节的位置
     *    第2节 = 蛇头的位置
     */
    for (let i = this.bodies.length - 1; i > 0; i--){
      let X = (this.bodies[i - 1] as HTMLElement).offsetLeft;
      let Y = (this.bodies[i - 1] as HTMLElement).offsetTop;

      (this.bodies[i] as HTMLElement).style.left = X + 'px';
      (this.bodies[i] as HTMLElement).style.top = Y + 'px';
    }
  }

  //检查是否撞到身体
  checkHeadBody() {
    //获取所有的身体,检测其是否和蛇头的坐标发生重叠
    for (let i = 1; i < this.bodies.length; i++){
      let bd = this.bodies[i] as HTMLElement;
      if (this.X === bd.offsetLeft && this.Y === bd.offsetTop) {
        throw new Error('撞到自己了~~~');
      }
    }
  }
}

export default Snake;

ScorePanel.ts

//定义表示记分牌的类
class ScorePanel{

  score = 0;
  level = 1;

  scoreEle: HTMLElement;
  levelEle: HTMLElement;

  maxLevel: number;
  upScore: number;
  
  constructor(maxLevel: number = 10,upScore: number = 10) {
    this.scoreEle = document.getElementById('score')!;
    this.levelEle = document.getElementById('level')!;
    this.maxLevel = maxLevel;
    this.upScore = upScore;
  }

  addScore() {
    this.scoreEle.innerHTML = ++this.score + '';

    if (this.score % this.upScore === 0) {
      this.levelUp();
    }
  }

  levelUp() {
    if (this.level < this.maxLevel) {
      this.levelEle.innerHTML = ++this.level + '';
    }
  }
}

export default ScorePanel;

// const scorePanel = new ScorePanel(100,2);
// for (let i = 0; i < 10; i++){
//   scorePanel.addScore();
// }

GameControl.ts

import Snake from "./Snake";
import Food from "./Food";
import ScorePanel from "./ScorePanel";

class GameControl{
  snake: Snake;
  food: Food;
  scorePanel: ScorePanel;

  //创建一个属性来存储蛇的一定方向(也就是按键的方向)
  direction: string = '';
  //创建一个属性用来记录游戏是否结束
  isLive = true;

  constructor() {
    this.snake = new Snake();
    this.food = new Food();
    this.scorePanel = new ScorePanel();
    this.init();
  }

  init() {
    document.addEventListener('keydown', this.keydownHandler.bind(this));
    this.run();
  }

  /**
   * 
   *  ArrowUp ArrowDown ArrowRight ArrowLeft
   */

  keydownHandler(event: KeyboardEvent) {
    //修改direction属性
    this.direction = event.key;
    
  }

  run() {
    let X = this.snake.X;
    let Y = this.snake.Y;

    //根据按键方向来修改X值和Y值
    switch (this.direction) {
      case "ArrowUp":
      case "Up":
        Y -= 10;
        break;
      case "ArrowDown":
      case "Down":
        Y += 10;
        break;
      case "ArrowLeft":
      case "Left":
        X -= 10;
        break;
      case "ArrowRight":
      case "Right":
        X += 10;
        break;
    }
    
    //检测蛇是否吃到了食物
    this.checkEat(X, Y);

    //修改蛇的X和Y值
    try {
      this.snake.X = X;
      this.snake.Y = Y;
    } catch (e) {
      alert((e as Error).message + ' GAME OVER!');
      this.isLive = false;
    }

    this.isLive && setTimeout(this.run.bind(this), 300 - (this.scorePanel.level - 1)*30);
  }

  //定义一个方法,用来检测蛇是否吃到食物
  checkEat(X: number, Y: number) {
    if (X === this.food.X && Y === this.food.Y) {
         //食物的位置要进行重置
         this.food.change();
         //分数增加
         this.scorePanel.addScore();
         //蛇要增加一节
         this.snake.addBody();
    }
  }
}

export default GameControl;

Food.ts

class Food{

  element: HTMLElement;

  constructor() {
    //获取页面中的food元素并将其赋值给element
    this.element = document.getElementById('food')!;//!不可能为空
  }

  get X() {
    return this.element.offsetLeft;
  }

  get Y() {
    return this.element.offsetTop;
  }

  change() {

    let top = Math.round(Math.random() * 29) * 10;
    let left = Math.round(Math.random() * 29) * 10;

    this.element.style.left = left + 'px';
    this.element.style.top = top + 'px';
  }

}

export default Food;

// const food = new Food();
// console.log(food.X, food.Y);
// food.change();
// console.log(food.X, food.Y);

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值