JavaScript

javascript 基础

快速上手
MDN

JS 定义

HTML/CSS/JS 的关系,HTML 负责网页结构,CSS 负责网页样式,JS 负责网页行为

JavaScript 是运行在浏览器端的脚本语言(弱类型),由浏览器解释执行,不需要编译,功能在运行过程中逐行解释并执行的,现在的 JavaScript 也可以在 Node.js 技术进行服务端运行

现在 JS 使用于网页特效,服务端开发(Node.js),桌面程序(Flectron),App(Cordova),控制硬件-物联网(Ruff),游戏开发(cocos2d js)等等

  1. 浏览器如何执行 JS

浏览器分为渲染部分和 JS 引擎
渲染引擎:用来解析 HTML 和 CSS,俗称内核,如 chrome 浏览器的 blink,老版本的 webkit
JS 引擎:也成为 JS 解释器。用来读取网页中的 JS 代码,对其处理后运行,如 chrome 浏览器的 V8

  1. JSAPI DOM—文档对象模型

是处理可扩展性标记语言的标准编程接口,提供各种接口,可以对页面上的各种元素进行操作如大小,位置,颜色

  1. JSAPI BOM—浏览器对象模型

提供独立内容,可以于浏览器窗口进行互动的对象结构,通过 BOM 可以操作浏览器窗口,如弹出窗口,控制浏览器跳转,获取分辨率

JS 引入方式

  1. 行内式 - 用于事件
<input type="button" name="" onclick="alert('ok!')" />
  1. 内嵌式 - 学习使用
<script type="text/javascript">
  alert("ok!");
</script>
  1. 外链式 - 常用
<script type="text/javascript" src="js/inderx.js"></script>

JS 代码结构

  1. 使用;分割消息,会有自动分号插入,但并不总是插入在预期位置。

  2. 使用//或者/* */进行注释

  3. 现代模式需要使用use strict开启,且必须在脚本的第一行,或使用包装器函数进行包裹。

    • 目前最好将其写入脚本当中,但当代码全部写在 class 和 module 中时,会自动开启。
    • 开启后无法关闭。

变量和数据类型

  1. 变量的声明 - 通常使用let进行声明
let user; // 只声明
let username = "jojo"; // 声明并赋值
let age = 18,
  message = "hello"; // 同时声明多个变量
  1. 变量命名规范 - 变量名称必须仅包含字母、数字、符号 $_,且首字符必须非数字。
// 区分大小写,通常使用小驼峰命名(除第一个单词以外都以大写字母开头)
// 对象类型+变量名如:对象 age 则命名为 nAge
// 变量名最好有实际含义
  1. 常量的声明 - 使用const进行声明,声明的常量不可修改。
const COLOR_RED = "#f00"; // 常量名全部大写
const age = someCode(BIRTHDAY); // 常量值在声明时确定,这样的常量则使用变量命名法则。
  1. 旧的声明方式 - 使用var进行声明(不推荐使用)
var username = "jojo"; // 一般情况下不再使用
  1. 数据类型 - js 当中有七大原始类型
    • number 数据类型,bigint 大整数类型
    • string 字符类型
    • boolean 布尔类型
    • undefined undefined 类型,变量声明未初始化
    • null null 类型,表示空对象,如果定义的变量将来准备保存对象,可将变量初始化为 null
    • object 复合类型,Symbol 创建对象的唯一标识符
let nFigure = 1; // typeof iNumber = number
nFigure = 2.123; // typeof iNumber = number
nFigure = Infinity; // typeof iNumber = number
nFigure = NaN; // typeof iNumber = number
bFigure = 123n; // typeof iNumber = bigint // 整数后加n设置为 bigint 大整数类型

let sName = "jojo"; // 可以使用单引号或双引号,没有特殊含义 typeof sName = string
let sName2 = `${sName} is a boy`; // 使用反引号包裹,可以插入变量;

let boolNameFieldChecked = true; // 名称字段是否被检测,true已经检查,typeof boolNameFieldChecked = boolean

let age = null; // typeof age = object // null 表示空,是一个空对象指针

let age2; // typeof age2 = undefined // 未定义

// 最后一种到对象时演示
  1. 数据类型转换
let nFigure = 12;
nFigure1 = Infinity;
nFigure2 = NaN;
let sFigure = "abc12";
let sFigure1 = "12";
let boolFigure = true;
let empty = null;
let undefinedFigure;
let conversion;

// 转换为字符串
conversion = String(nFigure); // '12'
conversion = nFigure1.toString(); // 'Infinity'
conversion = nFigure2.toString(); // 'NaN'
conversion = String(boolFigure); // 'true'
conversion = String(empty); // '[object Undefined]'
conversion = undefinedFigure.toString(); // 'undefined'

// 转换为数字
conversion = Number(sFigure); // NaN
conversion = Number(sFigure1); // 12
conversion = Number(boolFigure); // 1
conversion = Number(empty); // 0
conversion = Number(undefinedFigure); // NaN

// 转换为布尔值
let boolFigure = Boolean(nFigure); // true
boolFigure = Boolean(empty); // false 数字0,空字符串"",null,undefined,NaN 变换后为 false

// 转为object到对象时演示

交互方式

  1. alert - 弹出消息窗口
alert("hello world!");
  1. prompt - 弹出消息窗口,可以接收输入
let age = prompt("请输入年龄:", 18); // 第一个参数是提示信息,第二个参数是初始值(可以省略,但不建议)
alert(`${age}岁`); // 会弹两次窗口,第一次是输入窗口,第二次是弹出的提示窗口
  1. confirm - 弹出消息窗口,并可以点击确定或取消
let isConfirm = confirm("确定要删除吗?");
if (isConfirm) {
  alert(isConfirm);
  alert("删除成功");
} else {
  alert("取消删除");
}

运算符

优先级 运算符类型 结合性 运算符 使用场景
19 分组 n/a (…) a*(b-c)
18 成员访问 从左到右 ….… obj.a
18 需计算的成员访问 从左到右 …[…] obj["name"]
18 new(带参数列表) n/a new …(…)构建实例 new add(1,2)
18 函数调用 从左到右 …(…)执行函数 add(1,2)
18 可选链 从左到右 …?.…?前校验 obj.a?.b
17 new(无参数列表) 从右到左 new … new add
16 后置递增 n/a …++ i++
16 后置递减 n/a …-- i--
15 逻辑非 从右到左 !… !true=false
15 按位非 从右到左 ~… ~1011=0100
15 一元加法 从右到左 +… +12
15 一元减法 从右到左 -… -13
15 前置递增 从右到左 ++… ++i
15 前置递减 从右到左 --… --i
15 typeof 从右到左 typeof … typeof a
15 void 从右到左 void … void(0)
15 delete 从右到左 delete … delete a.b
15 await 从右到左 await … await add()
14 从右到左 …**… a**b
13 乘法 从左到右 …*… a*b
13 除法 从左到右 …/… a/b
13 取余 从左到右 …%… a%b
12 加法 从左到右 …+… a+b
12 减法 从左到右 …-… a-b
11 按位左移 从左到右 …<<… 16<<2=64
11 按位右移 从左到右 …>>… 16>>2=4
11 无符号右移 从左到右 …>>>… -16>>>31=1
10 小于 从左到右 …<… 5<6=true
10 小于等于 从左到右 …<=… 5<=6=true
10 大于 从左到右 …>… 5>6=false
10 大于等于 从左到右 …>=… 5>=6=false
10 in 从左到右 …in… a in b
10 instanceof 从左到右 …instanceof… a instanceof b
9 相等 从左到右 …==… a == b
9 不相等 从左到右 …!=… a != b
9 严格相等 从左到右 …===… a === b
9 严格不相等 从左到右 …!==… a !== b
8 按位与 从左到右 …&… 12 & 4=4
7 按位异或 从左到右 …^… 12 ^ 4=8
6 按位或 从左到右 …|… 12 | 4=12
5 逻辑与 从左到右 …&&… 12 && 4=4
4 逻辑或 从左到右 …||… 12 & 4=12
4 空值合并 从左到右 …??… 替代逻辑或,做默认值
3 条件(三元)运算符 从右到左 …?…:… a?1:2
2 赋值 从右到左 …=… a=2
2 赋值 从右到左 …+=… a+=2
2 赋值 从右到左 …-=… a-=2
2 赋值 从右到左 …**=… a**=2
2 赋值 从右到左 …*=… a*=2
2 赋值 从右到左 …/=… a/=2
2 赋值 从右到左 …%=… a%=2
2 赋值 从右到左 …<<=… a<<=2
2 赋值 从右到左 …>>=… a>>=2
2 赋值 从右到左 …>>>=… a>>>=2
2 赋值 从右到左 …&=… a&=2
2 赋值 从右到左 …^=… a^=2
2 赋值 从右到左 …|=… a|=2
2 赋值 从右到左 …&&=… a&&=2
2 赋值 从右到左 …||=… a||=2
2 赋值 从右到左 …??=… a??=2
1 逗号/序列 从左到右 …,… a=2,b=3
  1. ()[],优先级运算符
  2. 一元操作符 -减,+加,--自减,++自增
    • 自减自增运算符,前置和后置的区别,前置先运算再赋值,后置先赋值再运算
    • 正/负运算符,会自动执行类型转换,将其他类型转换为数字类型
  3. 算术运算符 +加,-减,*乘,/除,%取余,**幂;先乘除后加减
    • 连接运算符 +,'1'+2="12",2+2+'1'="41",1+'2'+'2'="122"
  4. 关系操作符 ==等于,!=不等于,===全等于,!==不全等于,>大于,<小于,>=大于等于,<=小于等于
    • 比较结果均返回布尔类型
    • 字符串比较会按照字典的方式一个个字符比较,比较的是字符的 Unicode 编码
    • 等于==会转换类型并对比。全等于===不转换类型直接对比。
  5. 逻辑运算符 !非,&&与,||或,??空值合并;先非再与最后或
    • !true = false,!false = true
    • true||true = true,true||false = true,false||true = true,false||false = false
    • 或运算执行到true就停止,返回true
    • true&&true = true,true&&false = false,false&&true = false,false&&false = false
    • 与运算执行到false就停止,返回false
    • ??空值合并运算符,当运算符左侧为nullundefined时,返回右侧的值,否则返回左侧的值
  6. 三目运算符 ? :,如果条件为真,则返回冒号前的值,否则返回冒号后的值
  7. 赋值运算符 =等号,+=加等,-=减等,*=乘等,/=除等,%=取余等
  8. 逗号运算符 ,,从左到右依次执行,返回最后一个值

条件分支语句

  1. if 语句
let nFigure = 70;
let sFigure = "12";
//第一种
if (nFigure == 70) {
  console.log("相等!");
}
//第二种
if (sFigure === 12) {
  console.log("相等!");
} else {
  console.log("不相等!");
}
//第三种
if (nFigure <= 100 && nFigure > 90) {
  console.log("优秀");
} else if (nFigure <= 90 && nFigure > 80) {
  console.log("良好");
} else if (nFigure <= 80 && nFigure >= 60) {
  console.log("及格");
} else {
  console.log("不及格");
}
  1. ? 语句
let nFigure = 70;
let sFigure = "12";
console.log(nFigure == 70 ? "相等!" : "不相等!");

console.log(
  nFigure <= 100 && nFigure > 90
    ? "优秀"
    : nFigure <= 90 && nFigure > 80
    ? "良好"
    : nFigure <= 80 && nFigure >= 60
    ? "及格"
    : "不及格"
);

nFigure <= 100 && nFigure > 90
  ? console.log("优秀")
  : nFigure <= 90 && nFigure > 80
  ? console.log("良好")
  : nFigure <= 80 && nFigure >= 60
  ? console.log("及格")
  : console.log("不及格");

循环语句

  1. while 循环
let nFigure = 0;
while (nFigure < 100) {
  nFigure++;
  console.log(nFigure);
}
  1. do-while 循环,至少循环一次
let nFigure = 0;
do {
  nFigure++;
  console.log(nFigure);
} while (nFigure < 100);
  1. for 循环
// let nFigure = 0;
for (let nFigure = 0; nFigure <= 100; nFigure++) {
  // 内联变量声明
  console.log(nFigure);
}
/* 执行流程:
->let nFigure = 0;
->如果nFigure <= 100 成立 -> 运行console.log(nFigure); ->运行nFigure++
->如果nFigure <= 100 成立 -> 运行console.log(nFigure); ->运行nFigure++
->...
->如果nFigure <= 100 不成立 -> 结束循环
*/

跳出循环

  1. break 语句
for (let nFigure = 0; nFigure <= 100; nFigure++) {
  if (nFigure == 50) {
    break;
  }
  console.log(nFigure);
}
  1. continue 语句
for (let nFigure = 0; nFigure <= 100; nFigure++) {
  if (nFigure == 50) {
    continue;
  }
  console.log(nFigure);
}
  1. 标签跳出
outer: for (let nFigure = 0; nFigure <= 100; nFigure++) {
  for (let jNum = 0; jNum <= 100; jNum++) {
    if (nFigure == 50) {
      break outer; // 跳出outer循环,直接执行到console.log("跳出循环");
    }
    console.log(nFigure);
  }
}
console.log("跳出循环");

多分枝语句

这里主要的是接收数据的数据类型,需要同时匹配才可以。

switch (x) {
  case 1:
    //do something
    break;
  case 2:
    //do something
    break;
  default:
    //do something
    break;
}

函数

函数声明和调用

函数通常是一个动作/行为,给函数起名时应该是动词,简短且明确的描述函数作用。
通常是动词开头例如:show..展示什么内容,get...得到什么值,calc...计算某些内容,create...创建一些东西,check...检查内容并返回布尔值。

// 1. 函数声明,声明后的函数可以在任意地方调用,因为这是全局性的
function fnName(parameter1, parameter2, ... parameterN) {
  ...body...
}// 声明一个叫fnName的函数,接收参数parameter1,parameter2,...parameterN,函数体为body
// 2. 函数复制
let func = fnName; // 将函数赋值给变量func,func也可以调用函数
// 3. 函数调用
fnName(value1, value2, ... valueN);// 调用函数,传入参数value1,value2,...valueN
func(value1, value2, ... valueN);// 同等调用
// 4. 函数表达式,创建一个匿名函数并复制给sayHi
// 执行到这一句时才会创建函数,这只能在此之后调用函数
let sayHi = function(value1, value2, ... valueN) {
  alert(value1);
};
// 5. 传入的参数可以是任意类型,包括函数
// 如果传入函数,则称其为回调函数,回调函数会在函数体内被调用

示例:

function calcAdd(nFigure1, nFigure2) {
  let nRs = nFigure1 + nFigure2;
  alert(nRs);
  return nRs;
}
let nCount = calcAdd(3, 4);
alert(nCount);

变量作用域

js 变量分为全局变量和局部变量,局部变量优先全局变量
如果全局变量和局部变量重名,在函数内使用的是局部变量(形成遮蔽),在函数外使用的是全局变量

//局部变量
function fnMyalert() {
  let b = 20;
  alert(b);
}
fnMyalert();
alert(b);
//全局变量
let c = 22;
function fnYouralert() {
  c++;
}
fnYouralert();
alert(c);

传入参数和返回值

function calcAdd(nFigure1, nFigure2 = "abc") {
  // 设置形参,接收传入的参数
  // 设置默认参数,这里可以是另外的函数(当第二个参数没传入时,才会执行,如果传入参数则不会执行)

  return nFigure1 + nFigure2; // 返回值,将计算结果返回,函数立即停止并返回内容,若果没有return则返回undefined
}

ES6 箭头函数

let func = () => ...body...;
let func1 = n => ...body...;
let func2 = (n, m) => {
  ...body...;
  return n*m
};
()=>...body...;
// 等同于
let func = function () {
  ...body...;
}
let func1 = function (n) {
  ...body...;
}
let func2 = function (n,m) {
  ...body...;
  return n*m
}
function(){
  ...body...;
}

浏览器调试方法

F12 开启开发者工具
在“控制台”输入代码,可以实时查看代码运行结果。
在“代码来源”处添加 js 文件,可进行断点调试。
在代码文件当中添加debugger;,可进行断点调试。执行到此时会自动暂停。
image

在“控制台”使用console.log()代码可以输出内容

函数测试

使用 Mocha 进行函数测试

function pow(x, n) {
  return x ** n;
}
describe("pow", function () {
  it("pow(2,3) should return 8", function () {
    // 一个it最好只使用一个断言
    assert.equal(pow(2, 3), 8); // 这里代码就叫做断言,如果pow(2,3)不等于8,则测试失败
  });
  it("pow(3,4) should return 81", function () {
    // 通常使用循环创建多个it进行测试通用性
    assert.equal(pow(3, 4), 81);
  });
});

示例写法

<!DOCTYPE html>
<html>
  <head>
    <!-- add mocha css, to show results -->
    <link
      rel="stylesheet"
      href="https://cdnjs.cloudflare.com/ajax/libs/mocha/3.2.0/mocha.css"
    />
    <!-- add mocha framework code -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/3.2.0/mocha.js"></script>
    <script>
      mocha.setup("bdd"); // minimal setup
    </script>
    <!-- add chai -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/chai/3.5.0/chai.js"></script>
    <script>
      // chai has a lot of stuff, let's make assert global
      let assert = chai.assert;
    </script>
  </head>

  <body>
    <script>
      function pow(x, n) {
        return x ** n; // using exponentiation operator
        /* function code is to be written, empty now */
      }
    </script>

    <!-- the script with tests (describe, it...) -->
    <!-- <script src="../js/test.js"></script> -->
    <!-- 文件当中的内容如下所示 -->
    <script>
      describe("pow", function () {
        it("raises to n-th power", function () {
          assert.equal(pow(2, 3), 8);
        });
      });
    </script>

    <!-- the element with id="mocha" will contain test results -->
    <div id="mocha"></div>

    <!-- run tests! -->
    <script>
      mocha.run();
    </script>
  </body>
</html>

对象

使用对象可以存储数据,对象中的数据叫做属性,属性名和属性值用冒号分隔,多个属性之间用逗号分隔。

对象的写法

  1. 通用写法
// 字面量写法
let user = {};
// 构造函数写法
let user = new Object();

let user = {
  // 一个对象,对象如果是纯数字会自动正向排序,否则按照添加时间排序。纯数字前加`+`将取消自动排序
  name: "jojo", // 属性名(所有的属性名都会被转换成str类型):属性值
  age: 18,
};
user.city = "shanghai"; // 添加属性
alert(user.name); // 读取属性
delete user.name; // 删除属性
  1. 使用方括号来加强属性名
let user = {};
user["likes birds"] = true;
alert(user["likes birds"]); // true

let key = prompt("你想了解点什么", "Online");
user["user " + key] = true; // user["user Online"] = true;
alert(user["user " + key]);
  1. 若在函数中使用同名形参传递,则在对象处可简写
function makeUser(name, age) {
  return {
    name, // 相等于name:name
    age, // 相等于age:age
  };
}
  1. 特殊 key__proto__这个 key 只能设置是对象的值
let obj = {};
obj.__proto__ = 5; // __proto__ 是一个特殊属性
alert(obj.__proto__); // [object Object]
  1. 文档注释对象标签
/** 用户对象
 * @typedef {object} User
 * @property {number} id - 用户ID
 * @property {string} name - 用户名
 */

/**获取用户信息
 * @param {number} userId - 用户ID
 * @returns {User} - 返回用户对象
 */
function getUserInfo(userId) {
  return {
    id: userId,
    name: "jojo",
  };
}
getUserInfo(123);
  1. 在对象当中创建函数
// 第一种在对象当中创建函数的方法
let user = {
  name : "jojo",
  age : 18,
}
function sayHi(){
  console.log("Hello, world!");
}
user.sayHi = sayHi; // 将sayHi函数添加到user对象当中
user.sayHi(); // 调用执行user对象当中添加的sayHi函数

// 第二种在对象当中创建函数的方法
let user = {
  name = "jojo",
  age = 18,
  sayHi: function(){ // 在对象当中创建函数,并使用一个名称来指向这个函数
    alert("Hello, world!");
  },
  sayHi1(){ // 直接创建一个指向sayHi函数的名称,不能重名
    alert("Hello, world!");
  }
}

对象的in方法

in查询属性是否存在,for... in...循环遍历属性

let obj = { test: NaN, test2: 1 };
alert("test" in obj); // true
for (let key in obj) {
  alert(key); // 得到key值 test test2
  alert(obj[key]); // 得到value值 NaN 1
}

浅拷贝和深拷贝

let user1 = {
  name: "jojo1",
  sizes: {
    height: 182,
    width: 50,
  },
};
let user2 = { name: "jojo2" };

// 引用,使用另一个名称引用同一个对象。
let admin = user1; // admin和user指向同一个对象,修改admin会修改user
admin.name = "peter";
alert(user1.name); // 'peter'

// 合并,将两个对象合并成一个,覆盖相同的属性。
Object.assign(user1, user2); // admin和user指向不同的对象,修改admin不会修改user
alert(user1); // user2不改变,user1变为{ name: 'jojo2', sizes: { height: 182, width: 50 } }

// 浅拷贝
// 方案1
let clone = {};
for (let key in user1) {
  clone[key] = user1[key];
}
clone.name = "jojo3"; // 不影响user1;
clone.sizes.wight = 60; // 影响 user1;
// 方案2
let clone1 = Object.assign({}, user1); // 完全拷贝只对一层有效,其他层级还是引用
clone1.name = "jojo4"; // 不影响user1和clone
clone1.sizes.wight = 60; // 影响 user1和clone

// 多层深拷贝
// 方案1
let deepclone = JSON.parse(JSON.stringify(user1)); // 使用JSON.stringify将对象转换成字符串,再使用JSON.parse将字符串转换成对象
// 方案2
import _ from "lodash";
let deepclone2 = _.cloneDeep(user1); // 使用lodash库

对象的回收

当对象没有任何引用时,会被垃圾回收机制回收.
对象变为不可达状态后被回收,主要是从根开始的不可达。

对象的this方法

当需要在对象内的函数中引用对象中的一些属性时,可以使用 this 关键字来引用对象

let user = {
  name: "jojo",
  age: 18,
  sayHi: function sayHi() {
    alert(this.name); // 使用this来引用对象中的属性,这里的this指向User对象
  },
};
user.sayHi(); // jojo

// 简写
let user = {
  name: "jojo",
  age: 18,
  sayHi() {
    alert(this.name); // 使用this来引用对象中的属性,这里的this指向User对象
  },
};

函数内部都可以使用this关键字来引用一些属性,这个值是计算出来的,不同的对象当中得到的结果不同

function SayHi() {
  // 先定义函数
  console.log(this.name); // 使用this来引用对象中的属性,这个值是计算出来的,不同的对象当中得到的结果不同
}
let User = { name: "jojo" }; // 创建对象
let Admin = { name: "peter" }; // 创建对象
User.func = sayHi; // 将函数添加到对象当中,函数当中的this指向User对象
Admin.func = sayHi; // 将函数添加到对象当中,函数当中的this指向Admin对象
User.func(); // jojo
Admin.func(); // peter
// 在全局当中使用this,则指向window对象,如果使用严格模式则this指向undefined
console.log(this.name); // 这里的this指向window对象(window对象是全局对象)。如果使用严格模式,则this指向undefined

对象的new方法

使用new方法需要先声明构造函数,然后再使用new方法进行实例化。
构造函数起名通常使用大驼峰命名法(UserInfo),构造函数中的this指向实例化的对象。

  1. 第一种构造函数,使用new方法,不推荐使用
function User(name, age) {
  // 声明一个构造函数,
  this.name = name;
  this.age = age;
  this.sayHi = function () {
    console.log("Hi" + this.age + this.name);
  };
}

let user = new User("jj", 18); // 使用new方法进行实例化
let user1 = new User("jj1", 18); // 使用new方法进行实例化
user.sayHi(); // Hi18jj
user1.sayHi(); // Hi18jj1
  1. 第二种构造函数,使用new方法,更好的做法
function User(name, age) {
  this.name = name;
  this.age = age;
}
User.prototype.sayHi = function () {
  console.log("Hi" + this.age + this.name);
};

let user = new User("jj", 18); // 使用new方法进行实例化
user.sayHi(); // Hi18jj
  1. 通过顶级 Object 类型来实例化一个对象,通过对象字面量创建对象。
// Object类来实例对象,相当于创建一次性的构造函数,并直接实例化
let oCar = new Object();
oCar.brand = "Benz";
oCar.number = "00001";
oCar.speed = 200;
oCar.driver = function () {
  console.log(this.brand, this.number, this.speed);
};
console.log(oCar.number);
oCar.driver();
//字面量
let oTom = {
  name: "tom",
  age: "20",
  sing: function () {
    console.log(this.name + this.age);
  },
};
console.log("name" + oTom.name);
oTom.sing();
  1. 第三种构造函数,使用new方法,新语法是第二种的语法糖。
class User {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  sayHi() {
    console.log("Hi" + this.age + this.name);
  }
}

let user = new User("jj", 18); // 使用new方法进行实例化
user.sayHi(); // Hi18jj

对象的?.选择

?.这样的语法,可以选择对象当中可能不存在的属性,但选择的对象是必须要声明的。
?.()这是选择调用一个可能不存在的函数。
?.[]读取一个可能不存在的属性。更为安全的读取。
这些符号也可以用以delete关键字之后,删除一个可能不存在的属性。存在则删除,不存在则不改变。
但是这些符号不能用于写入。

obj?.prop; // 如果obj存在,则返回obj.prop,否则返回undefined
obj?.[prop]; // 如果obj存在,则返回obj[prop],否则返回undefined
obj?.method(); // 如果obj存在,则调用obj.method(),否则返回undefined
delete obj?.prop; // 如果obj存在,则删除obj.prop,否则不改变obj

Symbol类型

  1. Symbol类型和string类型可作为对象属性键。
let user = {
  name: "jojo", // 对象字面量方法,使用string类型作为属性键
  [id]: 1, // 对象字面量方法,使用Symbol类型作为属性键
};
let user1 = {
  name: "jojo",
  id: 1, // 这里是string类型的key,和后续创建的Symbol类型的key重复,但不冲突
};
let id = Symbol("id"); // 将id定义为symbol类型,并给予一个描述叫id
user1[id] = 2; // 使用symbol类型作为属性键
console.log(user1[id]); // 2,使用symbol类型作为属性键
console.log(user1.id); // 1,使用string类型作为属性键
  1. Symbol类型是唯一值,即使两个Symbol类型描述相同,也是不同的值。
let id1 = Symbol("id");
let id2 = Symbol("id");
console.log(id1 == id2); // false,即使描述相同,也是不同的值
  1. Symbol类型不能自动转换成string类型,需要使用toString()进行。
  2. Symbol类型使用Symbol.description显示描述。
let id = Symbol("id");
console.log(id); // error
console.log(id.toString()); // Symbol(id),将symbol类型转换成string类型
console.log(id.description); // id,显示symbol类型的描述
  1. Symbol类型可以创建隐藏属性,防止被意外读取。
  2. Symbol类型不能使用for...in循环遍历。
let user = {
  name: "jojo",
}; // 这是在其他代码当中引用的

let id = Symbol("id");
user[id] = 1;

console.log(user[id]); // 1
console.log(user.id); // undefined,因为id是symbol类型,不能自动转换成string类型,所以不能使用点语法来访问

for (let key in user) {
  console.log(key); // name,因为id是symbol类型,不能使用for...in循环遍历
}
  1. 全局Symbol类型,使用Symbol.for()创建,使用Symbol.keyFor()获取。
// 从全局注册表中读取,按给定的描述返回Symbol。
let id = Symbol.for("idaa"); // 如果该Symbol不存在则创建
let idAgain = Symbol.for("idaa"); // 获取这个全局Symbol
console.log(id == idAgain); // true

consoloe.log(Symbol.keyFor(id)); //idaa,按找给定的Symbol,返回描述

对象的转换

  1. 对象不会自动转换为布尔类型,若主动转换成布尔类型,都是 true
  2. 转为数字类型发生在对象相减或者应用数学函数时,例如,Date 对象相减会得到两个日期之间的差值
  3. 转为字符串通常发生在诸如 alert(obj)或者对象属性键这种输出函数中

使用 hint 进行自定义转换

hint有三种取值StringNumbreDefault

//String取值通常发生在(输出,将对象作为属性键)
alert(obj);
console.log(obj);
Obj[obj] = 123;
//Number取值通常发生在(显示转换,数学运算(除了二元加法),大于/小于比较)
let num = Number(obj);
let x = +obj;
let delta = data1 - data2;
let greater = num1 > num2; // 这里是因为历史原因使用的是Number
//Default取值通常发生在(二元加法,与字符串、数字或Symbol进行==比较)
let total = obj1 + obj2;
if (user == 1){...};

然后根据hint得到的值,就可以自定义转换,调用的函数是[Symbol.toPrimitive](hint).通常调用的顺序是

  1. 尝试调用obj[Symbol.toPrimitive](hint),如果定义了则改变,否则下延。
  2. hintString,尝试调用obj.toString()obj.valueOf(),那个存在调用那个
  3. hintNumberDefault,尝试调用obj.valueOf()obj.toString(),那个存在调用那个
// obj[Symbol.toPrimitive](hint)使用方法
obj[Symbol.toPrimitive](hint) = function(hint){
  // hint取值有三种,String,Number,Default
  // 必须返回一个原始值(string,number,bigin,boolean,symbol,null,undefined)
}
let user = {
  name: "John",
  money: 1000,

  [Symbol.toPrimitive](hint) {
    console.log(`hint: ${hint}`);
    return hint == "string" ? `{name: "${this.name}"}` : this.money; // 根据hint的值,返回不同的值,string返回this.name,number和default返回this.money
  },
}
console.log(user); // hint: string -> {name: "John"}
console.log(+user); // hint: number - > 1000
console.log(user + 500); // hint: default - > 1500
// obj.toString()和obj.valueOf()使用方法
// 如果hint是String,则优先调用toString(),然后再用valueOf()
// 如果hint是Number或Default,则优先调用valueOf(),然后再用toString()
let user = {
  name: "John",
  money: 1000,

  // 对于hint="string"
  toString() { // 相对来说比较全能
    return `{name: "${this.name}"}`;
  }
  // 对于hint="number"或"default"
  valueOf() {
    return this.money;
  }
};
console.log(user); // hint: string -> toString -> {name: "John"}
console.log(+user); // hint: number -> valueOf -> 1000
console.log(user + 500); // hint: default -> valueOf -> 1500

数据类型的使用

在 JS 中原始类型有七种:stringnumberbigintbooleanundefinedsymbolnull
这些原始类型可以被当作对象来做一些方法,但这并不是真正的对象,我们不能给这些原始类型添加属性。

数字类型的使用

  1. 分割数字,使用_进行分割,例如let num = 1_000_000_000;
  2. 科学计数法,使用e进行科学计数法,例如let num = 1e3;// 1e3 = 1000
  3. 二进制、八进制和十六进制,使用0b0o0x进行表示,例如let num = 0b1111;0o377;0xff
  4. 数字进制转换,使用toString(base)进行转换base=2~36,例如let num = 255;num.toString(2) // 1111_1111123456..toString(36); // 2n9c使用两个.调用。
  5. 舍入,使用Math.round()进行四舍五入,使用Math.ceil()向上舍入,使用Math.floor()向下舍入,使用Math.trunc()去掉小数部分,使用Math.toFixed(n)将数字舍入到指定的小数位数,返回字符串。
  6. 是否为正确的数字,使用Number.isFinite()判断是否为有限数字,使用Number.isNaN()判断是否为NaN,使用Number.isInteger()判断是否为整数。
  7. 数字转换,使用parseInt(str,radix)从左向右读取字符串转换为整数直到非数字出现(可设置进制),使用parseFloat(str)从左向右读取字符串转换为浮点数直到除第一个.后的非数字出现,使用Number()将其他类型转换为数字。
  8. 更多内置 Math 对象文档

字符串类型的使用

  1. 字符串的引号,使用单引号、双引号、反引号(模板字符串)。
function myTag(strings, personExp, ageExp) {
  let str0 = strings[0];
  let str1 = strings[1];
  let str2 = strings[2];

  let ageStr = ageExp > 17 ? "adult" : "child";
  return `${str0}${personExp}${str1}${ageStr}${str2}`;
}
const output = myTag`Hello,${person}! You are ${age}.`;
let age = +prompt("age", 18);
let person = prompt("person", "jojo");

console.log(output); // Hello,jojo! You are adult.
  1. 特殊符号,使用\进行转义,例如\n换行,\t制表符,\b退格符,\r回车符,\f换页符,\v垂直制表符,\\反斜杠,\'单引号,\"双引号,\x十六进制,\u Unicode。
  2. 字符串的长度,使用str.length获取字符串长度。
  3. 访问字符,使用str[i]获取字符串第i个字符,使用str.charAt(i)获取字符串第i个字符,使用str.charCodeAt(i)获取字符串第i个字符的 Unicode 编码。这里不能单独赋值(字符串不可变)
  4. 字符串大小写转换,使用str.toUpperCase()将字符串转换为大写,使用str.toLowerCase()将字符串转换为小写。
  5. 字符串查找
// str.indexOf(substr,pos)从左向右和str.lastIndexOf(substr,pos)从右向左,找到一次就返回。
let str = "Hello, world!";
str.indexOf("Hello"); // 0
if (~str.indexOf("world")) {
  // ~-1 = 0
  // ~0 = -1
  // ~1 = -2
}
// str.includes(substr,pos)从左向右、str.startsWith(substr,pos)从右向左、str.endsWith(substr,pos)找到一次就返回true。
  1. 字符串截取
    • str.slice(start,end),start为开始位置,end为结束位置,startend可以为负数,start为负数表示从后往前数,end为负数(或空)表示截取到字符串末尾,start大于end则返回空字符串。
    • str.substring(start,end),start为开始位置,end为结束位置,startend不为负数,start大于end则调换startend位置。
    • str.substr(start,length),start为开始位置,length为截取长度,start可为负数表示从后往前数。
  2. 字符串的 UTF-16 编码,使用str.codePointAt(pos)获取字符串第pos个字符的 UTF-16 编码,使用String.fromCodePoint(code)将 UTF-16 编码转换为字符串。

数组操作方法

  1. 声明数组
let lA = new Array(1, 2, 3);

let lA2 = [1, 2, 3, "asd"];

let lA3 = [ // 数组可以嵌套,可存储任意类型
  [1, 2, 3],
  ["a", "b", "c"],
  {name:"jojo"}, // console.log(lA3[2].name); // jojo
  function (){console.log("asd")}, // lA3[3](); // asd
];

let lA2[2] = 4; // 修改数组元素 [1,2,4,"asd"]
let lA[3] = 4; // 数组添加元素 [1,2,3,4]
console.log(lA.length); // 数组长度 4,自动更细,可写(arr.length=0清空数组)
console.log(lA2); // 显示整个数组 [1,2,4,"asd"]
console.log(lA.at(-1)); // 获取数组最后一个元素 4
  1. 队列/栈操作(unshift,shift,push,pop)
let lA = [1, 2, 3, 4];

// 队列操作
lA.unshift(0); //从最前面进行插入
console.log(lA); // [0, 1, 2, 3, 4]
console.log(lA.shift()); //删除第一个
console.log(lA); // [1, 2, 3, 4]

// 栈操作
lA.push(5); //从最后进行插入
console.log(lA); // [1, 2, 3, 4, 5]
console.log(lA.pop()); //删除最后一个
console.log(lA); // [1, 2, 3, 4]
  1. 数组的遍历(for,for in,for of,forEach,reduce,reduceRight)
let lA = [1, 2, 3, 4, 5, 6, 7, 8, 9];

//for循环遍历
for (let i = 0; i < lA.length; i++) {
  console.log(lA[i]);
}

//for循环变形
for (let n in lA) {
  console.log(n);
}

//for循环变形
for (let n of lA) {
  console.log(n);
}

// arr.forEach(function (element, index, array) {...}); // arr指代数组,function指对数组元素进行操作的函数,element指数组元素,index指数组元素下标,array指数组
let lA = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
lA.forEach(function (element, index, array) {
  // 只是创建一个可遍历数组的函数,不对数组内容修改
  console.log(`${element} is at index ${index} in ${array}`);
});
// 0 is at index 0 in 0,1,2,3,4,5,6,7,8,9
// 1 is at index 1 in 0,1,2,3,4,5,6,7,8,9
// 2 is at index 2 in 0,1,2,3,4,5,6,7,8,9

// arr.reduce(function (accumulator, currentValue, currentIndex, array) {...}, initialValue); // arr指代数组,function指对数组元素进行操作的函数,accumulator指累加器,currentValue指数组元素,currentIndex指数组元素下标,array指数组,initialValue指累加器初始值
let lA = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let lA2 = lA.reduce(function (accumulator, currentValue, currentIndex, array) {
  // 1. accumulator=0, currentValue=0, currentIndex=0, array=0,1,2,3,4,5,6,7,8,9
  // 2. accumulator=0, currentValue=1, currentIndex=1, array=0,1,2,3,4,5,6,7,8,9
  // 3. accumulator=1, currentValue=2, currentIndex=2, array=0,1,2,3,4,5,6,7,8,9
  return accumulator + currentValue;
}, 0);
  1. 数组的toString方法
let lA = [1, 2, 3, 4, 5, 6, 7, 8, 9];

console.log(lA.toString()); // 1,2,3,4,5,6,7,8,9
console.log(lA + 1); // 1,2,3,4,5,6,7,8,91
  1. 根据下标添加和删除元素(splice,slice,concat)
// arr.splice(start,num,element1,...,elementN);
// arr指代数组,star指代要对数组开始操作的元素下标,num指要对多少元素进行操作,elementN指修改后或被添加的元素
let lA = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
// 删除操作
lA.splice(3, 3); // 删除,从下标3开始删除三个元素
console.log(lA); // [0, 1, 2, 6, 7, 8, 9]
// 插入操作
lA.splice(3, 0, 33); // 插入,从下标3插入一个33,元素数据向后移动
console.log(lA); // [0, 1, 2, 33, 6, 7, 8, 9]
// 插入多个数据
lA.splice(3, 0, 44, 55, 66); // 插入多个,原本元素向后移动
console.log(lA); // [0, 1, 2, 44, 55, 66, 33, 6, 7, 8, 9]
// 替换操作
lA.splice(3, 1, 333); // 替换,从下标3替换一个元素,其他元素不变
console.log(lA); // [0, 1, 2, 333, 55, 66, 33, 6, 7, 8, 9]
// 替换多个数据
lA.splice(3, 2, 333, 444, 555); // 替换多个,可一部分替换一部分插入
console.log(lA); // [0, 1, 2, 333, 444, 555, 66, 33, 6, 7, 8, 9]

// arr.slice(start,end); // arr指代数组,star指代要对数组开始操作的元素下标,end指要对数组结束操作的元素下标,返回一个新数组
let lA = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let lA2 = lA.slice(3, 6); // 截取,从下标3开始截取到下标6,不包含下标6,返回一个新数组
console.log(lA2); // [3, 4, 5]

// arr.concat(element1,...,elementN); // arr指代数组,elementN指要添加的元素,返回一个新数组
let lA = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let lA2 = lA.concat(10, 11, 12); // 拼接,将10,11,12添加到lA数组后面,返回一个新数组
console.log(lA2); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
  1. 数组的搜索(indexOf,lastIndexOf,includes,find,findIndex,findLastIndex,filter)
// arr.indexOf(item, from); // arr指代数组,item指要搜索的元素,from指从哪个下标开始搜索,返回元素下标,没有找到返回-1
let lA = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log(lA.indexOf(3)); // 3

// arr.includes(item, from); // arr指代数组,item指要搜索的元素,from指从哪个下标开始搜索,返回布尔值,可以正确处理NaN数据。
let lA = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log(lA.includes(3)); // true

// arr.find(function(element, index, array){}); // arr指代数组,function指对数组元素进行操作的函数,element指数组元素,index指数组元素下标,array指数组,返回找到的元素,没有找到返回undefined
let lA = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let lA2 = lA.find(function (element, index, array) {
  return element > 5;
});
console.log(lA2); // 6
let lA3 = lA.find((item) => item > 5);
console.log(lA3); // 6

// arr.filter(function(element, index, array){}); // arr指代数组,function指对数组元素进行操作的函数,element指数组元素,index指数组元素下标,array指数组,返回一个新数组,新数组为原数组中满足条件的元素
let lA = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let lA2 = lA.filter(function (element, index, array) {
  return element > 5;
});
console.log(lA2); // [6, 7, 8, 9]
let lA3 = lA.filter((item) => item > 5);
console.log(lA3); // [6, 7, 8, 9]
  1. 转换数组(map,sort,reverse,split,join)
//arr.map(function(element, index, array){}); // arr指代数组,function指对数组元素进行操作的函数,element指数组元素,index指数组元素下标,array指数组,返回一个新数组,新数组为原数组中满足条件的元素
let lA = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let lA2 = lA.map(function (element, index, array) {
  return element * 2;
});
console.log(lA2); // [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
let lA3 = lA.map((item) => item * 2);
console.log(lA3); // [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

// arr.sort(function(a, b){}); // arr指代数组,function指对数组元素进行操作的函数,a,b指数组元素,计算得到的值用于排序,正值则不变,负值则交换。返回一个新数组但通常省略,原数组会改变。
let lA = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let lA2 = lA.sort(function (a, b) {
  return a - b;
});
console.log(lA2); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
let lA3 = lA.sort((a, b) => a - b);
console.log(lA3); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

// arr.reverse(); // arr指代数组,返回一个新数组,原数组会改变。
let lA = [0, 1, 2, 3, 10, 5, 6, 7, 8, 9];
let lA2 = lA.reverse();
console.log(lA2); // [9, 8, 7, 6, 5, 10, 3, 2, 1, 0]

// str.split(separator, limit); // str指代字符串,separator指分隔符,limit指返回的数组最大长度,返回一个数组
let str = "a,b,c,d,e,f,g,h,i,j";
let arr = str.split(",", 5);
console.log(arr); // ["a", "b", "c", "d", "e"]

// arr.join(separator); // arr指代数组,separator指分隔符,返回一个字符串
let arr = ["a", "b", "c", "d", "e"];
let str = arr.join(",");
console.log(str); // "a,b,c,d,e"
  1. 数组的判断(isArray,instanceof)
let lA = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log(Array.isArray(lA)); // true
console.log(lA instanceof Array); // true

特别注意

上述操作大多数都可以在末尾添加thisArg,可以将定义的函数的this值改变为thisArg的值,例如:

let army = {
  minAge: 18,
  maxAge: 27,
  canJoin: function (user) {
    return user.age >= this.minAge && user.age <= this.maxAge;
  },
};
let users = [{ age: 16 }, { age: 20 }, { age: 23 }, { age: 30 }];
let armyCanJoin = users.filter(army.canJoin, army);
console.log(armyCanJoin.length); // 2
console.log(armyCanJoin); // [{ age: 20 }, { age: 23 }]
console.log(armyCanJoin[0].age); // 20

可迭代对象

可迭代对象,都具有Symbol.iterator属性,因此在对象当中添加Symbol.iterator属性,就可以让对象成为可迭代对象。
对于for of循环启动时都需要调用Symbol.iterator方法。这个方法必须返回一个next方法的对象。

对于字符串、数组、Map、Set、arguments 对象、NodeList 对象这些都是内置可迭代对象,不需要自己添加Symbol.iterator

let range = { // 创建一个对象
  from: 1,
  to: 5,
};
// 在对象当中添加Symbol.iteration属性,让对象成为可迭代对象。
// for of 方法会调用这个对象的 Symbol.iterator 方法
range[Symbol.iterator] = function () { // 返回迭代器对象(iterator object)
  // for of 仅与下面的迭代器对象一起工作,需要提供一下个值
  return { // 这里重新创建了一个对象,一个可以迭代的对象
    current: this.from,
    last: this.to,
    // for of 的每轮循环迭代中都会调用 next 方法
    next() { // next 方法是在Symbol.iterator方法中的,range对象自身没有。
      if (this.current <= this.last) {
        // 返回{done:..., value:...}格式的对象
        return { done: false, value: this.current++ };
      } else {
        return { done: true };
      }
    },
  };
};
// 迭代器对象和与其进行迭代的对象是分开的。
for (let num of range) {
  console.log(num); // 1 2 3 4 5
}

/////////第二种实现方法
// 将range自身作为迭代器,将next方法定义在range对象当中。
let range = {
  from: 1,
  to: 5,
  [Symbol.iterator]() {
    this.current = this.from;
    return this; // 这里直接返回自身即可,因为自身是一个可迭代的对象
  }
  next() {
    if (this.current <= this.to) {
      return { done: false, value: this.current++ };
    }else{
      return { done: true };
    }
  }
};
for (let num of range) {
  console.log(num); // 1 2 3 4 5
}

对于可迭代对象,可以直接显示调用Symbole.iterator方法

let str = "hello";
// 等同于for (let char of str) console.log(char);
let iterator = str[Symbol.iterator]();
while (true) {
  let result = iterator.next();
  if (result.done) break;
  console.log(result.value);
} // h e l l o

可迭代(iterable)对象和类数组(array-like)对象
可迭代对象:上述自定义或内置的具有Symbole.iterator方法的对象。(上述都是)
类数组对象:具有length属性和索引属性的对象。(下述举例)

let arraylike = {
  0: "Hello",
  1: "World",
  length: 2,
};
let arr = Array.from(arraylike);
console.log(arr); // ["Hello", "World"]
console.log(arr.pop()); // World

当然对于可迭代对象也是可以使用Array.from方法将其转换为数组。

let range = {
  from: 1,
  to: 5,

  [Symbol.iterator]() {
    this.current = this.from;
    return this;
  },

  next() {
    if (this.current <= this.to) {
      return { done: false, value: this.current++ };
    } else {
      return { done: true };
    }
  },
};
let arr = Array.from(range);
console.log(arr); // [1, 2, 3, 4, 5]

并且在使用Array.from()时,可以传入第二个参数(一个函数或表达式),用于对每个元素进行处理,返回一个新数组。

let str = "𝒳😂";
let arr = Array.from(str, (num) => num + num);
console.log(arr); // ['𝒳𝒳', '😂😂']
console.log(arr[0]); // 𝒳𝒳
console.log(arr.length); // 2

注意

  1. 可迭代对象需要包含 Symbol.iterator 属性,并且该属性需要是一个函数,该函数返回一个可迭代对象,该对象需要包含 next 方法。
  2. 第二种创建方法在出现多个 for of 循环且异步时,会出现共享迭代状态的情况
// 1. 定义“自己当迭代器”的 range 对象(状态 current 存在自身)
let range = {
  from: 1,
  to: 5,
  current: 1, // 共享状态:所有遍历共用这个 current

  // Symbol.iterator 返回 this(自己当迭代器)
  [Symbol.iterator]() {
    console.log(`[迭代器启动] 重置 current 为 ${this.from}`);
    this.current = this.from; // 每次启动遍历,都会重置 current!
    return this;
  },

  // next() 操作共享的 current 属性
  next() {
    if (this.current <= this.to) {
      const value = this.current;
      console.log(
        `[next 执行] current = ${
          this.current
        } → 返回 ${value},current 自增为 ${this.current + 1}`
      );
      this.current++;
      return { done: false, value };
    }
    console.log(
      `[next 执行] current = ${this.current}(超过 to=${this.to})→ 遍历结束`
    );
    return { done: true };
  },
};

// 2. 非阻塞暂停函数(用 Promise 包裹 setTimeout,不阻塞主线程)
function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

// 3. 第一个遍历:遍历到 2 时暂停,释放主线程
async function firstTraversal() {
  console.log("\n=== 第一个遍历开始 ===");
  // for...of 会自动调用 range[Symbol.iterator]() 和 next()
  for await (let num of range) {
    // 用 for await...of 支持异步暂停
    console.log("第一个遍历拿到值:", num);

    // 当第一个遍历拿到 2 时,暂停 100ms(非阻塞,释放主线程)
    if (num === 2) {
      console.log("第一个遍历:拿到 2,暂停 100ms,释放主线程...");
      await sleep(100); // 关键:非阻塞暂停,主线程可执行其他任务
      console.log("第一个遍历:暂停结束,继续执行");
    }
  }
  console.log("=== 第一个遍历结束 ===");
}

// 4. 第二个遍历:在第一个遍历暂停时启动
function secondTraversal() {
  console.log("\n=== 第二个遍历开始 ===");
  for (let num of range) {
    // 普通 for...of(同步遍历)
    console.log("第二个遍历拿到值:", num);
  }
  console.log("=== 第二个遍历结束 ===");
}

// 5. 启动测试:先跑第一个遍历,在它暂停时跑第二个遍历
firstTraversal();
// 第一个遍历启动后,主线程会在“拿到 2 并 await sleep(100)”时释放
// 此时设置一个 50ms 的定时器,确保第二个遍历在第一个遍历暂停期间启动
setTimeout(secondTraversal, 50);

映射(Map)与集合(Set)

Map 是一个带键的数据项集合,
Set 是一个唯一值的集合。

let map = new Map(); // 创建一个 Map 对象
map.set("name", "张三"); // 添加一个键值对,字符串键
map.set(1, "李四"); //数字键
map.set(true, "王五"); // 布尔值键
map.set({ name: "John" }, 123); // 对象键,对象键会被转换为字符串 "[object Object]"
map.set(null, "null").set(undefined, "undefined"); // null 键 和 undefined 键,可以链式调用
console.log(map.get("name")); // 获取键为"name"的值
console.log(map.has("name")); // 判断是否存在键为"name"的键值对
map.delete("name"); // 删除键为"name"的键值对
console.log(map.size); // 获取键值对的数量
map.clear(); // 清空所有键值对

// Map 的遍历
map.keys(); // 获取所有键的迭代器
map.values(); // 获取所有值的迭代器
map.entries(); // 获取所有键值对的迭代器,在for of循环中默认调用
map.forEach((value, key) => {
  console.log(key, value); // 遍历所有键值对
});

// Map 和对象相互转换
let obj = {
  name: "张三",
  age: 18,
};
let map = new Map(Object.entries(obj)); // 对象 => Map
console.log(map); // Map { 'name' => '张三', 'age' => 18 }
let map = new Map([
  ["1", "value1"],
  [1, "value2"],
  [true, "value3"],
]);
let prices = Object.fromEntries(map); // Map => 对象
console.log(prices); // { 1: 'value2', true: 'value3' }
let set = new Set(); // 创建一个 Set 对象
let john = { name: "John" };
let pete = { name: "Pete" };
let mary = { name: "John" };
set.add(john); // 添加一个对象
set.add(pete);
set.add(mary);
set.add(pete); // 再次添加同一个对象
set.add(john);
console.log(set.size); // 3 获取集合的大小,重复不计入
set.delete(mary); // 删除一个对象
set.has(pete); // 判断集合中是否存在某个对象
set.clear(); // 清空集合

// Set 的遍历
set.keys(); // 获取所有键的迭代器
set.values(); // 与 set.keys() 作用相同,这是为了兼容 Map
set.entries(); // 遍历并返回一个包含所有的实体 [value, value] 的可迭代对象,它的存在也是为了兼容 Map
set.forEach((value, key) => {
  console.log(key, value); // 遍历所有键值对
});

弱映射(WeakMap)和弱集合(WeakSet)

在上述 Map 和 Set 的基础上,取消了对键值对引用的计数,即不会阻止垃圾回收器回收键值对,因此键值对只能是对象,不能是原始类型。
当对象被操作为 null 或对象被删除时,WeakMap 和 WeakSet 中的键值对也会被删除。
这样的设计使得 WeakMap 和 WeakSet 更适合用于存储那些不需要手动删除的键值对。
WeakMap 仅支持get set has delete方法,WeakSet 仅支持add delete has方法。WeakMap 的键必须为对象,WeakSet 的值必须为对象。

let weakMap = new WeakMap();
let weakSet = new WeakSet();

let john = { name: "John" };
weakMap.set(john, "..."); // john添加到WeakMap
john = null; // 覆盖引用,john 被从内存中删除了!

john = { name: "John" }; // 重新创建一个对象
weakSet.add(john); // john添加到WeakSet
john = null; // 覆盖引用,john 被从内存中删除了!

对 Object 使用keys,values,entries方法

let user = {
  name: "John",
  age: 30,
};
console.log(Object.keys(user)); // ["name", "age"]
console.log(Object.values(user)); // ["John", 30]
console.log(Object.entries(user)); // [["name", "John"], ["age", 30]]

解构赋值

let [name, age] = ["John", 30]; // 数组解构
console.log(name, age); // John 30

let [firstName, , title] = ["Julius", "Caesar", "Consul", "Republic"]; // 跳过第二个元素
console.log(title); // Consul

let [a, b, c] = "abc"; // 字符串解构
console.log(a, b, c); // a b c

let [guest, admin] = ["Jane", "Pete"]; // 交换变量值
[guest, admin] = [admin, guest];
console.log(guest, admin); // Pete Jane

let [name1, name2, ...rest] = ["Julius", "Caesar", "Consul", "Republic"]; // rest 是包含从第三项开始的其余数组项的数组
console.log(rest, rest.length); // ["Consul", "Republic"] 2

let [name = "Guest", surname = "Anonymous"] = ["Julius"]; // 默认值
console.log(name); // Julius(来自数组的值)
console.log(surname); // Anonymous(默认值被使用了)

let { height, width, title } = { title: "Menu", height: 200, width: 100 }; // 对象解构, 顺序不重要
console.log(name, age); // John 30

let { name: firstName, age: years } = { name: "John", age: 30 }; // 重命名
console.log(firstName, years); // John 30

let { name: firstName, age: years = 18 } = { name: "John" }; // 默认值
console.log(firstName, years); // John 18

let { name, ...restProps } = { name: "John", age: 30, job: "teacher" }; // restProps 是一个对象,包含除 name 属性之外的所有属性
console.log(restProps); // { age: 30, job: 'teacher' }

// 注意,在js当中{}代表的是代码块,而不是对象,所以解构赋值时,如果{}不在[]内,则会被解析为代码块,而不是对象。
// 所以在解构赋值时,需要将{}放在[]内,表示对象解构。
({ name, age } = { name: "John", age: 30 }); // 使用括号来消除歧义

let options = {
  size: {
    width: 100,
    height: 200,
  },
  items: ["Cake", "Donut"],
  extra: true,
};

// 为了清晰起见,解构赋值语句被写成多行的形式
let {
  size: {
    // 把 size 赋值到这里
    width,
    height,
  },
  items: [item1, item2], // 把 items 赋值到这里
  title = "Menu", // 在对象中不存在(使用默认值)
} = options; // 这里得到的数据是 width、height、item1、item2 和具有默认值的 title 变量。
console.log(width); // 100

通常用于函数的参数部分,将形参变为一个对象,传入参数时只需要传入一个对象即可,函数内部则可得到解构完成的实参。

function showMenu({
  title = "Menu",
  width = 200,
  height = 100,
  items = [],
} = {}) {
  // 这里使用多层默认值,防止传入空对象时,解构赋值失败
  console.log(`Title: ${title}, W${wight}px, H${height}px`);
  console.log(items);
}
let options = { title: "My menu", items: ["Item1", "Item2"] };
showMenu(options); // Title: My menu, W200px, H100px ['Item1', 'Item2']
showMenu(); // Title: Menu, W200px, H100px []

日期和时间

let now = new Date();
console.log(now); // 星期 月份 日期 年份 时:分:秒 时区 时区描述
// new Date(milliseconds) 时间戳 1234567890123
// new Date(datestring) 格式字符串 2022-01-01
// new Date(year, month, date, hours, minutes, seconds, ms) 多参数
// 在创建时会自动矫正输入错误
let wrongTime = new Date(2013, 0, 32);
console.log(wrongTime); // 2013-02-01

// 可以单独方位年月日时分秒
console.log(now.getFullYear()); // 年
console.log(now.getMonth()); // 月 0-11
console.log(now.getDate()); // 日
console.log(now.getDay()); // 星期 0-6 日-六
console.log(now.getHours()); // 时
console.log(now.getMinutes()); // 分
console.log(now.getSeconds()); // 秒
console.log(now.getMilliseconds()); // 毫秒
console.log(now.getTime()); // 时间戳 console.log(+now);console.log(Date.now());相同
console.log(now.getTimezoneOffset()); // 时区偏移

// 设置年月日时分秒
now.setFullYear(2022);
now.setMonth(0);
now.setDate(1);
now.setHours(0);
now.setMinutes(0);
now.setSeconds(0);
now.setMilliseconds(0);

// 时间加减,不需要考虑闰年的情况,直接加减即可
now.setDate(now.getDate() + 1); // 加一天
now.setMonth(now.getMonth() - 1); // 减一月

// 字符串转换时间戳和日期格式
let ms = Date.parse("2012-01-26T13:51:50.417-07:00");
// YYYY-MM-DDTHH:mm:ss.sss+08:00(东八区)
console.log(ms); // 时间戳
console.log(new Date(ms)); // 日期格式

JSON 方法,toJSON

为了弥补 toString()方法的麻烦性,JSON 提供了 JSON.stringify(对象转换为 json) 和 JSON.parse(json 转换为对象) 方法。

JSON。stringify 支持Object{...}``Arrays[...]``strings``numbers``boolean values``null
不支持functions、存储undefined的属性、Symbol类型的键和值,遇到后直接忽略。
不能有循环引用。可以通过指定格式属性来解决。

// JSON.stringify(value, reolacer, spaces);
let student = {
  name: "John",
  age: 30,
  isAdmin: false,
  courses: ["html", "css", "js"],
  spouse: null,
};

let json = JSON.stringify(student);
console.log(typeof json); // string
console.log(json);
/* JSON 编码的对象:
{
  "name": "John",
  "age": 30,
  "isAdmin": false,
  "courses": ["html", "css", "js"],
  "spouse": null
}
*/

// toJSON 方法,可以自定义 JSON.stringify() 的行为
let room = {
  number: 23,
  toJSON() {
    return this.number;
  },
};
let meetup = {
  title: "Conference",
  room,
};
console.log(JSON.stringify(room)); // 23
console.log(JSON.stringify(meetup)); // {"title":"Conference","room":23}

// JSON.parse(value, reviver)
let numbers = "[1,2,3,4]";
numbers = JSON.parse(numbers);
console.log(numbers, typeof numbers); // [1,2,3,4] object
let userData =
  '{ "name": "John", "age": 35, "isAdmin": false, "friends": [0,1,2,3] }';

let user = JSON.parse(userData);
console.log(user.friends[1]); // 1

let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}';

let meetup = JSON.parse(str, function (key, value) {
  if (key == "date") return new Date(value);
  return value;
});

console.log(meetup.date.getDate()); // 30

认识 jQuery

jQuery 是对 JS 的封装,简化 JS 的编程,作用效果和 JS 一样
当前 jQuery 兼容现在主流浏览器,加快开发效率

jQuery 的使用

jQuery 官网,一般使用旧版本,兼容性更高

jQuery 的引用

  • jQuery 下载到本地并引用,使用<script src=''><\script>标签进行引用
  • 联网引用 jQuery,许多用户在访问其他站点时,已经从其他地方加载过 jQuery。当访问网站时使用别人的文件,会从缓存中加载 jQuery,这样可以减少加载时间。
<!DOCTYPE html>
<html>
  <head>
    <title>Document</title>
    // 引用百度的jQuery
    <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
    <script>
      // jQuery的ready函数做为入口
      $(document).ready(function () {
        // 在jQuery中如果获取标签对象时,前缀要用一个$
        let $d = $("#d1");
        alert("jquery" + $d);
      });
      //简写法
      $(function () {
        let $d = $("#d1");
        alert("简写" + $d);
      });
    </script>
  </head>
  <body>
    <div class="" id="d1">asd</div>
  </body>
</html>

jQuery 的选择器

快速选择标签元素,获取标签元素,选择规则和 css 一样。

选择器类型

  • 标签选择器 $('li')
  • 类选择器 $('.myClass')
  • id 选择器 $('#myId')
  • 层级选择器 $('#ul1 li span')选择 id 为 ul1 下的所有 li 标签下的 span 标签
  • 属性选择器 $('input[name=first]')选择 name 属性等于 first 的 input 标签

判断是否选择成功用到.length,如果 lenght 大于 0 表示选择成功。

$(function () {
  result = $("div").length;
  alert(result);
});
<!DOCTYPE html>
<html>
  <head>
    <title>Document</title>
    <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
    <script>
      $(function () {
        //标签选择器
        let $mk1 = $("h1");
        $mk1.css({ background: "red" });
        //类选择器
        let $mk2 = $(".mp");
        $mk2.css({ color: "blue" });
        // id选择器
        let $mk3 = $("#d1");
        $mk3.css({ "font-size": "30px" });
        // 层级选择器
        let $mk4 = $("div a");
        $mk4.css({ background: "red" });
        // 属性选择器
        let $mk5 = $("input[value=fgh]");
        $mk5.css({ background: "blue" });
        let $mk5 = $("input[type=password]");
        $mk5.css({ background: "red" });
      });
    </script>
  </head>
  <body>
    <h1>qwe</h1>
    <p class="mp">asd</p>
    <div class="" id="d1">zxc</div>
    <div><a href="www.baiadu.com">rty</a></div>
    <input type="text" name="" id="" value="fgh" />
    <input type="password" name="" id="" value="vbn" />
  </body>
</html>

选择器过滤

选择器过滤就是在选择的标签集合中过滤出自己需要的标签

  • has(选择器名称)方法,选取包含指定选择器的标签
  • eq(索引)方法,表示选取指定索引的标签
<!DOCTYPE html>
<html>
  <head>
    <title>Document</title>
    <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
    <script>
      $(function () {
        let $divs = $("div");
        // alert($divs.length);
        console.log($divs);
        // has 过滤
        let $mk1 = $divs.has("p");
        $mk1.css({ background: "red" });
        // last 过滤
        let $mk2 = $divs.last();
        $mk2.css({ background: "blue" });
        // first 过滤
        let $mk3 = $divs.first();
        $mk3.css({ color: "blue" });
        // eq 过滤
        let $mk4 = $divs.eq(1);
        $mk4.css({ color: "pink" });
      });
    </script>
  </head>
  <body>
    <div><p>qwe</p></div>
    <div>asd</div>
    <div>zxc</div>
  </body>
</html>

选择器转移

以某一个选择的标签为参照,获取转移后的标签

  • $('#box').prev();表示选择 id 是 box 元素的上一个的同级元素
  • $('#box').prevAll();表示选择 id 是 box 元素的上面所有同级元素
  • $('#box').next();表示选择 id 是 box 元素的下一个的同级元素
  • $('#box').nextAll();表示选择 id 是 box 元素的下面所有同级元素
  • $('#box').parent();表示选择 id 是 box 元素的父元素
  • $('#box').children();表示选择 id 是 box 元素的所有子元素
  • $('#box').siblings();表示选择 id 是 box 元素的其他同级元素
  • $('#box').find(".myClass");表示选择 id 是 box 元素的 class 为 myClass 的元素

设置标签内容

jQuery 中 html 方法可以获取和设置标签的 html 内容

<!DOCTYPE html>
<html>
  <head>
    <title>Document</title>
    <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
    <script>
      $(function () {
        let $div1 = $("#div1");
        // alert($div1)
        // 获取div当中的内容
        let $mkp = $div1.html();
        alert($mkp);
        // 修改div当中的内容
        $div1.html('<a href="www.baidu.com">baidu</a>');
        // 添加div当中的内容
        $div1.append("<hr>");
        $div1.append('<a href="www.baidu.com">百度</a>');
      });
    </script>
  </head>
  <body>
    <div id="div1"><p>hello</p></div>
  </body>
</html>

设置标签元素属性

<!DOCTYPE html>
<html>
  <head>
    <title>Document</title>
    <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
    <script>
      $(function () {
        // 获取对象
        let $inp = $('[type="text"]');
        let $a = $("a");
        // 通过prop()方法获取属性
        $inp.prop({ value: "kkkk", class: "incla" });
        $a.prop({ href: "www.biadu.com" });
        // val方法可以快速获取value值
        alert($inp.val());
        $inp.val("ssss");
      });
    </script>
  </head>
  <body>
    <input type="text" name="username" value="xxx" class="cin" id="inid" />
    <a>百度</a>
  </body>
</html>

jQuery 事件绑定

  • click()鼠标单击
  • blur()元素失去焦点
  • focus()元素获得焦点
  • mouseover()鼠标进入(进入子元素也触发)
  • mouseout()鼠标离开(离开子元素也触发)
  • ready()DOM 加载完成
<!DOCTYPE html>
<html>
  <head>
    <title>Document</title>
    <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
    <script>
      $(function () {
        let $div = $("div");
        let $btn = $("#inb");
        let $text = $("#in1");
        let $lis = $("ol li");
        // 给按钮绑定对象
        $btn.click(function () {
          alert("点到按钮了");
        });
        // 给列表绑定对象
        $lis.click(function () {
          // 点击后更改颜色,this指代当前的内容
          $(this).css({ color: "red" });
          // 弹出点击内容
          alert($(this).html());
        });
        // 获取焦点
        $text.focus(function () {
          $(this).css({ background: "blue" });
          $div.css({ background: "yellow" });
        });
        // 失去焦点
        $text.blur(function () {
          $(this).css({ background: "white" });
          $div.css({ background: "white" });
        });
        // 鼠标移入
        $div.mouseover(function () {
          $(this).css({ width: "20px", height: "20px", background: "red" });
        });
        // 鼠标移出
        $div.mouseout(function () {
          $(this).css({
            width: "100px",
            height: "100px",
            background: "yellow",
          });
        });
      });
    </script>
  </head>
  <body>
    <div>aa</div>
    <hr />
    <input type="text" id="in1" />
    <hr />
    <input type="button" id="inb" value="ok" />
    <hr />
    <ol>
      <li>第一</li>
      <li>第二</li>
      <li>第三</li>
    </ol>
  </body>
</html>

jQuery 事件代理

事件代理就是利用事件冒泡原理(事件会向父级一级一级传递),把事件加到父级身上,通过判断事件来源执行相对应的子元素操作,事件代理可以极大的减少事件绑定次数,提高性能;还可以让新加入的子元素也用有相同操作

<!DOCTYPE html>
<html>
  <head>
    <title>Document</title>
    <style>
      #fd {
        width: 300px;
        height: 300px;
        background: red;
      }
      #cd {
        width: 100px;
        height: 100px;
        background: blue;
      }
    </style>

    <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
    <!-- 冒泡原理 -->
    <!-- <script>
        $(function(){
            let $fd = $('#fd');
            let $cd = $('div').eq(1);
            
            $cd.click(function(){
                alert("子标签被点击");
            });
            $fd.click(function(){
                alert('父标签被点击了')
            })
        });
    </script> -->
    <!-- 代理父元素代理子元素 -->
    <script>
      $(function () {
        let $ol = $("ol");
        $ol.delegate("li", "click", function () {
          $(this).css({ background: "red" });
          alert($(this).html());
        });
      });
    </script>
  </head>
  <body>
    <div id="fd">
      qwe
      <div id="cd">asd</div>
    </div>
    <hr />
    <ol>
      <li>第一个</li>
      <li>第二个</li>
      <li>第三个</li>
    </ol>
  </body>
</html>

json

最外面一定要有容器进行包裹,内部只能用双引号
json 是一种数据格式,使用 js 的字符串进行描述

对象格式

{
  "name": "tom",
  "age": 18
}

数组格式

["tom", 18, "programmer"]

常出现的样式

{
    "name":"jack",
    "age":29,
    "hobby":["reading","travel","photography"]
    "school":{
        "name":"Merrimack College",
        "location":"North Andover, MA"
    }
}

使用 js 进行解析 json

// 创建一个js的str类型以json数组为样式
let sjson1 = '[1,2,3,4,5,"a"]';
console.log(typeof sjson1);
// 数据解析成js数组
let jArray = JSON.parse(sjson1);
console.log(jArray);

// 第二种形式
let sjson2 = '{"name":"tom","age":21}';
let tom = JSON.parse(sjson2);
console.log(tom);
console.log(tom.name, tom.age);

ajax

ajax 是一个前后端配合的技术,可以让 JS 发送异步的 http 请求,与后台进行数据的获取,ajax 最大的优点就是实现局部的刷新,ajax 可以发送 http 请求,当获取到后台的数据后就可以更新页面显示数据,实现局部刷新,前端页面想同后端服务器进行数据交互就要使用 ajax

ajax 的使用

jQuery 将 ajax 封装成一个$.ajax()方法,使用方法就可以直接执行 ajax 请求。
基本样式

$.ajax({
  // 1.url请求地址,可以写绝对地址,也可以写相对地址(可以自动补齐地址)
  url: "",
  // 2.type:请求方式,默认是"GET"请求方式,常用的还有"POST"
  type: "",
  // 3.dataType:设置返回的数据格式,常用的是"json"格式,还有"xml","html","text"
  dataType: "",
  // 4.data:设置发送给服务器的数据,没有参数可以不设置
  data: {},
  // 5.success:设置请求成功后的回调函数
  success: function (response) {
    console.log(response);
  },
  // 6.error:设置请求失败后的回调参数
  error: function () {
    alert("请求失败,请稍后再试");
  },
  // 7.async:设置是否异步进行,默认值为"true",表示异步,一般不用设置
  async: true,
});

具体使用方法

|--ajax
|--data.json
|--page.html
{ "name": "苹果笔记本15", "price": 55000 }
<!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>Document</title>
    <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
    <script>
      // 这个方法传入的是一个js对象
      $.ajax({
        url: "data.json",
        type: "GET",
        dataType: "JSON",
        // data:{page:1,count:20},
        success: function (data) {
          console.log(data);
          let $ln = $("#ln").next();
          $ln.html(data.name);
          let $lp = $("#lp").next();
          $lp.html(data.price);
        },
        error: function () {
          alert("请求失败");
        },
        async: true,
      });
    </script>
  </head>
  <body>
    <label id="ln">商品名:</label><label>a</label>
    <hr />
    <label id="lp">价格:</label><label>0</label>
  </body>
</html>

ajax 简写

get

$.get(url,success(data,status,xhr),dataType).error(func)

$.get(
  "data.json",
  { page: 1, count: 20 },
  function (data) {
    console.log(data);
    let $ln = $("#ln").next();
    $ln.html(data.name);
    let $lp = $("#lp").next();
    $lp.html(data.price);
  },
  "json"
).error(function () {
  alert("请求失败");
});

post

$.post(url,success(data,status,xhr),dataType).error(func)

$.post("data.json", { page: 1, count: 20 }, function (data) {
  console.log(data);
  let $ln = $("#ln").next();
  $ln.html(data.name);
  let $lp = $("#lp").next();
  $lp.html(data.price);
}).error(function () {
  alert("请求失败");
});

Vue

Vue.js 使用的是 MVVM 框架,有容易上手的 API
Vue.js 是一个数据驱动的 Web 界面的库
Vue.js 是一套构建用户界面的渐进式框架
核心是一个响应的数据绑定系统
Vue.js 的官方文档

使用 Vue.js

vue 的导入

<!--导入Vue.js库进去-->
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- 生产环境版本,优化了尺寸和速度 -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>

一般模板

<!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" />
    <!-- 开发环境版本,包含了有帮助的命令行警告 -->
    <!-- 导入vue -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>Document</title>
  </head>
  <body>
    <!-- 定义一个标签,并给标签一个id值 -->
    <div id="app"></div>
  </body>
  <!-- 创建vue实例 -->
  <script type="text/javascript">
    let app = new Vue({
      el: "#app",
      data: {
        message: "hello",
      },
    });
  </script>
</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" />
    <!-- 开发环境版本,包含了有帮助的命令行警告 -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <!-- vue使用声明式渲染 -->
      <a href="#">{{ message }}</a>
      <hr />
      <span>{{ hello }}</span>
      <hr />

      <!-- 使用v-bind给属性绑定数据 -->
      <a v-bind:href="url">点击</a>
      <hr />

      <!-- 将v-bind进行简写 -->
      <span :title="showmessage">鼠标放在这里</span>
      <hr />

      <!-- 使用v-if进行判断 -->
      <a href="#" v-if="isLogin">欢迎</a>
      <hr />
      <!-- v-if与v-else及v-else-if必须挨着否则无效 -->
      <a href="#" v-if="level === 1">青铜</a>
      <a href="#" v-else-if="level === 2">白银</a>
      <a href="#" v-else>王者</a>
      <hr />

      <!-- v-if和v-show的区别,使用f12打开控制台查看区别 -->
      <span v-if="seen"> v-if </span>
      <span v-show="seen"> v-shiw </span>
      <hr />

      <!-- v-for使用语法 -->
      <ul>
        <!-- 解包这个顺序不能改变 -->
        <li v-for="(item,index) in items">{{index+1}} {{item}}</li>
      </ul>
      <hr />
      <!-- 对对象进行遍历 -->
      <ul>
        <!-- 解包这个顺序不能改变 -->
        <li v-for="(value,key) in object">{{key}} : {{value}}</li>
      </ul>
      <hr />

      <!-- 使用v-on进行绑定动作 -->
      <button v-on:click="login">登录</button>
      <!-- 将v-on进行简写 -->
      <a href="" @click="register">注册</a>
      <button @click="add(counter)">+1</button>
      <hr />

      <!-- 绑定输入(双向绑定) -->
      <table>
        <tr>
          <td>用户名</td>
          <td><input type="text" name="username" v-model="username" /></td>
        </tr>
        <tr>
          <td>密码</td>
          <td>
            <input type="password" name="password1" v-model="password1" />
          </td>
        </tr>
        <tr>
          <td>确认密码</td>
          <td>
            <input type="password" name="password2" v-model="password2" />
          </td>
        </tr>
        <tr>
          <td>性别</td>
          <td>
            男<input type="radio" name="sex" value="boy" v-model="sex" />
            女<input type="radio" name="sex" value="girl" v-model="sex" />
          </td>
        </tr>
        <tr>
          <td>爱好</td>
          <td>
            足球<input
              type="checkbox"
              name="like"
              value="足球"
              v-model="like"
            />
            篮球<input
              type="checkbox"
              name="like"
              value="篮球"
              v-model="like"
            />
            乒乓球<input
              type="checkbox"
              name="like"
              value="乒乓球"
              v-model="like"
            />
          </td>
        </tr>
      </table>
      <button v-on:click="register2">注册</button>
    </div>
  </body>
  <script type="text/javascript">
    //创建一个app下的vue实例
    let app = new Vue({
      //挂载到id=app的标签上,el内写接管标签
      el: "#app",
      // 使用模板语言进行填充,数据进行双向绑定
      // data内写绑定数据
      data: {
        // 内容message赋值一个hello vue
        message: "hello vue",
        hello: "你好",
        // 将url发给绑定的位置
        url: "https://www.baidu.com",
        // 创建一个显示时间的函数赋值给showmessage
        showmessage: "当前时间是" + new Date().toLocaleString(),
        isLogin: true,
        level: 1,
        seen: false,
        items: [1, 2, 3, 4, 5],
        object: {
          title: "How to do lists in Vue",
          author: "Jane Doe",
          publishedAt: "2016-04-10",
        },
        counter: 1,
        username: "",
        password1: "",
        password2: "",
        sex: "",
        like: [],
      },
      // methods内写方法
      methods: {
        login: function () {
          alert("成功");
        },
        register: function () {
          alert("注册");
        },
        add: function (counter) {
          // this表示当前vue,通过this.level访问data中的变量
          this.level += counter;
        },
        register2: function () {
          alert(
            this.username +
              this.password1 +
              this.password2 +
              this.sex +
              this.like
          );
        },
      },
    });
  </script>
</html>

todolist 示例

<!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" />
    <!-- 开发环境版本,包含了有帮助的命令行警告 -->
    <!-- 导入vue -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>Document</title>
  </head>
  <body>
    <!-- 定义标签 -->
    <div id="app">
      <input type="text" name="todoitem" v-model="newitem" /><button
        @click="add"
      >
        添加
      </button>
      <hr />
      <ul>
        <li v-for="(item,index) in items">
          <a href="javascript:;" @click="up(index)">↑</a>
          {{ item }}
          <a href="#" @click="deleteitem(index)">删除</a>
          <a href="javascript:;" @click="down(index)">↓</a>
        </li>
      </ul>
    </div>
  </body>
  <!-- 创建vue实例 -->
  <script type="text/javascript">
    let app = new Vue({
      el: "#app",
      data: {
        items: ["asdasd", "asdasdqwe"],
        newitem: "",
      },
      methods: {
        add: function () {
          this.items.push(this.newitem);
          this.newitem = " ";
        },
        deleteitem: function (index) {
          this.items.splice(index, 1);
        },
        up: function (index) {
          // 获取当前元素
          current = this.items[index];
          // 把当前元素删除
          this.items.splice(index, 1);
          // 再添加,添加索引加1
          this.items.splice(index - 1, 0, current);
        },
        down: function (index) {
          // 获取当前元素
          current = this.items[index];
          // 把当前元素删除
          this.items.splice(index, 1);
          // 再添加,添加索引加1
          this.items.splice(index + 1, 0, current);
        },
      },
    });
  </script>
</html>

vue 实例生命周期

image

<!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" />
    <!-- 开发环境版本,包含了有帮助的命令行警告 -->
    <!-- 导入vue -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>Document</title>
  </head>
  <body>
    <!-- 定义标签 -->
    <div id="app">
      <span> {{message}} </span>
    </div>
  </body>
  <!-- 创建vue实例 -->
  <script type="text/javascript">
    let app = new Vue({
      el: "#app",
      data: {
        message: "hello",
      },
      // 生命周器的钩子(函数),没有在methods内
      // app对象实例化之前
      beforeCreate: function () {
        console.log("beforeCreate");
      },
      // app对象实例化后
      created: function () {
        console.log("created");
      },
      // app将作用标签之前
      beforeMount: function () {
        console.log("beforeMounted");
      },
      // app将作用标签之后
      mounted: function () {
        console.log("mounted");
      },
      // 数据或者属性更新之前
      berforeDestroy() {
        console.log("berforeDestroy");
      },
      // 数据或属性更新之后
      destroyed: function () {
        console.log("destroyed");
      },
    });
  </script>
</html>

发送 ajax 请求

vue 本身不能发送 ajax 请求,可以使用 axios 发送,是 vue2.0 官方推荐的
cdn

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

前端代码

<!DOCTYPE html>
<html lang="en">
  <head>
    <!-- 开发环境版本,包含了有帮助的命令行警告 -->
    <!-- 导入vue -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <!-- 导入axios -->
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <title>Document</title>
  </head>
  <body>
    <!-- 定义一个标签,并给标签一个id值 -->
    <div id="app">
      <!-- vue的大胡子模板语法和django/flask的模板语法冲突 -->
      <span>[[ message ]]</span>
      <button @click="login">登录</button>
      <hr />
      [[ username ]]

      <hr />
      <button @click="login2">post</button>
    </div>
  </body>
  <!-- 创建vue实例 -->
  <script type="text/javascript">
    let app = new Vue({
      el: "#app",
      // 修改vue语法的分隔符
      delimiters: ["[[", "]]"],
      data: {
        message: "hello",
        username: "",
      },
      methods: {
        // 这里发送ajax请求
        login: function () {
          alert("登录成功");
          // 定义一个url地址
          let url = "http://127.0.0.1:8000/rece?username=itcast&password=1234";
          // then catch 用 => 箭头函数 this指向问题
          axios
            .get(url)
            .then((response) => {
              // 返回的数据responser(响应)-->response.data(数据)-->response.data.info(ajax返回的字典)-->username
              console.log(response.data.info.username);
              this.username = response.data.info.username;
            })
            .catch((error) => {
              console.log(error);
            });
        },
        login2: function () {
          // axios.post().then().catch() then成功的回调,catach失败的回调
          let url = "http://127.0.0.1:8000/rece/";
          axios
            .post(url, {
              username: "李四",
              password: "123456",
            })
            .then((response) => {
              console.log(response.data.info.username);
              this.username = response.data.info.username;
            })
            .catch((error) => {
              console.log(response.data.info.username);
            });
        },
      },
    });
  </script>
</html>

后端参考 python 的 django 部分文档 view 内主要代码

import json
from django.shortcuts import render
from django.views import View
from django.http import JsonResponse
# Create your views here.
class LoginView(View):

def get(self,request):
return render(request,'login.html')

def post(self,request):
pass

class ReceiveView(View):

def get(self,request):
# 1。接收参数
data = request.GET
username = data.get('username')
password = data.get('password')
return JsonResponse({'info':{'username':username}})

def post(self,request):
data = json.loads(request.body.decode())
username = data.get('username')
password = data.get('password')
return JsonResponse({'info':{'username':username}})

urls 内主要代码

主路由
from django.contrib import admin
from django.urls import path, include, re_path

urlpatterns = [
path('admin/', admin.site.urls),
re_path(r'^',include('book.urls')),
]

分路由
from django.urls import path, re_path
from book.views import LoginView,ReceiveView

urlpatterns = [
re_path(r'^login/$',LoginView.as_view()),
re_path(r'^rece/$',ReceiveView.as_view()),
]

canvas

认识 canvas

可以使用 canvas 进行网页背景设计和图表设计
官方文档 mdn

一个简单的 canvas

<bod>
<!--
    id:标识元素的唯一性
    width:画布的宽度
    height:画布的高度
-->
    <canvas id="c1" width="600" heigth="400">
    当前浏览器不支持canvas请下载最新版的浏览器
    <a herf="https://www.google.cn/intl/zh-CN/chrome">下载新版浏览器</a>
    </canvas>

    <script>
        // 1.找到画布
        let c1 = document.getElementById('c1');
        // 判断浏览器是否支持canvas
        if(!c1.getContext){
            console.log("当前浏览器不支持canvas请下载最新版的浏览器")
        }
        // 2.获取画笔,上下文对象
        let ctx = c1.getContext('2d');
        // 绘制图形
        // 填充矩形:fillRect(位置x,位置y,宽度,高度)
        ctx.fillRect(100,200,300,300);
    </script>

</body>

canvas 绘制基本图形

栅格

image

canvas 的宽高和 css 设置的宽高

css 相当于对图片的放大缩小
canvas 是指定图片的大小
修改 css 对 canvas 的图片进行拉伸或压缩,导致图片变形

矩形绘制填充模式

<bod>
<!--
    id:标识元素的唯一性
    width:画布的宽度
    height:画布的高度
-->
    <canvas id="c1" width="600" heigth="400">
    当前浏览器不支持canvas请下载最新版的浏览器
    <a herf="https://www.google.cn/intl/zh-CN/chrome">下载新版浏览器</a>
    </canvas>

    <script>
        // 1.找到画布
        let c1 = document.getElementById('c1');
        // 判断浏览器是否支持canvas
        if(!c1.getContext){
            console.log("当前浏览器不支持canvas请下载最新版的浏览器")
        }
        // 2.获取画笔,上下文对象
        let ctx = c1.getContext('2d');
        // 绘制图形
        // 填充矩形:fillRect(位置x,位置y,宽度,高度)
        ctx.fillRect(100,200,300,300);
        // 填充矩形拆开写法:ctx.rect(100,200,300,300);ctx.fill();
        // 单写ctx.rect()不会显示,
        ctx.rect(100,200,300,300);
        // 填充后才显示
        ctx.fill();
    </script>

</body>

矩形绘制路径模式

<bod>
<!--
    id:标识元素的唯一性
    width:画布的宽度
    height:画布的高度
-->
    <canvas id="c1" width="600" heigth="400">
    当前浏览器不支持canvas请下载最新版的浏览器
    <a herf="https://www.google.cn/intl/zh-CN/chrome">下载新版浏览器</a>
    </canvas>

    <script>
        // 1.找到画布
        let c1 = document.getElementById('c1');
        // 判断浏览器是否支持canvas
        if(!c1.getContext){
            console.log("当前浏览器不支持canvas请下载最新版的浏览器")
        }
        // 2.获取画笔,上下文对象
        let ctx = c1.getContext('2d');
        // 绘制图形
        // 路径绘制矩形:strockeRect(位置x,位置y,宽度,高度)
        ctx.strokeRect(100,100,200,100);
        // 路径绘制矩形拆开写法:ctx.rect(100,200,300,300);ctx.stroke();
        // 单写ctx.rect()不会显示,
        ctx.rect(100,200,300,300);
        // 填充后才显示
        ctx.stroke();
    </script>

</body>

矩形绘制清除模式

<bod>
<!--
    id:标识元素的唯一性
    width:画布的宽度
    height:画布的高度
-->
    <canvas id="c1" width="600" heigth="400">
    当前浏览器不支持canvas请下载最新版的浏览器
    <a herf="https://www.google.cn/intl/zh-CN/chrome">下载新版浏览器</a>
    </canvas>

    <script>
        // 1.找到画布
        let c1 = document.getElementById('c1');
        // 判断浏览器是否支持canvas
        if(!c1.getContext){
            console.log("当前浏览器不支持canvas请下载最新版的浏览器")
        }
        // 2.获取画笔,上下文对象
        let ctx = c1.getContext('2d');
        // 绘制图形
        // 填充矩形:fillRect(位置x,位置y,宽度,高度)
        ctx.fillRect(100,200,300,300);
        // 清除填充内容
        ctx.clearRect(0,0,c1.clientWidth,c1.clientHeight);
        let height = 0;
        // 逐渐清除(每10ms清除一个height)
        let t1 = setInterval(()=>{
            height++;
            ctx.clearRect(0,0,c1.clientWidth,height);
            // 判断清除函数
            if(height > c1.clientHeight){
                // 清除t1这个函数
                clearInterval(t1);
            }
        },10)
    </script>

</body>

画笔抬起

<bod>
<!--
    id:标识元素的唯一性
    width:画布的宽度
    height:画布的高度
-->
    <canvas id="c1" width="600" heigth="400">
    当前浏览器不支持canvas请下载最新版的浏览器
    <a herf="https://www.google.cn/intl/zh-CN/chrome">下载新版浏览器</a>
    </canvas>

    <script>
        // 1.找到画布
        let c1 = document.getElementById('c1');
        // 判断浏览器是否支持canvas
        if(!c1.getContext){
            console.log("当前浏览器不支持canvas请下载最新版的浏览器")
        }
        // 2.获取画笔,上下文对象
        let ctx = c1.getContext('2d');
        // 绘制图形
        // 填充矩形:fillRect(位置x,位置y,宽度,高度)
        // 下笔
        ctx.bgeinPath();
        ctx.rect(100,100,200,100);
        ctx.stroke();
        // 抬笔
        ctx.closePath();

        // 下笔
        ctx.bgeinPath();
        ctx.rect(200,150,200,100);
        ctx.fill();
        // 抬笔
        ctx.closePath();
    </script>

</body>

绘制圆,圆弧

<bod>
<!--
    id:标识元素的唯一性
    width:画布的宽度
    height:画布的高度
-->
    <canvas id="c1" width="600" heigth="400">
    当前浏览器不支持canvas请下载最新版的浏览器
    <a herf="https://www.google.cn/intl/zh-CN/chrome">下载新版浏览器</a>
    </canvas>

    <script>
        // 1.找到画布
        let c1 = document.getElementById('c1');
        // 判断浏览器是否支持canvas
        if(!c1.getContext){
            console.log("当前浏览器不支持canvas请下载最新版的浏览器")
        }
        // 2.获取画笔,上下文对象
        let ctx = c1.getContext('2d');
        // 绘制图形
        // 绘制圆弧:ctx.arc(圆心x,圆心y,半径r,开始的角度,结束的角度,顺时针false /逆时针true)
        ctx.arc(300,200,50,0,Math.PI*2);
        ctx.fill();

        // 起始点
        ctx.moveTo(300,200);
        // 绘制圆弧:ctx.arcTo(点1x,点1y,点2x,点2y,半径r)
        ctx.arcTo(300,250,250,250,25);
        ctx.strock();

    </script>

</body>

使用 moveTo 移动点

<bod>
<!--
    id:标识元素的唯一性
    width:画布的宽度
    height:画布的高度
-->
    <canvas id="c1" width="600" heigth="400">
    当前浏览器不支持canvas请下载最新版的浏览器
    <a herf="https://www.google.cn/intl/zh-CN/chrome">下载新版浏览器</a>
    </canvas>

    <script>
        // 1.找到画布
        let c1 = document.getElementById('c1');
        // 判断浏览器是否支持canvas
        if(!c1.getContext){
            console.log("当前浏览器不支持canvas请下载最新版的浏览器")
        }
        // 2.获取画笔,上下文对象
        let ctx = c1.getContext('2d');
        // 绘制图形
        ctx.beginPath();
        // 绘制圆脸:
        ctx.arc(75,75,50,0,Math.PI *2);
        // 移动画笔
        ctx.moveTo(110,75)
        // 绘制嘴巴
        ctx.arc(75,75,35,0,Math.PI);
        // 移动画笔
        ctx.moveTo(65,65)
        // 绘制左眼
        ctx.arc(60,65,5,0,Math.PI *2);
        // 移动画笔
        ctx.moveTo(95,65)
        //绘制右眼
        ctx.arc(90,65,5,0,Math.PI *2);
        // 填充线段
        ctx.stroke();
        ctx.closePath();
    </script>

</body>

绘制折线

<bod>
<!--
    id:标识元素的唯一性
    width:画布的宽度
    height:画布的高度
-->
    <canvas id="c1" width="600" heigth="400">
    当前浏览器不支持canvas请下载最新版的浏览器
    <a herf="https://www.google.cn/intl/zh-CN/chrome">下载新版浏览器</a>
    </canvas>

    <script>
        // 1.找到画布
        let c1 = document.getElementById('c1');
        // 判断浏览器是否支持canvas
        if(!c1.getContext){
            console.log("当前浏览器不支持canvas请下载最新版的浏览器")
        }
        // 2.获取画笔,上下文对象
        let ctx = c1.getContext('2d');
        // 绘制图形
        ctx.beginPath();
        // 绘制折线
        // 开始位置在300,200
        ctx.moveTo(300,200);
        // 结束路径在350,250
        ctx.lineTo(350,250);


        ctx.strock();
        ctx.closePath();
    </script>

</body>

贝塞尔曲线

贝塞尔曲线由三个点控制的曲线,这三个点中一个起点,一个终点,还有一个控制点,控制点可以再增加其他点进行控制

<bod>
<!--
    id:标识元素的唯一性
    width:画布的宽度
    height:画布的高度
-->
    <canvas id="c1" width="600" heigth="400">
    当前浏览器不支持canvas请下载最新版的浏览器
    <a herf="https://www.google.cn/intl/zh-CN/chrome">下载新版浏览器</a>
    </canvas>

    <script>
        // 1.找到画布
        let c1 = document.getElementById('c1');
        // 判断浏览器是否支持canvas
        if(!c1.getContext){
            console.log("当前浏览器不支持canvas请下载最新版的浏览器")
        }
        // 2.获取画笔,上下文对象
        let ctx = c1.getContext('2d');
        // 绘制图形
        // 贝塞尔曲线:quadraticCurveTo(控制点x,控制点y,终点x,终点y);
        // 冒泡聊天框
        // 起始点
        ctx.moveTo(200,300);
        // 控制点,终止点
        ctx.quadraticCurveTo(150,300,150,200);
        ctx.quadraticCurveTo(100,100,300,100);
        ctx.quadraticCurveTo(450,100,450,200);
        ctx.quadraticCurveTo(450,300,250,300);
        ctx.quadraticCurveTo(250,350,150,300);
        ctx.quadraticCurveTo(200,350,200,300);
        ctx.strock();

         // 贝塞尔曲线:ctx.bezierCurveTo(控制点1x,控制点1y,控制点2x,控制点2y,终点x,终点y);
        // 爱心
        // 起始点
        ctx.moveTo(200,300);
        // 控制点1,控制点2,终止点
        ctx.bezierCurveTo(350,150,400,200,300,300);
        ctx.bezierCurveTo(350,150,400,200,300,300);
        ctx.strock();


    </script>

</body>

path2D-封装路径

<bod>
<!--
    id:标识元素的唯一性
    width:画布的宽度
    height:画布的高度
-->
    <canvas id="c1" width="600" heigth="400">
    当前浏览器不支持canvas请下载最新版的浏览器
    <a herf="https://www.google.cn/intl/zh-CN/chrome">下载新版浏览器</a>
    </canvas>

    <script>
        // 1.找到画布
        let c1 = document.getElementById('c1');
        // 判断浏览器是否支持canvas
        if(!c1.getContext){
            console.log("当前浏览器不支持canvas请下载最新版的浏览器")
        }
        // 2.获取画笔,上下文对象
        let ctx = c1.getContext('2d');

        // 创建路径实例
        let heartPath = new.Path2D();
        // 路径起始点
        heartPath.moveTo(200,300);
        // 路径使用贝塞尔曲线绘制
        heartPath.bezierCurveTo(350,150,400,200,300,300);
        heartPath.bezierCurveTo(350,150,400,200,300,300);
        // 绘制制定的路径
        cxt.strock(heartPath);

        // 创建一条折线
        // 使用svg的写法
        let polyline = new Path2D("M10 10 h 80 v 80 h -80 z");
        ctx.strocke(polyline);
    </script>

</body>

颜色修改

<bod>
<!--
    id:标识元素的唯一性
    width:画布的宽度
    height:画布的高度
-->
    <canvas id="c1" width="600" heigth="400">
    当前浏览器不支持canvas请下载最新版的浏览器
    <a herf="https://www.google.cn/intl/zh-CN/chrome">下载新版浏览器</a>
    </canvas>

    <script>
        // 1.找到画布
        let c1 = document.getElementById('c1');
        // 判断浏览器是否支持canvas
        if(!c1.getContext){
            console.log("当前浏览器不支持canvas请下载最新版的浏览器")
        }
        // 2.获取画笔,上下文对象
        let ctx = c1.getContext('2d');
        ctx.strokeStyle = "red";

        ctx.strokeRect(100,100,200,100);
        ctx.strokeStyle = "#ff00ff";
        ctx.rect(100,200,300,300);
        ctx.stroke();



    </script>

</body>

封装绘制的物体

<bod>
<!--
    id:标识元素的唯一性
    width:画布的宽度
    height:画布的高度
-->
    <canvas id="c1" width="600" heigth="400">
    当前浏览器不支持canvas请下载最新版的浏览器
    <a herf="https://www.google.cn/intl/zh-CN/chrome">下载新版浏览器</a>
    </canvas>

    <script>
        // 1.找到画布
        let c1 = document.getElementById('c1');
        // 判断浏览器是否支持canvas
        if(!c1.getContext){
            console.log("当前浏览器不支持canvas请下载最新版的浏览器")
        }
        // 2.获取画笔,上下文对象
        let ctx = c1.getContext('2d');

        // 封装
        class Heart{
            constructor(x,y){
                this.x = x;
                this.y = y;
                this.color = "red";
                this.isIn = false;
                this.eventMapList = {
                    hover:[],
                    leave:[],
                };
                // 监听鼠标
                c1.onmousemove = (e)=>{
                    // 获取鼠标位置
                    let x = e.offsetX;
                    let x = e.offsetX;
                    this.isIn = ctx.isPointInPath(
                    this.hearPath,x,y);
                    is(this.isIn){
                        this.eventMapList.hover.forEach((item)=>{item();});
                    }else{
                        this.eventMapList.hover.forEach((item)=>{item();});
                    }
                };
            }
            onHover(fn){
                this.eventMapList.hover.push(fn);
            }
            onLeace(fn){
                this.eventMapList.leave.push(fn);
            }
            // 修改设置x,y
            setPostition(x,y){
                this.x = x;
                this.y = y;
            }
            draw(){
                this.heartPath = new path2D();
                this.heartPath.moveTo(this.x,this.y);
                heartPath.bezierCurveTo(
                    this.x+50,
                    this.y-50,
                    this.x+100,
                    this.y,
                    this.x,
                    this.y+50
                );
                heartPath.bezierCurveTo(
                    this.x-100,
                    this.y,
                    this.x-50,
                    this.y-50,
                    this.x,
                    this,y
                );
                ctx.save();
                ctx.fillStyle = this.color;
                ctx.fill(this.heartpath);
                ctx.restore();
            }
        }
        // 创建画图实例
        let heart = new Heart(100,100);
        heart.onHover(()=>{
            heart.color = "blue";
        })
        heart.onLeave(()=>{
            heart.color = "red";
        })
        // 改动更新
        function render(){
            ctx.clearRect(0,0,c1.width,c1.heigth);
            heart.draw();
            // 不定时异步刷新
            requestAnimationFrame(render);
        }
        // 调用主函数
        render();

    </script>

</body>
posted @ 2025-03-27 13:20  *--_-  阅读(78)  评论(0)    收藏  举报