JavaScript验证表单内容概述： JavaScript 可用来在数据被送往服务器前对 HTML 表单中的这些输入数据进行验证。表单数据经常需要使用 JavaScript 来验证其正确性：验证表单数据是否为空？验证输入是否是一个正确的email地址？验证日期是否输入正确？验证表单输入内容是否为数字型？作用： 保证输入的数据符合要求、减轻服务器的压力实现思路：获得表单元素值使用JavaScript的一些方法对数据进行判断当表单提交时，触发事件，对获取的数据进行验证，并对不合法数据进行提示案例演示基础（后续案例均在此案例基础之上完成，已省略样式及部分页面）： 青鸟技术栈-注册页面 青鸟技术栈-注册账号 已有账号？立即登录 昵称 邮箱 没有邮箱？立即申请 密码 再次密码 性别 男 女 生日 2022年 1月 22日 爱好 读书 编程 运动 头像 所在地 河北省 秦皇岛市 北戴河区 手机号 可通过该手机号码快速找回密码 中国大陆地区以外手机号码 点击这里 获取短信验证码 我已阅读并同意相关服务条款和隐私政策 《服务协议》 《隐私政策》 参与满意度问卷调查 案例下载：青鸟技术栈-登录页面-表单验证基础页面.zip案例显示效果：表单选择器jQuery表单选择器语法描述:input匹配所有input、textarea、select和button元素:text匹配所有单行文本框:password匹配所有密码框:radio匹配所有单项按钮:checkbox匹配所有复选框:image匹配所有图像域:file匹配所有文件域:reset匹配所有重置按钮:button匹配所有按钮:submit匹配所有提交按钮jQuery表单属性过滤选择器语法描述:enabled匹配所有可用元素:disabled匹配所有不可用元素:checked匹配所有被选中元素（复选框、单项按钮、select中的option）:selected匹配所有选中的option元素表单校验与提示特效使用jQuery脚本实现如下要求：当用户点击“登录”按钮后，在HTML表单提交到服务器端之前对内容格式进行如下验证：判断邮箱是否为空。如果为空，则弹出警告框，并给出提示判断邮箱是否为空、邮箱格式是否正确（需要包含‘@’、‘.’,且‘.’的位置在‘@’之后，‘@’不能作为第一个字符），如不满足条件，则弹出警告框，并给出提示判断密码是否为空，如果为空，则弹出警告框，并给出提示演示案例：$(function () { $("form").submit(function () { let email = $("#email").val(); //获取到邮箱的值 let password = $("#password").val(); //获取到密码的值 //1.判断邮箱是否为空 if (email == "") { alert("邮箱不能为空"); return false; } let emialIndex = email.indexOf('@'); let dotIndex = email.indexOf('.'); //2.使用indexOf()方法判断邮箱是否包含“@”和“.”字符 if (emialIndex == -1 || dotIndex.indexOf(".") == -1) { alert("邮箱格式不正确，必须包含@和."); return false; } //使用indexOf()方法判断邮箱的首字符是否为“@”字符 if (emialIndex == 0) { alert("邮箱格式不正确，不能以@开头"); return false; } if (emialIndex > dotIndex) { alert("邮箱格式不正确，@不能在.之后"); return false; } //3.判断密码是否为空 if (password == "") { alert("密码不能为空"); return false; } //4.判断密码长度大于或等于8个字符 if (password.length < 8) { alert("密码必须大于或等于8个字符"); return false; } return true; }) })[scode type="red" size="simple"]📢弊端： 每当用户点击登录按钮后，系统都将以警告框的形式弹出对应的信息。这种操作存在如下问题：用户填写完全部表单并提交时才进行校验，错误提示不及时使用警告框提示错误，用户体验不友好🤔如何修正?在文本框失去焦点时触发验证在文本框的后面添加页面元素，用于存放错误提示信息使用jQuery的html( )方法，动态设置提示信息[/scode]演示案例：在对应的文本框区域之后添加相应的错误提示标签 邮箱 当验证失败时，修改错误提示标签的显示文本，并修改样式$(function () { //代码块1：为表单元素绑定blur事件处理函数 $("#email").blur(CheckEmail); $("#password").blur(CheckPassword); //代码块2：点击登录按钮提交表单 $("form").submit(function () { //邮箱和密码验证成功后提交表单 if (CheckEmail() && CheckPassword()) { return true; } return false; }) //代码块3：邮箱校验+密码校验 //邮箱校验 function CheckEmail() { var email = $("#email").val(); //获取到邮箱的值 var email_msg = $("#email_msg"); //提示信息元素 email_msg.html(""); //判断邮箱是否为空 if (email == "") { email_msg.html("*邮箱不能为空"); return false; } let emialIndex = email.indexOf('@'); let dotIndex = email.indexOf('.'); //使用indexOf()方法判断邮箱是否包含“@”和“.”符号 if (emialIndex == -1 || dotIndex == -1) { email_msg.html("*邮箱格式不正确，必须包含@和."); return false; } //使用indexOf()方法判断邮箱的首字符是否为“@”符号 if (emialIndex == 0) { email_msg.html("*邮箱格式不正确，不能以@开头"); return false; } if (emialIndex > dotIndex) { email_msg.html("*邮箱格式不正确，@不能在.之后"); return false; } return true; } //密码校验 function CheckPassword() { var password = $("#password").val(); //获取到密码的值 var password_msg = $("#password_msg"); //提示信息元素 password_msg.html(""); //判断密码是否为空 if (password == "") { password_msg.html("*密码不能为空"); return false; } //判断密码长度大于或等于8个字符 if (password.length < 8) { password_msg.html("*密码必须大于或等于8个字符"); return false; } return true; } })正则表达式验证初识正则表达式概述： 正则表达式(regular expression)描述了一种字符串匹配的模式（pattern），可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。定义正则表达式语法：let reg = /表达式规则/附加参数表达式规则：符号描述/…/代表一个模式的开始和结束^匹配字符串的开始$匹配字符串的结束\s任何空白字符\S任何非空白字符\d匹配一个数字字符，等价于 [0-9]\D除了数字之外的任何字符，等价于 [^0-9]\w匹配一个数字、下划线或字母字符，等价于 [A-Za-z0-9_]\W任何非单字字符，等价于 [^a-zA-z0-9_].除了换行符之外的任意字符{n}匹配前一项n次{n,}匹配前一项n次，或者多次{n,m}匹配前一项至少n次，但是不能超过m次*匹配前一项0次或多次，等价于 {0,}+匹配前一项1次或多次，等价于 {1,}?匹配前一项0次或1次，即前一项是可选的，等价于 {0,1}附加参数：附加参数含义描述iignore - 不区分大小写将匹配设置为不区分大小写，搜索时不区分大小写: A 和 a 没有区别。gglobal - 全局匹配查找所有的匹配项。mmulti line - 多行匹配使边界字符^ 和$ 匹配每一行的开头和结尾，记住是多行，而不是整个字符串的开头和结尾。s特殊字符圆点. 中包含换行符\n默认情况下的圆点. 是匹配除换行符\n 之外的任何字符，加上s 修饰符之后, . 中包含换行符 \n。特殊符号：[]：定义匹配的字符串，如 [a-zA-Z]表示匹配字母 a-z和 A-Z{}：用于匹配长度，如 \d{4}表示匹配四个数字()：是为了提取匹配到的字符串，表达式中有几个 ()就有几个相应的匹配字符串RegExp对象的方法：方法描述exec(String)：string检索一个字符串是否匹配某个模式。如果字符串中含有匹配的文本，则返回匹配的结果；否则返回nulltest(String)：boolean检索一个字符串是否匹配某个模式。如果字符串中含有匹配的文本，则返回true；否则返回falseString对象的方法：方法描述match(RegExp)找到一个或多个正则表达式的匹配search(RegExp)返回字符串中第一个与正则表达式相匹配的子串的起始位置replace(RegExp/String, replacement)替换字符串中与正则表达式匹配的子串替换为replacementsplit( separator, n )以separator为边界，将字符串分割为字符串数组，n为限制输出数组的个数演示案例：//i 修饰符为不区分大小写匹配，实例如下： var str="Google runoob taobao RUNoob"; var n1=str.match(/runoob/g); // 区分大小写 var n2=str.match(/runoob/gi); // 不区分大小写 //g 修饰符可以查找字符串中所有的匹配项： str="Google runoob taobao runoob"; n1=str.match(/runoob/); // 查找第一次匹配项 n2=str.match(/runoob/g); // 查找所有匹配项 //m 修饰符可以使 ^ 和 $ 匹配一段文本中每行的开始和结束位置。 str="runoobgoogle\ntaobao\nrunoobweibo"; n1=str.match(/^runoob/g); // 匹配一个 n2=str.match(/^runoob/gm); // 多行匹配 //默认情况下的圆点 . 是 匹配除换行符 \n 之外的任何字符，加上 s 之后, . 中包含换行符 \n。 str="google\nrunoob\ntaobao"; n1=str.match(/google./); // 没有使用 s，无法匹配\n n2=str.match(/runoob./s); // 使用 s，匹配\n //RegExp对象 var str = "amy like pink"; var reg = /pink/; var result1 = reg.test(str); // true var result2 = reg.exec(str); // pink //String对象 var str = "amy like pink"; var reg = /pink/; var result1 = str.match(reg); // pink var result2 = str.search(reg); // 9 var result3 = str.replace(reg, "red"); // amy like red var str1 = "red,pink,blue,white"; var result4 = str1.split(',', 2); // [ "red", "pink" ]正则表达式的应用常见的正则表达式// 身份证号, 支持1/2代(15位/18位数字) let reg_id = /(^\d{8}(0\d|10|11|12)([0-2]\d|30|31)\d{3}$)|(^\d{6}(18|19|20)\d{2}(0\d|10|11|12)([0-2]\d|30|31)\d{3}(\d|X|x)$)/; // 中国手机号(严谨), 根据工信部2019年最新公布的手机号段 let reg_phone = /^(?:(?:\+|00)86)?1(?:(?:3[\d])|(?:4[5-7|9])|(?:5[0-3|5-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\d])|(?:9[1|8|9]))\d{8}$/; // 12小时制时间（hh:mm:ss） let reg_date = /^(?:1[0-2]|0?[1-9]):[0-5]\d:[0-5]\d$/; // 24小时制时间（HH:mm:ss） let reg_dates = /^(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d$/; // ip-v4 let reg_ip = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; // ip-v6 let reg_ip6 = /^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$/i;演示案例//用户名校验 function CheckUser() { var user = $("#user").val(); //获取值 var reguser = /^[a-zA-Z0-9_]{4,16}$/; //定义校验规则 var user_msg = $("#user_msg"); //提示信息元素 user_msg.html(""); //判断是否为空 if (user == "") { user_msg.html("*昵称不能为空"); return false; } else if (reguser.test(user) == false) { user_msg.html("*用户名格式不正确"); return false; } return true; }HTML5表单验证HTML5表单新增属性新增属性列表：属性描述placeholder提供一种提示（hint），输入域为空时显示，获得焦点输入内容后消失required规定输入域不能为空pattern规定验证输入域的模式（正则表达式）注意： 使用HTML5表单验证属性结合正则表达式，是目前客户端校验中应用频次较高的一种校验方式validity属性应用概述： 为表单元素添加 required和 pattern属性后，可以通过 validity属性获取 ValidityState对象，以获取当前验证属性的验证状态语法：var validityState = document.getElementById("user").validity;ValidityState对象包括 8 个属性，分别针对 8 个方面的验证错误属性描述valueMissing当表单元素设置required属性后，如果表单的值为空，则无法通过表单验证，返回true；否则，返回falsetypeMismatch当用户输入的内容与表单类型不匹配时，返回true；否则，返回falsepatternMismatch当用户输入的内容与表单元素pattern特性的正则不匹配时，返回true；否则，返回falsetooLong当用户输入的内容超过了表单元素maxLength特性限定的字符长度，返回true；否则，返回falserangeUnderflow当用户输入的值小于min特性的值，返回true；否则，返回falserangeOverflow当用户输入的值大于max特性的值，返回true；否则，返回falsestepMismatch当用户输入的值不符合step特性所推算的规则，返回true；否则，返回falsecustomError使用自定义的验证错误提示信息时，当存在自定义错误信息时，返回true；否则，返回false演示案例：var email = document.getElementById("email"); if (email.validity.valueMissing == true) { // 当数据值为空时 email.setCustomValidity("邮箱不能为空"); } else if (email.validity.typeMismatch == true || email.validity.patternMismatch == true) { //当表单类型不匹配或正则表达式验证错误时 email.setCustomValidity("邮箱格式不正确"); } else { email.setCustomValidity(""); }
概述页面对不同访问者的响应叫做事件。事件处理程序指的是当 HTML 中发生某些事件时所调用的方法。例如：在某个元素上移动鼠标。选择某个单选按钮点击某个元素在事件中经常使用术语"触发"（或"激发"）例如： "当您按下按键时触发 keypress 事件"。常见 DOM 事件：鼠标事件键盘事件表单事件文档/窗口事件click 单击事件keypress 键盘释放且产生可打印字符submit 表单提交load 加载dblclick 双击事件keydown 键盘按下change 内容改变resize 调整窗体大小mouseenter 鼠标进入时keyup 键盘释放focus 获得焦点scroll 窗体滚动mouseleave 鼠标离开时 blur 失去焦点unload 离开窗体、重新加载【已移除】hover 鼠标悬浮时 mouseover 鼠标指针移过时鼠标在其被选元素的子元素上来回进入时：触发 mouseover不触发 mouseenter mouseout 鼠标指针移出时鼠标在其被选元素的子元素上来回离开时：触发 mouseout不触发 mouseleave 事件绑定与解绑绑定事件概述：on() 方法在被选元素及子元素上添加一个或多个事件处理程序。自 jQuery 版本 1.7 起，on() 方法是 bind()、live() 和 delegate() 方法的新的替代品。该方法给 API 带来很多便利，我们推荐使用该方法，它简化了 jQuery 代码库。[scode type="yellow" size="simple"]注意： 使用 on() 方法添加的事件处理程序适用于当前及未来的元素（比如由脚本创建的新元素）。提示： 如需移除事件处理程序，请使用 off() 方法。提示： 如需添加只运行一次的事件然后移除，请使用 one() 方法。[/scode]演示案例： 青鸟技术栈 该实例演示了如何使用 on() 来达到与 one() 相同的效果。 这些一些文本。 点我使用 on() 方法设置背景颜色。 这些一些文本。 点我使用 bind() 方法设置背景颜色。 扩展：// 为多个事件绑定相同的事件处理函数 $(selector).on("event2 event3 …", function () { …… }) // 同时绑定多个事件 $(selector).on({ "event1": function () { …… }, "event2 event3 …": function () { …… }, …… })解绑事件概述：off() 方法通常用于移除通过 on() 方法添加的事件处理程序。自 jQuery 版本 1.7 起，off() 方法是 unbind()、die() 和 undelegate() 方法的新的替代品。该方法给 API 带来很多便利，我们推荐使用该方法，它简化了 jQuery 代码库。[scode type="yellow" size="simple"]注意： 如需移除指定的事件处理程序，当事件处理程序被添加时，选择器字符串必须匹配 on() 方法传递的参数。[/scode]演示案例： 青鸟技术栈 该事件演示了如何使用 off() 和 unbind() 方法来达到相同的效果。 点击段落修改背景颜色。 点击以下按钮再点击这个段落 ( click 事件被移除)。 使用 off() 方法移除 click 事件 hover()概述：hover() 方法规定当鼠标指针悬停在被选元素上时要运行的两个函数。方法触发 mouseenter 和 mouseleave 事件。[scode type="yellow" size="simple"]注意: 如果只指定一个函数，则 mouseenter 和 mouseleave 都执行它。[/scode]演示案例： 青鸟技术栈 鼠标移动到该段落。 jQuery动画效果show() 与 hide()概述：show()方法控制元素的显示，hide()方法控制元素的隐藏语法：$(selector).show([duration][, easing][, complete]); $(selector).hide([duration][, easing][, complete]);参数说明：duration规定显示效果的速度，可选值毫秒值（如：1000）"slow"（600毫秒）"normal"（400毫秒，默认值）"fast"（200毫秒）easing规定在动画的不同点上元素的速度，可选值"swing"：默认值，在开头/结尾移动慢，中间移动快"linear"：匀速移动complete规定动画结束后要执行的方法，每个匹配元素的动画完成后都会调用一次fadeIn() 和 fadeOut()概述：fadeIn()和 fadeOut()方法可通过改变元素的透明度实现淡入淡出效果语法：$(selector).fadeIn([duration][, easing][, complete]); $(selector).fadeOut([duration][, easing][, complete]);slideDown() 和 slideUp()概述：slideDown()方法使元素逐步延伸显示、slideUp()方法使元素逐步缩短直至隐藏语法：$(selector).slideDown([duration][, easing][, complete]); $(selector).slideUp([duration][, easing][, complete]);自定义动画概述：animate() 方法执行 CSS 属性集的自定义动画。该方法通过 CSS 样式将元素从一个状态改变为另一个状态。CSS属性值是逐渐改变的，这样就可以创建动画效果。语法：$(selector).animate(properties[, duration][, easing][, complete]);参数描述styles必需。规定产生动画效果的一个或多个 CSS 属性/值。注意： 当与 animate() 方法一起使用时，该属性名称必须是驼峰写法： 您必须使用 paddingLeft 代替 padding-left，marginRight 代替 margin-right，依此类推。可以应用动画的属性：backgroundPositionX、backgroundPositionY、borderWidth、borderBottomWidth、borderLeftWidth、borderRightWidth、borderTopWidth、borderSpacing、margin、marginBottom、marginLeft、marginRight、marginTop、outlineWidth、padding、paddingBottom、paddingLeft、paddingRight、paddingTop、height、width、maxHeight、maxWidth、minHeight、minWidth、fontSize、bottom、left、right、top、letterSpacing、wordSpacing、lineHeight、textIndent提示： 颜色动画不包含在核心 jQuery 库中。如果您想要应用动画颜色，您需要从 jQuery.com 下载 颜色动画插件。speed可选。规定动画的速度。可能的值： 毫秒 "slow"* "fast"easing可选。规定在动画的不同点中元素的速度。默认值是 "swing"。可能的值： "swing" - 在开头/结尾移动慢，在中间移动快 "linear" - 匀速移动提示： 扩展插件中提供更多可用的 easing 函数。callback可选。animate 函数执行完之后，要执行的函数。如需学习更多有关 callback 的内容，请访问jQuery Callback演示案例： 青鸟技术栈 使用动画放大高度 重置高度 [scode type="blue" size="simple"]▶️ 开始学习下一章: JavaScript+Jquery+ES6入门到放弃之表单验证[/scode]
DOM概述DOM操作分为三类DOM Core： 任何一种支持DOM的编程语言都可以使用它getElementById()getElementsByTagName()HTML-DOM：用于处理HTML文档document.formsdocument.getElementById("title").innerHTMLCSS-DOM： 用于操作CSSelement.style.color="green"节点操作创建节点工厂函数 $() 可用于获取或创建节点：$(selector)：通过选择器获取节点$(element)：把DOM节点转化成jQuery节点$(html)：使用HTML字符串创建jQuery节点演示案例：$(function () { // 创建节点 var li1 = $(" 大头儿子 "); var li2 = $(" 小魔仙 "); })插入节点常见方法：语法（内部插入，子节点）功能$(parentNode).append(content)表示将content(jQuery节点对象)添加到 parentNode之中，末尾content.appendTo($(target))表示把content(jQuery节点对象)添加到 target中，末尾$(parentNode).prepend(content)表示将content(jQuery节点对象)添加到 parentNode之中，开头content.preappendTo($(target))表示把content(jQuery节点对象)添加到 target中，开头案例演示： 关于我们 联系我们 我的邮箱：arvin@git-x.cn 演示效果：语法（外部插入，同辈节点）功能$(parentNode).after(content)表示将content(jQuery节点对象)添加到 parentNode之后content.insertAfter($(target))表示把content(jQuery节点对象)追加到 target之后$(parentNode).before(content)表示将content(jQuery节点对象)添加到 parentNode之前content.insertBefore($(target))表示把content(jQuery节点对象)追加到 target之前演示案例： 关于我们 联系我们 我的邮箱：arvin@git-x.cn 演示效果：删除节点删除被选元素（及其子元素）：$(selector).remove([selector]);[scode type="blue" size="small"]删除 class="italic" 的所有 元素可以写成：$("p").remove(".italic");[/scode]删除被选元素（及其子元素）[保留元素的事件、数据]：$(selector).detach([selector]);从被选元素中删除子元素：$(selector).empty();复制节点clone()方法生成被选元素的副本，包含子节点、文本和属性。语法：$(selector).clone(true|false)参数说明：是否复制元素的事件和数据，默认为 false 青鸟技术栈 单击段落增加文本的大小。 这是另一个段落。 复制第一个p元素(包括事件处理程序),并附加到body元素中 替换节点replaceWith()replaceWith() 方法把被选元素替换为新的内容。语法结构：$(selector).replaceWith(content[,function(index)])参数解释：content 必需。规定要插入的内容（可包含 HTML 标签）。function(index) 可选。规定返回替换内容的函数。replaceAll()replaceAll() 方法把被选元素替换为新的 HTML 元素。 【与 replaceWith()作用一致，参数不一致】语法结构：$(content).replaceAll(selector)参数解释：content 必需。规定要插入的内容（可包含 HTML 标签）。演示案例： 青鸟技术栈 这是一个标题 这是一个段落。 这是另一个段落。 这是另一个段落。 替换每个P元素 节点属性操作获取和设置元素属性attr() 方法设置或返回被选元素的属性和值。当该方法用于返回属性值，则返回第一个匹配元素的值。当该方法用于设置属性值，则为匹配元素设置一个或多个属性/值对。语法结构：返回属性的值：$(selector).attr(attribute)设置属性和值：$(selector).attr(attribute,value)设置多个属性和值：$(selector).attr({attribute:value, attribute:value,...})删除元素属性removeAttr() 方法从被选元素移除一个或多个属性。语法结构：$(selector).removeAttr(attribute)参数解释：attribute 必需。规定要移除的一个或多个属性。如需移除若干个属性，请使用空格分隔属性名称。节点遍历遍历子元素概述： children() 方法返回被选元素的所有直接子元素。[scode type="share" size="simple"]⚠注意：1️⃣该方法只沿着 DOM 树向下遍历单一层级。如需向下遍历多个层级（返回子孙节点或其他后代），请使用 find() 方法。2️⃣如需沿着 DOM 树向上遍历单一层级，或向上遍历直至文档根元素的所有路径（返回父节点或其他祖先），请使用 parent() 或 parents() 方法。3️⃣该方法不会返回文本节点。如需返回包含文本节点的所有子节点，请使用 contents() 方法。[/scode]语法结构：$(selector).children(filter)参数解释：filter 可选。规定缩小搜索子元素范围的选择器表达式。演示案例： 青鸟技术栈 body (曾祖先节点) div (祖先节点) ul (直接父节点) li (子节点) span (孙节点) li (子节点) span (孙节点) 遍历同辈元素方法列表：语法功能next( [selector] )获取紧邻匹配元素之后的元素 $("li:eq(1)").next().addClass("orange");prev( [selector] )获取紧邻匹配元素之前的元素 $("li:eq(1)").prev().addClass("orange");slibings( [selector] )获取匹配元素的同辈元素 $("li:eq(1)").siblings().addClass("orange");遍历前辈元素parent( [selector] )：获取匹配元素的父级元素，可选参数selector对父元素进行过滤parents ( [selector] )：获取匹配元素的祖先元素，可选参数selector对祖先元素进行过滤其他遍历方法each()方法规定对每个匹配元素执行的函数语法结构：$(selector).each(function (index, element) { //xx });[scode type="red" size="simple"]⚠注意：参数index表示当前元素的下标，从0开始参数element表示当前元素，也可在函数中使用this表示当function函数返回false时可以停止循环[/scode]内容操作操作HTML代码概述：设置和获取元素中的HTML内容可以使用 html()方法，类似于JS中的 innerHTML属性语法：$(selector).html([content]);操作元素内容概述： 获取或设置元素的文本内容可以使用 text()方法语法：$(selector).text([content]); html()方法与text()方法的比较：是否存有参数方法功能描述无参数html()获取第一个匹配元素的HTML内容或文本内容无参数text()获取所有匹配元素的文本内容有参数html( content )设置所有匹配元素的HTML内容或文本内容有参数text( content）设置所有匹配元素的文本内容操作元素的属性值添加行内样式：概述：设置或获取指定元素的样式值可以使用 css()方法语法：// 获取样式值 $(selector).css("属性"); $(selector).css(["属性1", "属性2", ... ]); // 设置样式值 $(selector).css("属性", "属性值"); $(selector).css({ "属性1": "属性值1", "属性2": "属性值2", ... });添加类样式：概述：addClass() 方法向被选元素添加一个或多个类名。removeClass() 方法从被选元素移除一个或多个类。语法：// 添加样式 $(selector).addClass("classname"); $(selector).addClass("classname1 classname2 …"); $(selector).addClass(["classname1", "classname2", ... ]); // 移除样式 $(selector).removeClass("classname"); $(selector).removeClass("classname1 classname2 …"); $(selector).removeClass(["classname1", "classname2", ... ]); $(selector).removeClass(); //移除全部样式切换：概述：toggleClass() 方法对添加和移除被选元素的一个或多个类进行切换。该方法检查每个元素中指定的类。如果不存在则添加类，如果已设置则删除之。这就是所谓的切换效果。案例演示： 青鸟技术栈 这是一个段落。 这是另一个段落。 转换P元素的"main"类 样式存在判断：概述：hasClass() 方法检查被选元素是否包含指定的类名称。如果被选元素包含指定的类，该方法返回 "true"。语法：$(selector).hasClass(classname)扩展：除 css()方法外，jQuery还提供有获取和设置元素高度、宽度等样式的操作方法语法功能height([value])设置或返回匹配元素的高度width([value])设置或返回匹配元素的宽度offset([value])返回以像素为单位的top和left坐标。仅对可见元素有效offsetParent( )返回最近的已定位祖先元素。定位元素指的是元素的CSS position值被设置为relative、absolute或fixed的元素position( )返回第一个匹配元素相对于父元素的位置scrollLeft([position])参数可选。设置或返回匹配元素相对滚动条左侧的偏移scrollTop([position])参数可选。设置或返回匹配元素相对滚动条顶[scode type="blue" size="simple"]▶️ 开始学习下一章: JavaScript+Jquery+ES6入门到放弃之jQuery事件与动画[/scode]
什么是jQuery？概述：jQuery 是一个 JavaScript 函数库，jQuery 是一个轻量级的"写的少，做的多"的 JavaScript 库。jQuery 库包含以下功能：HTML 元素选取HTML 元素操作CSS 操作HTML 事件函数JavaScript 特效和动画HTML DOM 遍历和修改AJAXUtilities提示： 除此之外，jQuery 还提供了大量的插件。jQuery 安装可以通过多种方法在网页中添加 jQuery。 您可以使用以下方法：从 jquery.com 下载 jQuery 库Production version - 用于实际的网站中，已被精简和压缩。jquery-版本号.min.jsDevelopment version - 用于测试和开发（未压缩，是可读的代码）jquery-版本号.js从 CDN 中载入 jQuery, 如从 Google 中加载 jQuery如果您不希望下载并存放 jQuery，那么也可以通过 CDN（内容分发网络） 引用它。Staticfile CDN、百度、又拍云、新浪、谷歌和微软的服务器都存有 jQuery 。如果你的站点用户是国内的，建议使用百度、又拍云、新浪等国内CDN地址，如果你站点用户是国外的可以使用谷歌和微软。本地文件导入： jQuery 库是一个 JavaScript 文件，您可以使用 HTML 的 CND在线导入：// CDNJS // Staticfile CDN // BootCDN // Microsoft CDN // Google CDN 基础语法jQuery 语法是通过选取 HTML 元素，并对选取的元素执行某些操作。基础语法：$(selector).action() 或 jQuery(selector).action()美元符号定义 jQuery选择符（selector）"查询"和"查找" HTML 元素jQuery 的 action() 执行对元素的操作事件处理方法动画方法HTML/CSS方法遍历方法Ajax方法杂项方法文档就绪事件基础语法：$(document).ready(function(){ // 开始写 jQuery 代码... });实际开发中，通常使用 ready()方法实现在页面加载后执行操作$(document).ready()与 window.onload类似，但也有区别比较项window.onload$(document).ready()执行时机必须等待网页中所有的内容加载完毕后（包括图片、视频、flash等）才能执行网页中所有DOM文档结构绘制完毕后即刻执行，与DOM元素关联的内容（图片、视频、flash等）可能并没有加载完编写次数同一页面不能同时编写多个同一页面能同时编写多个简化写法无$( function() { //执行代码 } ) ;dom对象和jQuery对象DOM对象：直接使用JavaScript获取的节点对象var objDOM = document.getElementById("title"); var objHTML = objDOM.innerHTML;jQuery对象：使用jQuery包装DOM对象后产生的对象，能够使用jQuery中的方法var $objJQuery = $("#title"); var objHTML = $objJQuery.html();[scode type="red" size="simple"]注意： DOM对象和jQuery对象的方法不能混用[/scode]jQuery选择器概述： jQuery 选择器允许您对 HTML 元素组或单个元素进行操作。jQuery 选择器允许您对 HTML 元素组或单个元素进行操作。jQuery 选择器基于元素的 id、类、类型、属性、属性值等"选择"（或查找）HTML 元素。 它基于已经存在的 CSS 选择器，除此之外，它还有一些自定义的选择器。jQuery 中所有选择器都以美元符号开头：$()。基础选择器基础选择器包括ID选择器、类选择器、标签选择器、全局选择器和并集选择器名称语法构成描述示例ID选择器#id根据给定的id匹配元素$("#title")选取id为title的元素类选择器.class根据给定的class匹配元素$(".title")选取所有class为title的元素标签选择器element根据给定的标签名匹配元素$("h2" )选取所有h2元素全局选择器*匹配所有元素$("*")选取所有元素并集选择器selector1, selector2, ..., selectorN将每一个选择器匹配的元素合并后一起返回$("div,p,.title" )选取所有div、p和拥有class为title的元素层次选择器层次选择器通过DOM元素之间的层次关系来获取元素名称语法构成描述示例后代选择器anc dsct选取ancestor元素里的所有descendant（后代）元素$("#menu span")选取#menu下的元素子选择器parent>child选取parent元素下的child（子）元素$("#menu>span")选取#menu的子元素相邻元素选择器prev+next选取紧邻prev元素之后的next元素$("h2+dl")选取紧邻元素之后的同辈元素同辈元素选择器prev~sibings选取prev元素之后的所有siblings元素$("h2~dl")选取元素之后所有的同辈元素过滤选择器基本过滤选择器语法描述示例:first选取第一个元素$("li:first")选取所有元素中的第一个元素:last选取最后一个元素$("li:last")选取所有元素中的最后一个元素:not(selector)选取去除所有与给定选择器匹配的元素$("li:not(.three)")选取class不是three的元素:even选取索引是偶数的所有元素（index从0开始）$("li:even")选取所有元素中索引值是偶数的元素:odd选取索引是奇数的所有元素（index从0开始）$("li:odd")选取所有元素中索引值是奇数的元素:eq(index)选取索引等于index的元素（index从0开始）$("li:eq(1)")选取索引等于1的元素:gt(index)选取索引大于index的元素（index从0开始）$("li:gt(1)")选取索引大于1的元素:lt(index)选取索引小于index的元素（index从0开始）$("li:lt(1)")选取索引小于1的元素:header选取所有标题元素，如h1~h6$(":header")选取网页中所有标题元素:focus选取当前获取焦点的元素$(":focus")选取当前获取焦点的元素:animated选择所有动画$(":animated")选取当前所有动画元素属性选择器： 属性选择器通过HTML元素的属性来选择元素语法构成描述示例[attribute]选取包含给定属性的元素$("[href]")选取含有href属性的元素[attribute=value]选取等于给定属性是某个特定值的元素$("[href = '#']")选取href属性值为“#”的元素[attribute!=value]选取不等于给定属性是某个特定值的元素$("[href != '#']")选取href属性值不为“#”的元素[attribute^=value]选取给定属性是以某些特定值开始的元素$("[href ^= 'en']")选取href属性值以en开头的元素[attribute$=value]选取给定属性是以某些特定值结尾的元素选取href属性值以.jpg结尾的元素[attribute*=value]选取给定属性是以包含某些值的元素$("[href *= 'txt']")选取href属性值中含有txt的元素jQuery选择器注意事项特殊符号的转义： aa cc 选择器中的空格：选择器的书写规范很严格，多一个空格或少一个空格，都会影响选择器的效果var $t_a = $(".test :hidden"); // 带空格的jQuery选择器 [选取class为“test”的元素内的所有隐藏元素] var $t_b = $(".test:hidden"); // 不带空格的jQuery选择器 [选取class为“test”的隐藏元素]解决jQuery和其他库的冲突jQuery定义了jQuery.noConflict()方法，放弃对$的使用权jQuery.noConflict(); // 让渡$使用权，后续jQuery代码使用jQuery代替$ jQuery( document ).ready( … ); // 或者重新指定一个替代符号： var $j = jQuery.noConflict(); // 让渡$使用权，并指定用 $j 代替$ $j( document ).ready( … );上面的代码改变了jQuery的编码风格，烦琐且不利于重用已有代码推荐采用：jQuery.noConflict(); // 让渡$使用权，其他脚本库可以使用$ jQuery(document).ready(function ($) { // 在这里继续使用$编写jQuery代码 $("#show").click( … ); });或者：jQuery.noConflict(); // 让渡$使用权，其他脚本库可以使用$ (function ($) { // 在这里继续使用$编写jQuery代码 $(document).ready(function () { $("#show").click( … ); }); })(jQuery);[scode type="blue" size="simple"]▶️ 开始学习下一章: JavaScript+Jquery+ES6入门到放弃之jQuery操作DOM[/scode]
JavaScript+jQuery+ES6入门到放弃之ECMAScript 6 进阶
内置对象的扩展字符串的扩展模板字符串概述： 模板字符串（template string）是增强版的字符串，用反引号（`）标识。它可以当作普通字符串使用，也可以用来定义多行字符串，或者在字符串中嵌入变量。演示案例：// 普通字符串 let text = `定义一个普通字符串变量` // 多行字符串 let texts = `这是第一行数据 这是第二行数据 这是第三行数据`; // 字符串中嵌入变量 let userName = "Bob", age = 18; console.log(`你好，我叫：${userName}，今年${age}岁！`); // 如果在模板字符串中需要使用反引号，则前面要用反斜杠转义。 console.log(`在JavaScript中使用反引号（\`）定义模板字符串`);[scode type="yellow" size="simple"]⚠️注意：模板字符串中嵌入变量，需要将变量名写在 ${}之中，大括号内部可以放入任意的 JavaScript 表达式，可以进行运算、引用对象属性和调用函数。模板字符串中的空格和换行，都将被保留。如果模板字符串中的变量没有声明，将报错而不是输出undefined。[/scode]字符串新增的方法新增方法列表：方法局部变量includes(searchString:string)：boolean返回布尔值，表示是否找到了参数字符串startsWith(searchString:string)：boolean返回布尔值，表示参数字符串是否在原字符串头部endsWith(searchString:string)：boolean返回布尔值，表示参数字符串是否在原字符串尾部repeat(count:number):string返回一个新字符串，表示将原字符串重复n次padStart(length:number,string:string):string用于头部补全padEnd(length:number,string:string):string用于尾部补全trimStart():string消除字符串头部的空格trimEnd():string消除尾部的空格replaceAll(searchValue, replacement):string替换所有匹配字符串at(index)返回参数指定位置的字符演示案例：/** * includes()：返回布尔值，表示是否找到了参数字符串。 * startsWith()：返回布尔值，表示参数字符串是否在原字符串的头部。 * endsWith()：返回布尔值，表示参数字符串是否在原字符串的尾部。 */ let s = 'Hello world!'; s.startsWith('Hello') // 是否以'Hello'开头： true s.endsWith('!') // 是否以'!'结尾：true s.includes('o') // 是否包含'o': true // 上面三个方法都支持第二个参数 // 参数2表示开始搜索的位置 s.startsWith('wo', 6) // 从下标6开始计算，是否以'wo'开头：true s.includes('He', 6) // 从下标6开始计算，是否包含'He'：false // 参数2表示结束位置 s.endsWith('Hello', 5) // 从第一个字符开始到下标为5的字符中，是否以'Hello'结尾：true /** * repeat(n)：返回一个新字符串，表示将原字符串重复n次。 */ 'x'.repeat(3) // "xxx" 'hello'.repeat(2) // "hellohello" 'na'.repeat(0) // "" // 参数如果是小数，会被取整。 'na'.repeat(2.9) // "nana" // 如果repeat的参数是负数或者Infinity(无穷大)，会报错。 'na'.repeat(Infinity) // RangeError 'na'.repeat(-1) // RangeError /** * 如果某个字符串不够指定长度，会在头部或尾部补全。 * padStart()用于头部补全 * padEnd()用于尾部补全。 * 第一个参数是字符串补全生效的最大长度，第二个参数是用来补全的字符串。 */ 'x'.padStart(5, 'ab') // 'ababx' 'x'.padStart(4, 'ab') // 'abax' 'x'.padEnd(5, 'ab') // 'xabab' 'x'.padEnd(4, 'ab') // 'xaba' // 如果原字符串的长度，等于或大于最大长度，则字符串补全不生效，返回原字符串。 'xxx'.padStart(2, 'ab') // 'xxx' 'xxx'.padEnd(2, 'ab') // 'xxx' // 如果用来补全的字符串与原字符串，两者的长度之和超过了最大长度，则会截去超出位数的补全字符串。 'abc'.padStart(10, '0123456789') // '0123456abc' // 如果省略第二个参数，默认使用空格补全长度。 'x'.padStart(4) // ' x' 'x'.padEnd(4) // 'x ' /** * trimStart()和trimEnd()这两个方法。 * 它们的行为与trim()一致。 * trimStart()消除字符串头部的空格 * trimEnd()消除尾部的空格。 * 它们返回的都是新字符串，不会修改原始字符串。 */ const text = ' abc '; text.trim() // "abc" text.trimStart() // "abc " text.trimEnd() // " abc" /** * replaceAll()方法，可以一次性替换所有匹配。 */ let textInfo = 'Hello~~'; textInfo.replace('~', '!'); //Hello!~ textInfo.replaceAll('~', '!'); //Hellpo!! /** * at()方法接受一个整数作为参数，返回参数指定位置的字符，支持负索引（即倒数的位置）。 */ const str = 'hello'; str.at(1) // "e" str.at(-1) // "o"数组的扩展扩展运算符概述： 扩展运算符（spread）是三个点（...）。它好比 rest 参数的逆运算，将一个数组转为用逗号分隔的参数序列。案例演示：let arr = []; //扩展运算符的基本用法 arr.push(...[1, 2, 3, 4, 5]); console.log(arr); // [1, 2, 3, 4, 5] console.log(1, ...[2, 3, 4], 5) // 1 2 3 4 5 //可以放置表达式 console.log(...(1 > 0 ? ['a'] : [])); //a //空数组，不产生效果 console.log([...[], 1]); // [1] //用于函数调用-1 let arrs = []; function push(array, ...items) { array.push(...items); } push(arrs, ...[1, 3, 5, 7, 9]); console.log(arrs); // [1, 3, 5, 7, 9] //用于函数调用-2 function add(x, y) { console.log(x + y); } const numbers = [4, 38]; add(...numbers) // 42替代函数的apply方法：// 将一个数组添加到另一个数组的尾部在ES5和ES6中的写法区别 // ES5的 写法 var arr1 = [0, 1, 2]; var arr2 = [3, 4, 5]; Array.prototype.push.apply(arr1, arr2); // ES6 的写法 let arr3 = [0, 1, 2]; let arr4 = [3, 4, 5]; arr3.push(...arr4);常见应用复制数组：数组是复合的数据类型，直接复制的话，只是复制了指向底层数据结构的指针，而不是克隆一个全新的数组。const a1 = [1, 2]; const a2 = a1; a2[0] = 2; a1 // [2, 2]上面代码中，a2并不是 a1的克隆，而是指向同一份数据的另一个指针。修改 a2，会直接导致 a1的变化。ES5 只能用变通方法来复制数组。const a1 = [1, 2]; const a2 = a1.concat(); a2[0] = 2; a1 // [1, 2]上面代码中，a1会返回原数组的克隆，再修改 a2就不会对 a1产生影响。扩展运算符提供了复制数组的简便写法。const a1 = [1, 2]; // 写法一 const a2 = [...a1]; // 写法二 const [...a2] = a1;上面的两种写法，a2都是 a1的克隆。合并数组： 扩展运算符提供了数组合并的新写法。const arr1 = ['a', 'b']; const arr2 = ['c']; const arr3 = ['d', 'e']; // ES5 的合并数组 arr1.concat(arr2, arr3); // [ 'a', 'b', 'c', 'd', 'e' ] // ES6 的合并数组 [...arr1, ...arr2, ...arr3] // [ 'a', 'b', 'c', 'd', 'e' ]与解构赋值结合：扩展运算符可以与解构赋值结合起来，用于生成数组。const [first, ...rest] = [1, 2, 3, 4, 5]; console.log(first); // 1 console.log(rest); // [2, 3, 4, 5]如果将扩展运算符用于数组赋值，只能放在参数的最后一位，否则会报错。const [...butLast, last] = [1, 2, 3, 4, 5]; // 报错 const [first, ...middle, last] = [1, 2, 3, 4, 5]; // 报错字符串： 扩展运算符还可以将字符串转为真正的数组。let arr = [...'hello']; console.log(arr); // [ "h", "e", "l", "l", "o" ]看看它的输出结果是什么？let arr1 = [1, 2, 3]; let arr2 = [4, 5, [6, 7, 8]]; arr1.push(...arr2); console.log(arr1);Array.from()概述：Array.from()方法用于将两类对象转为真正的数组：类似数组的对象（array-like object）和可遍历（iterable）的对象（包括 ES6 新增的数据结构 Set 和 Map）。下面是一个类似数组的对象，Array.from()将它转为真正的数组。【类数组对象，最基本的要求是具有 length属性的对象】let arrayLike = { '0': 'a', '1': 'b', '2': 'c', length: 3 }; // ES5 的写法 var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c'] // ES6 的写法 let arr2 = Array.from(arrayLike); // ['a', 'b', 'c'][scode type="red" size="simple"]📢类数组对象转换为真正的数组所需条件：类数组对象必须具有length属性，用于指定数组的长度如果没有length属性，那么转换后的数组是一个空数组类数组对象的属性必须为数值型或字符串型的数字，且从0开始let arrayLike = { 'name': 'tom', 'age': '65', 'sex': '男', 'friends': ['jane', 'john', 'Mary'], length: 4 } let arr = Array.from(arrayLike) console.log(arr) // [undefined, undefined, undefined, undefined][/scode]includes()概述：Array.prototype.includes方法返回一个布尔值，表示某个数组是否包含给定的值，与字符串的 includes方法类似。ES2016 引入了该方法。[1, 2, 3].includes(2) // true [1, 2, 3].includes(4) // false [1, 2, NaN].includes(NaN) // true该方法的第二个参数表示搜索的起始位置，默认为 0。如果第二个参数为负数，则表示倒数的位置，如果这时它大于数组长度（比如第二个参数为 -4，但数组长度为 3），则会重置为从 0开始。[1, 2, 3].includes(3, 3); // false [1, 2, 3].includes(3, -1); // trueentries()，keys() 和 values()概述： ES6 提供三个新的方法——entries()，keys()和 values()——用于遍历数组。它们都返回一个遍历器对象，可以用 for...of循环进行遍历，唯一的区别是 keys()是对键名的遍历、values()是对键值的遍历，entries()是对键值对的遍历。let arr = ['a', 'b']; for (let index of arr.keys()) { console.log(index); } // 0 // 1 for (let elem of arr.values()) { console.log(elem); } // 'a' // 'b' for (let [index, elem] of arr.entries()) { console.log(index, elem); } // 0 "a" // 1 "b"如果不使用 for...of循环，可以手动调用遍历器对象的 next方法，进行遍历。【💡扩展】let letter = ['a', 'b', 'c']; let entries = letter.entries(); console.log(entries.next().value); // [0, 'a'] console.log(entries.next().value); // [1, 'b'] console.log(entries.next().value); // [2, 'c']find()，findIndex()，findLast() 和 findLastIndex()概念： 数组实例的 find()方法，用于找出第一个符合条件的数组成员。它的参数是一个回调函数，所有数组成员依次执行该回调函数，直到找出第一个返回值为 true的成员，然后返回该成员。如果没有符合条件的成员，则返回 undefined。let arr = [1, 4, -5, 10]; // 查找数组中，元素值小于0的数，并返回该值 var res = arr.find((n) => { return n < 0; }); console.log(res); // -5find()方法的回调函数可以接受三个参数，依次为当前的值、当前的位置和原数组。数组实例的 findIndex()方法的用法与 find()方法非常类似，返回第一个符合条件的数组成员的位置，如果所有成员都不符合条件，则返回 -1。let arr = [1, 5, 10, 6, 20, 30] // 获取数组下标元素大于2的，且数组值大于9的元素下标 let res = arr.findIndex((value, index, arr) => { if (index > 2) { return value > 9; } }) console.log(res); //4find()和 findIndex()都是从数组的0号位，依次向后检查。ES2022 新增了两个方法 findLast()和 findLastIndex()，从数组的最后一个成员开始，依次向前检查，其他都保持不变。let arr = [1, 5, 20, 6, 10, 30] // 从数组结束位置开始查找小于10的数值 let res = arr.findLast((value) => { return value < 10; }) console.log(res); //6 // 从数组结束位置开始查找小于等于10的数值的下标 res = arr.findLastIndex((value) => { return value <= 10; }) console.log(res); //4对象的扩展概述： 对象（object）是 JavaScript 最重要的数据结构。ES6 对它进行了重大升级。ES6 允许在大括号里面，直接写入变量和函数，作为对象的属性和方法。这样的书写更加简洁。const foo = 'bar'; const baz = {foo}; baz // {foo: "bar"} // 等同于 const baz = {foo: foo};上面代码中，变量 foo直接写在大括号里面。这时，属性名就是变量名, 属性值就是变量值。除了属性简写，方法也可以简写。const o = { method() { return "Hello!"; } }; // 等同于 const o = { method: function() { return "Hello!"; } };演示：let birth = '2000/01/01'; const Person = { name: '张三', //等同于birth: birth birth, // 等同于hello: function ()... hello() { console.log('我的名字是', this.name); } };属性名表达式JavaScript 定义对象的属性，有两种方法：// 方法一 obj.foo = true; // 方法二 obj['a' + 'bc'] = 123;上面代码的方法一是直接用标识符作为属性名，方法二是用表达式作为属性名，这时要将表达式放在方括号之内。但是，如果使用字面量方式定义对象（使用大括号），在 ES5 中只能使用方法一（标识符）定义属性。var obj = { foo: true, abc: 123 };ES6 允许字面量定义对象时，用方法二（表达式）作为对象的属性名，即把表达式放在方括号内。let propKey = 'foo'; let obj = { [propKey]: true, ['a' + 'bc']: 123 };下面是另一个例子。let lastWord = 'last word'; const a = { 'first word': 'hello', [lastWord]: 'world' }; a['first word'] // "hello" a[lastWord] // "world" a['last word'] // "world"表达式还可以用于定义方法名。let obj = { ['h' + 'ello']() { return 'hi'; } }; obj.hello() // hi注意，属性名表达式与简洁表示法，不能同时使用，会报错。Object.is()ES5 比较两个值是否相等，只有两个运算符：相等运算符（==）和严格相等运算符（===）。它们都有缺点，前者会自动转换数据类型，后者的 NaN不等于自身，以及 +0等于 -0。JavaScript 缺乏一种运算，在所有环境中，只要两个值是一样的，它们就应该相等。ES6 提出“Same-value equality”（同值相等）算法，用来解决这个问题。Object.is就是部署这个算法的新方法。它用来比较两个值是否严格相等，与严格比较运算符（===）的行为基本一致。Object.is('foo', 'foo') // true Object.is({}, {}) // false不同之处只有两个：一是 +0不等于 -0，二是 NaN等于自身。+0 === -0 //true NaN === NaN // false Object.is(+0, -0) // false Object.is(NaN, NaN) // trueObject.assign()概述： Object.assign()方法用于对象的合并，将源对象（source）的所有可枚举属性，复制到目标对象（target）。const target = { a: 1 }; const source1 = { b: 2 }; const source2 = { c: 3 }; Object.assign(target, source1, source2); target // {a:1, b:2, c:3}Object.assign()方法的第一个参数是目标对象，后面的参数都是源对象。注意，如果目标对象与源对象有同名属性，或多个源对象有同名属性，则后面的属性会覆盖前面的属性。const target = { a: 1, b: 1 }; const source1 = { b: 2, c: 2 }; const source2 = { c: 3 }; Object.assign(target, source1, source2); target // {a:1, b:2, c:3}如果只有一个参数，Object.assign()会直接返回该参数。const obj = {a: 1}; Object.assign(obj) === obj // true如果该参数不是对象，则会先转成对象，然后返回。typeof Object.assign(2) // "object"由于 undefined和 null无法转成对象，所以如果它们作为参数，就会报错。Object.assign(undefined) // 报错 Object.assign(null) // 报错如果非对象参数出现在源对象的位置（即非首参数），那么处理规则有所不同。首先，这些参数都会转成对象，如果无法转成对象，就会跳过。这意味着，如果 undefined和 null不在首参数，就不会报错。let obj = {a: 1}; Object.assign(obj, undefined) === obj // true Object.assign(obj, null) === obj // true其他类型的值（即数值、字符串和布尔值）不在首参数，也不会报错。但是，除了字符串会以数组形式，拷贝入目标对象，其他值都不会产生效果。const v1 = 'abc'; const v2 = true; const v3 = 10; const obj = Object.assign({}, v1, v2, v3); console.log(obj); // { "0": "a", "1": "b", "2": "c" }上面代码中，v1、v2、v3分别是字符串、布尔值和数值，结果只有字符串合入目标对象（以字符数组的形式），数值和布尔值都会被忽略。这是因为只有字符串的包装对象，会产生可枚举属性。常见用途（1）为对象添加属性class Point { constructor(x, y) { Object.assign(this, {x, y}); } }上面方法通过 Object.assign()方法，将 x属性和 y属性添加到 Point类的对象实例。（2）为对象添加方法Object.assign(SomeClass.prototype, { someMethod(arg1, arg2) { ··· }, anotherMethod() { ··· } }); // 等同于下面的写法 SomeClass.prototype.someMethod = function (arg1, arg2) { ··· }; SomeClass.prototype.anotherMethod = function () { ··· };上面代码使用了对象属性的简洁表示法，直接将两个函数放在大括号中，再使用 assign()方法添加到 SomeClass.prototype之中。（3）克隆对象function clone(origin) { return Object.assign({}, origin); }上面代码将原始对象拷贝到一个空对象，就得到了原始对象的克隆。不过，采用这种方法克隆，只能克隆原始对象自身的值，不能克隆它继承的值。如果想要保持继承链，可以采用下面的代码。function clone(origin) { let originProto = Object.getPrototypeOf(origin); return Object.assign(Object.create(originProto), origin); }（4）合并多个对象将多个对象合并到某个对象。const merge = (target, ...sources) => Object.assign(target, ...sources);如果希望合并后返回一个新对象，可以改写上面函数，对一个空对象合并。const merge = (...sources) => Object.assign({}, ...sources);（5）为属性指定默认值const DEFAULTS = { logLevel: 0, outputFormat: 'html' }; function processContent(options) { options = Object.assign({}, DEFAULTS, options); console.log(options); // ... }上面代码中，DEFAULTS对象是默认值，options对象是用户提供的参数。Object.assign()方法将 DEFAULTS和 options合并成一个新对象，如果两者有同名属性，则 options的属性值会覆盖 DEFAULTS的属性值。注意，由于存在浅拷贝的问题，DEFAULTS对象和 options对象的所有属性的值，最好都是简单类型，不要指向另一个对象。否则，DEFAULTS对象的该属性很可能不起作用。const DEFAULTS = { url: { host: 'example.com', port: 7070 }, }; processContent({ url: {port: 8000} }) // { // url: {port: 8000} // }上面代码的原意是将 url.port改成 8000，url.host不变。实际结果却是 options.url覆盖掉 DEFAULTS.url，所以 url.host就不存在了。对象遍历Object.keys()：ES5 引入了 Object.keys方法，返回一个数组，成员是参数对象自身的（不含继承的）所有可遍历（enumerable）属性的键名。var obj = { foo: 'bar', baz: 42 }; Object.keys(obj) // ["foo", "baz"]ES2017 引入了跟 Object.keys配套的 Object.values和 Object.entries，作为遍历一个对象的补充手段，供 for...of循环使用。let {keys, values, entries} = Object; let obj = { a: 1, b: 2, c: 3 }; for (let key of keys(obj)) { console.log(key); // 'a', 'b', 'c' } for (let value of values(obj)) { console.log(value); // 1, 2, 3 } for (let [key, value] of entries(obj)) { console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3] }Object.values()：Object.values方法返回一个数组，成员是参数对象自身的（不含继承的）所有可遍历（enumerable）属性的键值。const obj = { foo: 'bar', baz: 42 }; Object.values(obj) // ["bar", 42]返回数组的成员顺序，与本章的《属性的遍历》部分介绍的排列规则一致。const obj = { 100: 'a', 2: 'b', 7: 'c' }; Object.values(obj) // ["b", "c", "a"]上面代码中，属性名为数值的属性，是按照数值大小，从小到大遍历的，因此返回的顺序是 b、c、a。Object.values只返回对象自身的可遍历属性。const obj = Object.create({}, {p: {value: 42}}); Object.values(obj) // []上面代码中，Object.create方法的第二个参数添加的对象属性（属性 p），如果不显式声明，默认是不可遍历的，因为 p的属性描述对象的 enumerable默认是 false，Object.values不会返回这个属性。只要把 enumerable改成 true，Object.values就会返回属性 p的值。const obj = Object.create({}, {p: { value: 42, enumerable: true } }); Object.values(obj) // [42]Object.values会过滤属性名为 Symbol 值的属性。Object.values({ [Symbol()]: 123, foo: 'abc' }); // ['abc']如果 Object.values方法的参数是一个字符串，会返回各个字符组成的一个数组。Object.values('foo') // ['f', 'o', 'o']上面代码中，字符串会先转成一个类似数组的对象。字符串的每个字符，就是该对象的一个属性。因此，Object.values返回每个属性的键值，就是各个字符组成的一个数组。如果参数不是对象，Object.values会先将其转为对象。由于数值和布尔值的包装对象，都不会为实例添加非继承的属性。所以，Object.values会返回空数组。Object.values(42) // [] Object.values(true) // []Object.entries()：Object.entries()方法返回一个数组，成员是参数对象自身的（不含继承的）所有可遍历（enumerable）属性的键值对数组。const obj = { foo: 'bar', baz: 42 }; Object.entries(obj) // [ ["foo", "bar"], ["baz", 42] ]除了返回值不一样，该方法的行为与 Object.values基本一致。如果原对象的属性名是一个 Symbol 值，该属性会被忽略。Object.entries({ [Symbol()]: 123, foo: 'abc' }); // [ [ 'foo', 'abc' ] ]上面代码中，原对象有两个属性，Object.entries只输出属性名非 Symbol 值的属性。将来可能会有 Reflect.ownEntries()方法，返回对象自身的所有属性。Object.entries的基本用途是遍历对象的属性。let obj = { one: 1, two: 2 }; for (let [k, v] of Object.entries(obj)) { console.log( `${JSON.stringify(k)}: ${JSON.stringify(v)}` ); } // "one": 1 // "two": 2Object.entries方法的另一个用处是，将对象转为真正的 Map结构。const obj = { foo: 'bar', baz: 42 }; const map = new Map(Object.entries(obj)); map // Map { foo: "bar", baz: 42 }自己实现 Object.entries方法，非常简单。// Generator函数的版本 function* entries(obj) { for (let key of Object.keys(obj)) { yield [key, obj[key]]; } } // 非Generator函数的版本 function entries(obj) { let arr = []; for (let key of Object.keys(obj)) { arr.push([key, obj[key]]); } return arr; }Set和Map对象Set对象概述： ES6 提供了新的数据结构 Set。它类似于数组，但是成员的值都是唯一的，没有重复的值。Set本身是一个构造函数，用来生成 Set 数据结构。const s = new Set(); [2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x)); for (let i of s) { console.log(i); } // 2 3 5 4上面代码通过 add()方法向 Set 结构加入成员，结果表明 Set 结构不会添加重复的值。常见属性和方法：属性/方法描述Set.prototype.constructor构造函数，默认就是Set函数Set.prototype.size返回Set实例的成员总数Set.prototype.add(value)添加某个值，返回 Set 结构本身Set.prototype.delete(value)删除某个值，返回一个布尔值，表示删除是否成功Set.prototype.has(value)返回一个布尔值，表示该值是否为Set的成员Set.prototype.clear()清除所有成员，没有返回值Set.prototype.keys()返回键名的遍历器Set.prototype.values()返回键值的遍历器Set.prototype.entries()返回键值对的遍历器Set.prototype.forEach()使用回调函数遍历每个成员Map对象概述： JavaScript 的对象（Object），本质上是键值对的集合（Hash 结构），但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。为了解决这个问题，ES6 提供了 Map 数据结构。它类似于对象，也是键值对的集合，但是“键”的范围不限于字符串，各种类型的值（包括对象）都可以当作键。也就是说，Object 结构提供了“字符串—值”的对应，Map 结构提供了“值—值”的对应，是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构，Map 比 Object 更合适。演示案例：const m = new Map(); m.set("1001", { name: "张三", age: 16 }); m.set("1002", { name: "李四", age: 10 }); console.log(m.size); // 2 console.log(m.has('1000')); // false console.log(m.get('1001')); // {name: '张三', age: 16} console.log(m.delete('1001')); //true console.log(m.delete('1001')); //false console.log(m.size); //1 //Map 也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。 const map = new Map([ ['name', '张三'], ['title', 'Author'] ]); for (const iterator of map.keys()) { console.log(iterator); //name title }如果对同一个键多次赋值，后面的值将覆盖前面的值。const map = new Map(); map .set(1, 'aaa') .set(1, 'bbb'); map.get(1) // "bbb"上面代码对键 1连续赋值两次，后一次的值覆盖前一次的值。数组的 map和 filter方法：let arr = [2, 4, 6, 8]; let res = arr.map((v) => { return v * 2; }) console.log(res); // [4, 8, 12, 16] let arr2 = [1, 2, 3, 4, 5]; let res2 = arr2.filter((v) => { return v % 2 == 0; }); console.log(res2); // [2, 4]常见属性和方法：属性/方法描述size 属性返回 Map 结构的成员总数Map.prototype.set(key, value)设置键名key对应的键值为value，返回整个 Map 结构Map.prototype.get(key)读取key对应的键值，如果找不到key，返回undefinedMap.prototype.has(key)返回一个布尔值，表示某个键是否在当前 Map 对象之中Map.prototype.delete(key)删除某个键，返回true。如果删除失败，返回falseMap.prototype.clear()清除所有成员，没有返回值Map.prototype.keys()返回键名的遍历器Map.prototype.values()返回键值的遍历器Map.prototype.entries()返回所有成员的遍历器Map.prototype.forEach()遍历 Map 的所有成员Module语法概述： 历史上，JavaScript 一直没有模块（module）体系，无法将一个大程序拆分成互相依赖的小文件，再用简单的方法拼装起来。ES6 在语言标准的层面上，实现了模块功能，而且实现得相当简单，ES6 模块的设计思想是尽量的静态化，使得编译时就能确定模块的依赖关系，以及输入和输出的变量。ES6模块带来的好处：避免变量污染，命名冲突、提高代码复用率、提高维护性、依赖关系的管理。模块功能主要由两个命令构成：export命令：用于规定模块的对外接口import命令：用于输入其他模块提供的功能export和import命令export（导出）：一个模块就是一个独立的文件。该文件内部的所有变量，外部无法获取。如果你希望外部能够读取模块内部的某个变量，就必须使用 export关键字输出该变量。下面是一个 JS 文件，里面使用 export命令输出变量。// profile.js export var firstName = 'Michael'; export var lastName = 'Jackson'; export var year = 1958;上面代码是 profile.js文件，保存了用户信息。ES6 将其视为一个模块，里面用 export命令对外部输出了三个变量。export的写法，除了像上面这样，还有另外一种。// profile.js var firstName = 'Michael'; var lastName = 'Jackson'; var year = 1958; export { firstName, lastName, year };上面代码在 export命令后面，使用大括号指定所要输出的一组变量。它与前一种写法（直接放置在 var语句前）是等价的，但是应该优先考虑使用这种写法。因为这样就可以在脚本尾部，一眼看清楚输出了哪些变量。export命令除了输出变量，还可以输出函数或类（class）。export function multiply(x, y) { return x * y; };上面代码对外输出一个函数 multiply。通常情况下，export输出的变量就是本来的名字，但是可以使用 as关键字重命名。function v1() { ... } function v2() { ... } export { v1 as streamV1, v2 as streamV2, v2 as streamLatestVersion };上面代码使用 as关键字，重命名了函数 v1和 v2的对外接口。重命名后，v2可以用不同的名字输出两次。需要特别注意的是，export命令规定的是对外的接口，必须与模块内部的变量建立一一对应关系。// 报错 export 1; // 报错 var m = 1; export m;上面两种写法都会报错，因为没有提供对外的接口。第一种写法直接输出 1，第二种写法通过变量 m，还是直接输出 1。1只是一个值，不是接口。正确的写法是下面这样。// 写法一 export var m = 1; // 写法二 var m = 1; export {m}; // 写法三 var n = 1; export {n as m};上面三种写法都是正确的，规定了对外的接口 m。其他脚本可以通过这个接口，取到值 1。它们的实质是，在接口名与模块内部变量之间，建立了一一对应的关系。同样的，function和 class的输出，也必须遵守这样的写法。// 报错 function f() {} export f; // 正确 export function f() {}; // 正确 function f() {} export {f};import（导入）：使用 export命令定义了模块的对外接口以后，其他 JS 文件就可以通过 import命令加载这个模块。// main.js import { firstName, lastName, year } from './profile.js'; function setName(element) { element.textContent = firstName + ' ' + lastName; }上面代码的 import命令，用于加载 profile.js文件，并从中输入变量。import命令接受一对大括号，里面指定要从其他模块导入的变量名。大括号里面的变量名，必须与被导入模块（profile.js）对外接口的名称相同。如果想为输入的变量重新取一个名字，import命令要使用 as关键字，将输入的变量重命名。import { lastName as surname } from './profile.js';import命令输入的变量都是只读的，因为它的本质是输入接口。也就是说，不允许在加载模块的脚本里面，改写接口。import {a} from './xxx.js' a = {}; // Syntax Error : 'a' is read-only;上面代码中，脚本加载了变量 a，对其重新赋值就会报错，因为 a是一个只读的接口。但是，如果 a是一个对象，改写 a的属性是允许的。import {a} from './xxx.js' a.foo = 'hello'; // 合法操作上面代码中，a的属性可以成功改写，并且其他模块也可以读到改写后的值。不过，这种写法很难查错，建议凡是输入的变量，都当作完全只读，不要轻易改变它的属性。import后面的 from指定模块文件的位置，可以是相对路径，也可以是绝对路径。如果不带有路径，只是一个模块名，那么必须有配置文件，告诉 JavaScript 引擎该模块的位置。import { myMethod } from 'util';上面代码中，util是模块文件名，由于不带有路径，必须通过配置，告诉引擎怎么取到这个模块。注意，import命令具有提升效果，会提升到整个模块的头部，首先执行。foo(); import { foo } from 'my_module';上面的代码不会报错，因为 import的执行早于 foo的调用。这种行为的本质是，import命令是编译阶段执行的，在代码运行之前。由于 import是静态执行，所以不能使用表达式和变量，这些只有在运行时才能得到结果的语法结构。// 报错 import { 'f' + 'oo' } from 'my_module'; // 报错 let module = 'my_module'; import { foo } from module; // 报错 if (x === 1) { import { foo } from 'module1'; } else { import { foo } from 'module2'; }上面三种写法都会报错，因为它们用到了表达式、变量和 if结构。在静态分析阶段，这些语法都是没法得到值的。最后，import语句会执行所加载的模块，因此可以有下面的写法。import 'lodash';上面代码仅仅执行 lodash模块，但是不输入任何值。如果多次重复执行同一句 import语句，那么只会执行一次，而不会执行多次。import 'lodash'; import 'lodash';上面代码加载了两次 lodash，但是只会执行一次。import { foo } from 'my_module'; import { bar } from 'my_module'; // 等同于 import { foo, bar } from 'my_module';上面代码中，虽然 foo和 bar在两个语句中加载，但是它们对应的是同一个 my_module模块。也就是说，import语句是 Singleton 模式。模块整体加载：除了指定加载某个输出值，还可以使用整体加载，即用星号（*）指定一个对象，所有输出值都加载在这个对象上面。下面是一个 circle.js文件，它输出两个方法 area和 circumference。// circle.js export function area(radius) { return Math.PI * radius * radius; } export function circumference(radius) { return 2 * Math.PI * radius; }现在，加载这个模块。// main.js import { area, circumference } from './circle'; console.log('圆面积：' + area(4)); console.log('圆周长：' + circumference(14));上面写法是逐一指定要加载的方法，整体加载的写法如下。import * as circle from './circle'; console.log('圆面积：' + circle.area(4)); console.log('圆周长：' + circle.circumference(14));注意，模块整体加载所在的那个对象（上例是 circle），应该是可以静态分析的，所以不允许运行时改变。下面的写法都是不允许的。import * as circle from './circle'; // 下面两行都是不允许的 circle.foo = 'hello'; circle.area = function () {};export default命令概述： 使用 import命令的时候，用户需要知道所要加载的变量名或函数名，否则无法加载。但是，用户肯定希望快速上手，未必愿意阅读文档，去了解模块有哪些属性和方法。为了给用户提供方便，让他们不用阅读文档就能加载模块，就要用到 export default命令，为模块指定默认输出。// export-default.js export default function () { console.log('foo'); }上面代码是一个模块文件 export-default.js，它的默认输出是一个函数。其他模块加载该模块时，import命令可以为该匿名函数指定任意名字。// import-default.js import customName from './export-default'; customName(); // 'foo'上面代码的 import命令，可以用任意名称指向 export-default.js输出的方法，这时就不需要知道原模块输出的函数名。需要注意的是，这时 import命令后面，不使用大括号。export default命令用在非匿名函数前，也是可以的。// export-default.js export default function foo() { console.log('foo'); } // 或者写成 function foo() { console.log('foo'); } export default foo;上面代码中，foo函数的函数名 foo，在模块外部是无效的。加载的时候，视同匿名函数加载。下面比较一下默认输出和正常输出。// 第一组 export default function crc32() { // 输出 // ... } import crc32 from 'crc32'; // 输入 // 第二组 export function crc32() { // 输出 // ... }; import {crc32} from 'crc32'; // 输入上面代码的两组写法，第一组是使用 export default时，对应的 import语句不需要使用大括号；第二组是不使用 export default时，对应的 import语句需要使用大括号。export default命令用于指定模块的默认输出。显然，一个模块只能有一个默认输出，因此 export default命令只能使用一次。所以，import命令后面才不用加大括号，因为只可能唯一对应 export default命令。本质上，export default就是输出一个叫做 default的变量或方法，然后系统允许你为它取任意名字。所以，下面的写法是有效的。// modules.js function add(x, y) { return x * y; } export {add as default}; // 等同于 // export default add; // app.js import { default as foo } from 'modules'; // 等同于 // import foo from 'modules';正是因为 export default命令其实只是输出一个叫做 default的变量，所以它后面不能跟变量声明语句。// 正确 export var a = 1; // 正确 var a = 1; export default a; // 错误 export default var a = 1;上面代码中，export default a的含义是将变量 a的值赋给变量 default。所以，最后一种写法会报错。同样地，因为 export default命令的本质是将后面的值，赋给 default变量，所以可以直接将一个值写在 export default之后。// 正确 export default 42; // 报错 export 42;上面代码中，后一句报错是因为没有指定对外的接口，而前一句指定对外接口为 default。有了 export default命令，输入模块时就非常直观了，以输入 lodash 模块为例。import _ from 'lodash';如果想在一条 import语句中，同时输入默认方法和其他接口，可以写成下面这样。import _, { each, forEach } from 'lodash';对应上面代码的 export语句如下。export default function (obj) { // ··· } export function each(obj, iterator, context) { // ··· } export { each as forEach };上面代码的最后一行的意思是，暴露出 forEach接口，默认指向 each接口，即 forEach和 each指向同一个方法。export default也可以用来输出类。// MyClass.js export default class { ... } // main.js import MyClass from 'MyClass'; let o = new MyClass();Promise 对象 【扩展】概述： Promise 是异步编程的一种解决方案，比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现，ES6 将其写进了语言标准，统一了用法，原生提供了 Promise对象。所谓 Promise，简单说就是一个容器，里面保存着某个未来才会结束的事件（通常是一个异步操作）的结果。从语法上说，Promise 是一个对象，从它可以获取异步操作的消息。Promise 提供统一的 API，各种异步操作都可以用同样的方法进行处理。ES6 规定，Promise对象是一个构造函数，用来生成 Promise实例。下面代码创造了一个 Promise实例。const promise = new Promise(function(resolve, reject) { // 这里执行对应的异步操作代码 if (/* 异步操作成功 */){ resolve(value); } else { reject(error); } });Promise构造函数接受一个函数作为参数，该函数的两个参数分别是 resolve和 reject。它们是两个函数，由 JavaScript 引擎提供，不用自己部署。resolve函数的作用是，将 Promise对象的状态从“未完成”变为“成功”（即从 pending 变为 resolved），在异步操作成功时调用，并将异步操作的结果，作为参数传递出去；reject函数的作用是，将 Promise对象的状态从“未完成”变为“失败”（即从 pending 变为 rejected），在异步操作失败时调用，并将异步操作报出的错误，作为参数传递出去。Promise实例生成以后，可以用 then方法分别指定 resolved状态和 rejected状态的回调函数。promise.then(function(value) { // success }, function(error) { // failure });then方法可以接受两个回调函数作为参数。第一个回调函数是 Promise对象的状态变为 resolved时调用，第二个回调函数是 Promise对象的状态变为 rejected时调用。这两个函数都是可选的，不一定要提供。它们都接受 Promise对象传出的值作为参数。下面是一个 Promise对象的简单例子。function timeout(ms) { return new Promise((resolve, reject) => { //模拟异步请求耗时 setTimeout(() => { //产生一个随机数字 let num = Math.floor(Math.random() * 10 + 1); //如果产生的随机数是偶数，则表示响应成功 if (num % 2 == 0) { resolve(num); //正常返回数据 } //否则响应失败 reject(`请求失败，错误编号：${num}`); //返回包装数据 }, ms); }); } //请求方法 timeout(100).then((value) => { console.log(value); });上面代码中，timeout方法返回一个 Promise实例，表示一段时间以后才会发生的结果。过了指定的时间（ms参数）以后，Promise实例的状态变为 resolved或 reject，就会触发 then方法绑定的回调函数。Promise 新建后就会立即执行。let promise = new Promise(function(resolve, reject) { console.log('Promise'); resolve(); }); promise.then(function() { console.log('resolved.'); }); console.log('Hi!'); // Promise // Hi! // resolved上面代码中，Promise 新建后立即执行，所以首先输出的是 Promise。然后，then方法指定的回调函数，将在当前脚本所有同步任务执行完才会执行，所以 resolved最后输出。下面是异步加载图片的例子。function loadImageAsync(url) { return new Promise(function (resolve, reject) { const image = new Image(); image.src = url; //指定图片土建 //当图片成功加载后，则返回图片数据 image.onload = function () { resolve(image); }; //当图片加载失败时，则引发异常 image.onerror = function () { throw '图片加载失败'; }; //设置一个定时器，模拟图片超时加载,200毫秒后， setTimeout(() => { reject('图片加载超时') }, 500); }) } //调用方法 loadImageAsync('https://www.baidu1.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png') .then((res) => { console.log(res); }, (err) => { console.log(err); }).catch((err) => { console.log('错误：' + err); }).finally(() => { console.log('方法执行结束'); })上面代码中，使用 Promise包装了一个图片加载的异步操作。如果加载成功，就调用 resolve方法，否则就调用 reject方法。更多的内容请，请访问：https://es6.ruanyifeng.com/#docs/promise[scode type="blue" size="simple"]▶️ 开始学习下一章: JavaScript+Jquery+ES6入门到放弃之jQuery基础[/scode]
JavaScript+jQuery+ES6入门到放弃之ECMAScript 6 基础
ECMAScript 6简介概述ECMAScript 6.0（以下简称 ES6）是 JavaScript 语言的下一代标准，已经在 2015 年 6 月正式发布了。它的目标，是使得 JavaScript 语言可以用来编写复杂的大型应用程序，成为企业级开发语言。ECMAScript 和 JavaScript 的关系要讲清楚这个问题，需要回顾历史。1996 年 11 月，JavaScript 的创造者 Netscape 公司，决定将 JavaScript 提交给标准化组织 ECMA，希望这种语言能够成为国际标准。次年，ECMA 发布 262 号标准文件（ECMA-262）的第一版，规定了浏览器脚本语言的标准，并将这种语言称为 ECMAScript，这个版本就是 1.0 版。该标准从一开始就是针对 JavaScript 语言制定的，但是之所以不叫 JavaScript，有两个原因。一是商标，Java 是 Sun 公司的商标，根据授权协议，只有 Netscape 公司可以合法地使用 JavaScript 这个名字，且 JavaScript 本身也已经被 Netscape 公司注册为商标。二是想体现这门语言的制定者是 ECMA，不是 Netscape，这样有利于保证这门语言的开放性和中立性。因此，ECMAScript 和 JavaScript 的关系是，前者是后者的规格，后者是前者的一种实现（另外的 ECMAScript 方言还有 JScript 和 ActionScript）。日常场合，这两个词是可以互换的。ES6 与 ECMAScript 2015 的关系2011 年，ECMAScript 5.1 版发布后，就开始制定 6.0 版了。因此，ES6 这个词的原意，就是指 JavaScript 语言的下一个版本。但是，因为这个版本引入的语法功能太多，而且制定过程当中，还有很多组织和个人不断提交新功能。事情很快就变得清楚了，不可能在一个版本里面包括所有将要引入的功能。常规的做法是先发布 6.0 版，过一段时间再发 6.1 版，然后是 6.2 版、6.3 版等等。但是，标准的制定者不想这样做。他们想让标准的升级成为常规流程：任何人在任何时候，都可以向标准委员会提交新语法的提案，然后标准委员会每个月开一次会，评估这些提案是否可以接受，需要哪些改进。如果经过多次会议以后，一个提案足够成熟了，就可以正式进入标准了。这就是说，标准的版本升级成为了一个不断滚动的流程，每个月都会有变动。标准委员会最终决定，标准在每年的 6 月份正式发布一次，作为当年的正式版本。接下来的时间，就在这个版本的基础上做改动，直到下一年的 6 月份，草案就自然变成了新一年的版本。这样一来，就不需要以前的版本号了，只要用年份标记就可以了。ES6 的第一个版本，就这样在 2015 年 6 月发布了，正式名称就是《ECMAScript 2015 标准》（简称 ES2015）。2016 年 6 月，小幅修订的《ECMAScript 2016 标准》（简称 ES2016）如期发布，这个版本可以看作是 ES6.1 版，因为两者的差异非常小（只新增了数组实例的 includes方法和指数运算符），基本上是同一个标准。根据计划，2017 年 6 月发布 ES2017 标准。因此，ES6 既是一个历史名词，也是一个泛指，含义是 5.1 版以后的 JavaScript 的下一代标准，涵盖了 ES2015、ES2016、ES2017 等等，而 ES2015 则是正式名称，特指该年发布的正式版本的语言标准。本书中提到 ES6 的地方，一般是指 ES2015 标准，但有时也是泛指“下一代 JavaScript 语言”。let和const命令let命令概述： ES6 新增了 let命令，用来声明变量。它的用法类似于 var，但是所声明的变量，只在 let命令所在的代码块内有效。语法：let 变量名 = 值;[scode type="red" size="simple"]⚠️注意：在使用let命令声明变量时包含如下特性：let命令作用域只局限于当前代码块// 创建一个作用域 { var a = 10; let b = 1; } console.log(a); //输出：10 console.log(b); //Uncaught ReferenceError: b is not definedlet命令声明的变量作用域不会被提前（不会被预解析），暂时性死区console.log(a); //undefined var a = 10; console.log(b); //Uncaught ReferenceError: Cannot access 'b' before initialization (初始化前无法访问“b”) let b = 20; var tmp = 123; if (true) { tmp = 'abc'; // Uncaught ReferenceError: Cannot access 'tmp' before initialization (暂时性死区) let tmp; }在相同的作用域下，不允许同时声明相同的let变量var a = 10; var a = 20; console.log(a); let b = 10; let b = 20; // Uncaught SyntaxError: Identifier 'b' has already been declared (标识符“b”已声明) console.log(b);[/scode]块级作用域：ES5 只有全局作用域和函数作用域，没有块级作用域，这带来很多不合理的场景。第一种场景，内层变量可能会覆盖外层变量。var tmp = new Date(); function f() { console.log(tmp); if (false) { var tmp = 'hello world'; } } f(); // undefined⭐️ 上面代码中， if代码块的外部使用外层的 tmp变量，内部使用内层的 tmp变量。但是，函数 f执行后，输出结果为 undefined，原因在于变量提升，导致内层的 tmp变量覆盖了外层的 tmp变量。第二种场景，用来计数的循环变量泄露为全局变量。var s = 'hello'; for (var i = 0; i < s.length; i++) { console.log(s[i]); } console.log(i); // 5⭐️上面代码中，变量 i只用来控制循环，但是循环结束后，它并没有消失，泄露成了全局变量。在ES6中新增的块级作用域：function show1() { let a = 10; if (true) { let a = 20; } console.log(a); } function show2() { var a = 10; if (true) { var a = 20; } console.log(a); } //调用方法,查看输出结果 show1(); show2(); /** * 输出结果为: * 10 * 20 */⭐️上面的函数有两个代码块，都声明了变量 a，运行后输出 5。这表示外层代码块不受内层代码块的影响。如果两次都使用 var定义变量 a，最后输出的值才是 10。在ES6中，允许块级作用域的任意嵌套。const命令概述： const声明一个只读的常量。一旦声明，常量的值就不能改变。[scode type="red" size="simple"]⚠️注意：const在声明时，必须赋值。只在声明所在的块级作用域内有效。const命令声明的常量不提升。存在暂时性死区，只能在声明的位置后面使用声明的常量，也与let一样不可重复声明[/scode]案例演示：// 定义常量 const PI = 3.14; // 修改常量值 PI = 10; // Assignment to constant variable (给常量赋值) // 定义常量未赋值 const Week; // Missing initializer in const declaration(常量声明中缺少初始值设定项)查看下方代码，并说出运行结果：function f1() { const m = 10; if (true) { let n = 10; m = 20; } console.log(n); } f1();解构赋值数组解构赋值概述：ES6 允许按照一定模式，从数组和对象中提取值，对变量进行赋值，这被称为解构（Destructuring）。以前，为变量赋值，只能直接指定值：let a = 1; let b = 2; let c = 3;ES6 允许写成下面这样：let [a, b, c] = [1, 2, 3];上面代码表示，可以从数组中提取值，按照对应位置，对变量赋值。本质上，这种写法属于“模式匹配”，只要等号两边的模式相同，左边的变量就会被赋予对应的值。案例演示：// 可以嵌套 let [foo, [[bar], baz]] = [1, [[2], 3]]; foo // 1 bar // 2 baz // 3 // 可以忽略 let [ , , third] = ["foo", "bar", "baz"]; third // "baz" // 不完全解构 let [x, y] = [1, 2, 3]; x // 1 y // 2 // 剩余运算符 let [head, ...tail] = [1, 2, 3, 4]; head // 1 tail // [2, 3, 4] // 解构默认值 let [x, y = 'b'] = ['a']; console.log(x); //'a' console.log(y); //'b' // 如果解构不成功，变量的值就等于undefined let [x, y, ...z] = ['a']; x // "a" y // undefined z // []扩展案例-使用结构实现两数交换：let [x, y] = [1, 2]; console.log(`x:${x},y:${y}`); //x:1,y:2 //开始交换 [x, y] = [y, x]; console.log(`x:${x},y:${y}`); //x:2,y:1对象解构赋值概述：解构不仅可以用于数组，还可以用于对象。对象的解构与数组有一个重要的不同。数组的元素是按次序排列的，变量的取值由它的位置决定；而对象的属性没有次序，变量必须与属性同名，才能取到正确的值。let { bar, foo } = { foo: 'aaa', bar: 'bbb' }; foo // "aaa" bar // "bbb" let { baz } = { foo: 'aaa', bar: 'bbb' }; baz // undefined上面代码中，等号左边的两个变量的次序，与等号右边两个同名属性的次序不一致，但是对取值完全没有影响。而第二个例子中的变量没有对应的同名属性，导致取不到值，最后等于 undefined。⭐️如果解构失败，变量的值等于 undefined。案例演示：//可嵌套 let obj = { p: ['hello', { y: 'world' }] }; let { p: [x, { y }] } = obj; console.log(x); //hello console.log(y); //world //可忽略 let obj = { p: ['hello', { y: 'world' }, 'ECMAScript'] }; let { p: [x, { y }] } = obj; console.log(x); //hello console.log(y); //world //不完全解构 let obj = { p: [{ y: 'world' }, 'hello'] }; let { p: [{ y }, x, z] } = obj; console.log(x, y, z); //hello ,world,undefined //剩余运算符 let { a, b, ...rest } = { a: 10, b: 20, c: 30, d: 40 }; console.log(a, b); //10,20 console.log(rest); //{c: 30, d: 40} //解构默认值 let { a = 10, b = 5 } = { a: 3 }; console.log(a); //3 console.log(b); //5分析下面的代码，它的作用是什么：let { log } = console; log("msg");分析下面的代码，输出结果是什么：let obj = {p: ['hello', {y: {z: 'world'}}, 'ECMAScript'] }; let {p: [x, { y }] } = obj; console.log(x); console.log(y);字符串解构赋值概述：字符串也可以解构赋值。这是因为此时，字符串被转换成了一个类似数组的对象。数组的对象都有一个 length属性，因此还可以对这个属性解构赋值。const [a, b, c, d, e] = 'hello'; console.log(a) // "h" console.log(b) // "e" console.log(c) // "l" console.log(d) // "l" console.log(e) // "o" let { length: l } = 'hello' console.log(l); //5函数参数解构赋值概述： 除了数组、对象、字符串等可以使用解构赋值外，函数的参数也可以使用解构赋值。function add([x, y]){ return x + y; } let x = add([1, 2]); console.log(x); //3上面代码中，函数 add的参数表面上是一个数组，但在传入参数的那一刻，数组参数就被解构成变量 x和 y。对于函数内部的代码来说，它们能感受到的参数就是 x和 y。变量解构的用途变量值的交换从函数返回多个值函数参数的定义提取 JSON 数据函数参数的默认值遍历 Map 结构导入模块的指定方法 【更多请访问：ECMAScript 6 入门-变量的解构赋值-用途】函数扩展函数参数默认值概述： ES6 之前，不能直接为函数的参数指定默认值，只能采用变通的方法。function log(x, y) { y = y || 'World'; console.log(x, y); } log('Hello') // Hello World log('Hello', 'China') // Hello China log('Hello', '') // Hello World上面代码检查函数 log()的参数 y有没有赋值，如果没有，则指定默认值为 World。这种写法的缺点在于，如果参数 y赋值了，但是对应的布尔值为 false，则该赋值不起作用。就像上面代码的最后一行，参数 y等于空字符，结果被改为默认值。ES6的新写法：function log(x, y = 'World') { console.log(x, y); } log('Hello') // Hello World log('Hello', 'China') // Hello China log('Hello', '') // Hello在上面的代码中，ES6 的写法比 ES5 简洁许多，而且非常自然。[scode type="red" size="simple"]⚠️注意：参数变量是默认声明的，所以不能用 let或 const再次声明。function foo(x = 5) { let x = 1; // error const x = 2; // error }使用参数默认值时，函数不能有同名参数。// 不报错 function foo(x, x, y) { // ... } // 报错 function foo(x, x, y = 1) { // ... } // SyntaxError: Duplicate parameter name not allowed in this context参数默认值一定要放在普通参数之后。【该标准已被更改】function foo1(x, y = 10) { console.log(x, y) } function foo2(x = 10, y) { //在之前的标准，该函数的定义将会报错，在新标准中，则不会 console.log(x, y) } foo1(1); //1,10 foo2(1); //1,undefined[/scode]解构赋值与默认值结合使用演示案例：function foo({x, y = 5}) { console.log(x, y); } foo({}) // undefined 5 foo({x: 1}) // 1 5 foo({x: 1, y: 2}) // 1 2 foo() // TypeError: Cannot read property 'x' of undefined上面代码只使用了对象的解构赋值默认值，没有使用函数参数的默认值。只有当函数 foo()的参数是一个对象时，变量 x和 y才会通过解构赋值生成。如果函数 foo()调用时没提供参数，变量 x和 y就不会生成，从而报错。通过提供函数参数的默认值，就可以避免这种情况。function foo({x, y = 5} = {}) { console.log(x, y); } foo() // undefined 5函数的 length 属性指定了默认值以后，函数的 length属性，将返回没有指定默认值的参数个数。也就是说，指定了默认值后，length属性将失效。(function (a) {}).length // 1 (function (a = 5) {}).length // 0 (function (a, b, c = 5) {}).length // 2上面代码中，length属性的返回值，等于函数的参数个数减去指定了默认值的参数个数。比如，上面最后一个函数，定义了 3 个参数，其中有一个参数 c指定了默认值，因此 length属性等于 3减去 1，最后得到 2。这是因为 length属性的含义是，该函数预期传入的参数个数。某个参数指定默认值以后，预期传入的参数个数就不包括这个参数了。同理，后文的 rest 参数也不会计入 length属性。如果设置了默认值的参数不是尾参数，那么 length属性也不再计入后面的参数了。(function (a = 0, b, c) {}).length // 0 (function (a, b = 1, c) {}).length // 1作用域一旦设置了参数的默认值，函数进行声明初始化时，参数会形成一个单独的作用域（context）。等到初始化结束，这个作用域就会消失。这种语法行为，在不设置参数默认值时，是不会出现的。var x = 1; function f(x, y = x) { console.log(y); } f(2) // 2上面代码中，参数 y的默认值等于变量 x。调用函数 f时，参数形成一个单独的作用域。在这个作用域里面，默认值变量 x指向第一个参数 x，而不是全局变量 x，所以输出是 2。rest参数和name属性rest参数概述： ES6 引入 rest 参数（形式为 ...变量名），用于获取函数的多余参数，这样就不需要使用 arguments对象了。rest 参数搭配的变量是一个数组，该变量将多余的参数放入数组中。function add(...values) { let sum = 0; for (var val of values) { sum += val; } return sum; } let res = add(2, 5, 3) console.log(res); // 10上面代码的 add函数是一个求和函数，利用 rest 参数，可以向该函数传入任意数目的参数。[scode type="red" size="simple"]⚠️注意：rest 参数之后不能再有其他参数（即只能是最后一个参数），否则会报错。[/scode]name属性概述：函数的 name属性，返回该函数的函数名。function foo() { } console.log(foo.name); // "foo"这个属性早就被浏览器广泛支持，但是直到 ES6，才将其写入了标准。需要注意的是，ES6 对这个属性的行为做出了一些修改。如果将一个匿名函数赋值给一个变量，ES5 的 name属性，会返回空字符串，而 ES6 的 name属性会返回实际的函数名。var f = function () {}; // ES5 f.name // "" // ES6 f.name // "f"上面代码中，变量 f等于一个匿名函数，ES5 和 ES6 的 name属性返回的值不一样。如果将一个具名函数赋值给一个变量，则 ES5 和 ES6 的 name属性都返回这个具名函数原本的名字。const bar = function baz() {}; // ES5 bar.name // "baz" // ES6 bar.name // "baz"箭头函数在ES6 允许使用“箭头”（=>）定义函数。// 箭头函数定义 var f = v => v; // 等同于 var f = function (v) { return v; };如果箭头函数不需要参数或需要多个参数，就使用一个圆括号代表参数部分。var f = () => 5; // 等同于 var f = function () { return 5 }; var sum = (num1, num2) => num1 + num2; // 等同于 var sum = function(num1, num2) { return num1 + num2; };如果箭头函数的代码块部分多于一条语句，就要使用大括号将它们括起来，并且使用 return语句返回。var calc = (num1, num2, opt) => { let result = 0; switch (opt) { case "+": result = num1 + num2; break; case "-": result = num1 - num2; break; //... } return result; }[scode type="red" size="simple"]⚠️箭头函数有几个使用注意点。（1）箭头函数没有自己的 this对象。⭐️（2）不可以当作构造函数，也就是说，不可以对箭头函数使用 new命令，否则会抛出一个错误。（3）不可以使用 arguments对象，该对象在函数体内不存在。如果要用，可以用 rest 参数代替。（4）不可以使用 yield命令，因此箭头函数不能用作 Generator 函数。上面四点中，最重要的是第一点。对于普通函数来说，内部的 this指向函数运行时所在的对象，但是这一点对箭头函数不成立。它没有自己的 this对象，内部的 this就是定义时上层作用域中的 this。也就是说，箭头函数内部的 this指向是固定的，相比之下，普通函数的 this指向是可变的。[/scode]案例分析：function Timer() { this.s1 = 0; this.s2 = 0; // 箭头函数 setInterval(() => this.s1++, 1000); // 普通函数 setInterval(function () { this.s2++; }, 1000); } var timer = new Timer(); setTimeout(() => console.log('s1: ', timer.s1), 3100); setTimeout(() => console.log('s2: ', timer.s2), 3100); // s1: 3 // s2: 0上面代码中，Timer函数内部设置了两个定时器，分别使用了箭头函数和普通函数。前者的 this绑定定义时所在的作用域（即 Timer函数），后者的 this指向运行时所在的作用域（即全局对象）。所以，3100 毫秒之后，timer.s1被更新了 3 次，而 timer.s2一次都没更新。注意：箭头函数的this不是调用的时候决定的，而是在定义的时候处在的对象就是它的this箭头函数不适用于下列场景第一个场合是定义对象的方法，且该方法内部包括 this。const cat = { lives: 9, jumps: () => { this.lives--; } }上面代码中，cat.jumps()方法是一个箭头函数，这是错误的。调用 cat.jumps()时，如果是普通函数，该方法内部的 this指向 cat；如果写成上面那样的箭头函数，使得 this指向全局对象，因此不会得到预期结果。这是因为对象不构成单独的作用域，导致 jumps箭头函数定义时的作用域就是全局作用域。var s = 21; const obj = { s: 42, m: () => console.log(this.s) }; obj.m() // 21上面例子中，obj.m()使用箭头函数定义。JavaScript 引擎的处理方法是，先在全局空间生成这个箭头函数，然后赋值给 obj.m，这导致箭头函数内部的 this指向全局对象，所以 obj.m()输出的是全局空间的 21，而不是对象内部的 42。上面的代码实际上等同于下面的代码。var s = 21; var m = () => console.log(this.s); const obj = { s: 42, m: m }; obj.m() // 21第二个场合是需要动态this的时候，也不应使用箭头函数：let box1 = document.getElementById('content-1'); let box2 = document.getElementById('content-2'); box1.onclick = () => { this.style.border = "2px solid red"; //执行错误,this: Window } box2.onclick = function () { this.style.border = "2px solid red"; //执行成功,this: div#content-2 }第三个场合是不能作为构造函数。第四个场合是不能用在原型方法绑定：function Person() { this.userName = "张三丰"; } Person.prototype.sayHi = () => { console.log("大家好,我叫:" + this.userName); } let stu = new Person(); stu.sayHi(); // 大家好,我叫:undefined[scode type="blue" size="simple"]▶️ 开始学习下一章: JavaScript+Jquery+ES6入门到放弃之ECMAScript 6 进阶[/scode]
对象概述： 在JavaScript中的定义是：几乎“所有事物”都是对象，对象是相关属性和方法的集合体。创建对象创建内置对象在JavaScript中我们可以使用 new关键字创建对象，例如创建一个Date对象：var date = new Date();、创建一个数组：var arr = new Array();，当然对于 Math对象所有属性和方法都是静态的，我们不需要使用 new关键字创建它，而是直接通过 Math关键字使用。创建自定义对象语法：var 对象名 = new Object();演示案例：/** * 在下方案例中，我们将创建对象并为其添加属性和方法 * 创建person对象 */ var person = new Object(); //给person对象添加属性 person.name = '张三'; person.age = 18; person.sex = '男生'; person.birthday = '2004-10-1'; //给person对象添加方法 person.sayHi = function () { console.log(`大家好,我是:${this.name},今年:${this.age}岁,我是${this.sex}生,我的出生日期是:${this.birthday}`) } //调用方法 person.sayHi(); //输出: 大家好,我是:张三,今年:18岁,我是男生,我的出生日期是:2004-10-1[scode type="yellow" size="simple"]在 sayHi方法中，我们使用 this.属性名用于获取person对应的属性，this是指当前person对象。[/scode]通过字面量创建对象【推荐】：/** * 在下方案例中，我们将创建对象并为其添加属性和方法 * 通过字面量赋值创建person对象 */ var person = { name: '张三', age: 18, sex: '男生', birthday: '2004-10-1', sayHi: function () { console.log(`大家好,我是:${this.name},今年:${this.age}岁,我是${this.sex}生,我的出生日期是:${this.birthday}`) } } //调用方法 person.sayHi(); //输出: 大家好,我是:张三,今年:18岁,我是男生生,我的出生日期是:2004-10-1简单工厂模式在上述案例中，无论是基于Object对象的方式还是使用字面量的方式创建对象，他们都有一个明显的缺陷：如果我们需要创建多个对象时，就需要产生大量的重复代码。🤔如何解决这个问题呢？很多开发者在软件开发过程中通常会将设计模式运用到项目中来。[font color="#1E90FF"]所谓的模式是指在某一个环境下对于某个问题的一种解决方案，在软件开发中，有许多优秀的模式被保留下来供后续人员学习使用，我们就称其为：设计模式。[/font]常见的设计模式有：工厂模式、单例模式、观察者模式、抽象工厂模式等等…工厂模式是最常用的一种用来创建对象的设计模式。通过封装不会暴露创建对象的具体逻辑，而是将逻辑封装在一个函数中，那么这个函数我们就可以称之为：工厂函数。工厂模式更具抽象程度可以分为以下三种：简单工厂模式工厂方法模式抽象工厂模式我们也将使用简单工厂模式来解决上面的问题。演示案例：/** * 创建一个工厂函数,用于创建person对象 * @param {*} name 姓名 * @param {number} age 年龄 * @param {*} sex 性别 * @param {*} birthday 出生日期 * @returns person对象 */ function createPerson(name, age, sex, birthday) { var person = { name: name, age: age, sex: sex, birthday: birthday, sayHi: function () { console.log(`大家好,我是:${this.name},今年:${this.age}岁,我是${this.sex}生,我的出生日期是:${this.birthday}`) } } return person; } //调用工厂函数获取不同的对象 var lisi = createPerson("李四", 16, "男", "2006-1-18"); var wangwu = createPerson("王五", 20, "男", "2002-2-10"); //调用person对象中的sayHi方法 lisi.sayHi(); wangwu.sayHi(); /** * 依次输出: * 大家好,我是:李四,今年:16岁,我是男生,我的出生日期是:2006-1-18 * 大家好,我是:王五,今年:20岁,我是男生,我的出生日期是:2002-2-10 */[scode type="yellow" size="simple"]📋笔记：在上述案例中，createPerson()就是一个工厂，我们可以重复多次的调用这个工程函数用于创建多个的对象，从而避免了大量的重复代码。📝弊端：使用简单工厂模式创建对象虽然解决了创建多个对象时需要大量的重复代码的问题，但是也有一个新的问题也随之出现即：对象识别困难（无法知道创建出来的对象是什么，在上述案例中无法体现）。在实际开发中可以使用构造函数来解决此问题。[/scode]构造函数和原型对象构造函数概述： 构造函数用来创建特定的对象。在实际开发中，我们可以创建自定义的构造函数，从而可以自定义对象类型的属性和方法。构造函数和普通函数一致，只是当我们使用 new关键字的时候，它就能创建一个新的对象。在构造函数中所使用的 this关键字，代表的就是被创建的对象。案例演示：/** * 构造函数,用于创建Person对象 * @param {*} name 姓名 * @param {number} age 年龄 * @param {*} sex 性别 * @param {*} birthday 出生日期 * @returns person对象 */ function Person(name, age, sex, birthday) { //首字母大写，以区别于其他函数 this.name = name; this.age = age; this.sex = sex; this.birthday = birthday; this.sayHi = function () { console.log(`大家好,我是:${this.name},今年:${this.age}岁,我是${this.sex}生,我的出生日期是:${this.birthday}`) } } //使用new关键字调用构造函数获得新的对象 var lisi = new Person("李四", 16, "男", "2006-1-18"); var wangwu = new Person("王五", 20, "男", "2002-2-10"); //调用方法 lisi.sayHi(); wangwu.sayHi(); /** * 依次输出: * 大家好,我是:李四,今年:16岁,我是男生,我的出生日期是:2006-1-18 * 大家好,我是:王五,今年:20岁,我是男生,我的出生日期是:2002-2-10 */[scode type="yellow" size="simple"]⚠️注意：在创建构造函数的时候，需要将函数名称的首字母改为大写，采用：采用Pascal(帕斯卡)命名法，即所有单词首字母均需大写，这是为了能够和普通函数予以区分。💡构造函数与其他的创建对象的方式的不同：没有显示的创建对象直接将属性和方法赋值给了this对象没有return语句🔦构造函数执行过程中会经历以下4个步骤：创建一个新对象将构造函数的作用域赋给新对象，因此this就指向了这个新对象执行构造函数中的代码，为这个新对象添加属性及方法 【📌】返回新对象[/scode]constructor属性和instanceof操作符对象中包含 constructor属性，指向其构造函数。使用 instanceof操作符检测对象类型。演示案例：/** * 构造函数,用于创建Person对象 * @param {*} name 姓名 * @param {number} age 年龄 * @param {*} sex 性别 * @param {*} birthday 出生日期 * @returns person对象 */ function Person(name, age, sex, birthday) { //首字母大写，以区别于其他函数 this.name = name; this.age = age; this.sex = sex; this.birthday = birthday; this.sayHi = function () { console.log(`大家好,我是:${this.name},今年:${this.age}岁,我是${this.sex}生,我的出生日期是:${this.birthday}`) } } /** * 创建学生构造函数 */ function Student() { this.name = ""; } //使用new关键字调用构造函数获得新的对象 var lisi = new Person("李四", 16, "男", "2006-1-18"); var wangwu = new Person("王五", 20, "男", "2002-2-10"); /** * 知识点: * ⭐️ 所有的对象都具备constructor属性,该属性指向当前对象的构造函数 * ⭐️ instanceof操作符用于检测对象所属类型 */ console.log(lisi.constructor == Person); //true console.log(lisi.constructor == Student); //false console.log(lisi instanceof Person); //true console.log(lisi instanceof Student); //false console.log(lisi instanceof Object); //true原型对象在之前的案例中，我们可以使用构造函数来实例化不同的对象，我们也是通过：执行构造函数中的代码，为这个新对象添加属性及方法。 这也就意味着每当我们实例化一个新对象时，构造函数中的方法都将会被重新执行：我们可以优化上面的代码，通过函数定义的方式把 sayHi()方法转移到构造函数的外部，所有实例共享全局作用域中定义的同一个 sayHello()函数：/** * 构造函数,用于创建Person对象 * @param {*} name 姓名 * @param {number} age 年龄 * @param {*} sex 性别 * @param {*} birthday 出生日期 * @returns person对象 */ function Person(name, age, sex, birthday) { //首字母大写，以区别于其他函数 this.name = name; this.age = age; this.sex = sex; this.birthday = birthday; this.sayHi = sayHello; //将构造函数中的sayHi指向全局sayHello函数,达到所有实例共享同一个函数的效果 } /** * 定义全局sayHello函数 */ function sayHello() { console.log(`大家好,我是:${this.name},今年:${this.age}岁,我是${this.sex}生,我的出生日期是:${this.birthday}`) } //使用new关键字调用构造函数获得新的对象 var lisi = new Person("李四", 16, "男", "2006-1-18"); var wangwu = new Person("王五", 20, "男", "2002-2-10"); //输出打印 lisi.sayHi(); //大家好,我是:李四,今年:16岁,我是男生,我的出生日期是:2006-1-18 wangwu.sayHi(); //大家好,我是:王五,今年:20岁,我是男生,我的出生日期是:2002-2-10 sayHello(); //大家好,我是:,今年:undefined岁,我是undefined生,我的出生日期是:undefined[scode type="red" size="simple"]弊端：我们将sayHello()修改成了全局函数，这也就意味着sayHello()函数可以直接被调用，但是当我们直接调用sayHello()时，系统所打印的数据全为：undefined，这也就发现了一个新的问题，sayHello()函数只能给某一个对象调用，这样的行为破坏了自定义类型的封装性。[/scode]🤔如何解决？无论何时，我们只要创建一个函数，就会按照特定的规则为这个函数创建一个 prototype(原型)属性，该属性指向原型对象。默认情况下，所有原型对象自动获得一个名为 constructor的属性，指回与之相关的构造函数。我们可以通过 prototype(原型)对象方式所添加的属性和方法在所有的实例中都能访问和使用，语法格式：构造函数名.prototype.新属性或新方法;演示案例：/** * 创建构造函数 * 声明构造函数 Student 后，函数就有了一个与之关联的原型对象 */ function Student() { console.log("Student被创建"); } /** * 给 Student 关联的prototype对象添加属性和方法 * 这些属性和方法为各个对象实例所共享 */ Student.prototype.name = "张三"; Student.prototype.age = 18; Student.prototype.email = "zhangsan@163.com"; Student.prototype.hobby = "打球"; /** * 通过 prototype 添加方法 */ Student.prototype.showName = function () { console.log(`大家好,我叫:${this.name}`) } /** * 调用 Student 构造函数，创建对象实例 * 在创建对象时,进入对应的构造函数 * 对象实例调用原型对象中共享的方法 */ var stu1 = new Student(); stu1.showName(); var stu2 = new Student(); stu2.showName(); var stu3 = new Student(); /** * 输出内容: * Student被创建 * 大家好,我叫:张三 * Student被创建 * 大家好,我叫:张三 * Student被创建 */[scode type="green" size="simple"]💻关系说明：构造函数、原型对象和对象实例是3个完全不同的对象（在JavaScript中，函数也是对象）。实例通过内部的隐藏特性 Prototype链接到原型对象(在Chrome、Firefox和Safari浏览器中可以通过 __proto__访问该特性)，构造函数通过 prototype属性链接到原型对象。实例与构造函数没有并没有直接联系而是与原型对象有直接联系，原型对象中的 constructor属性指向对应的构造函数。同一个构造函数创建的多个实例，共享同一个原型对象。正常的原型链会终止于Object的原型对象，Object原型的原型是null。[/scode]原型层级查找：实例对象访问属性时，会按照属性名开始逐层查找。优先查找实例对象本身。如果当前实例找到了对应的属性名，则返回对应的属性值。如果没有找到这个属性，则会沿着指针进入原型对象继续查找。在原型对象上找到属性后，再返回属性值。/** * 创建构造函数 * 声明构造函数 Student 后，函数就有了一个与之关联的原型对象 */ function Student() { this.job = "写代码"; } Student.prototype.name = "张三"; Student.prototype.age = 18; Student.prototype.email = "zhangsan@163.com"; Student.prototype.hobby = "打球"; Student.prototype.showName = function () { console.log(`大家好,我叫:${this.name}`) } var stu = new Student(); stu.className = "2020级理工4班"; console.log(stu.className, stu.name, stu.job); //2020级理工4班 张三 写代码属性屏蔽：只要给对象实例添加一个属性，这个属性就会遮蔽原型对象上的同名属性。function Student() { } Student.prototype.name = "张三"; Student.prototype.age = 18; Student.prototype.email = "zhangsan@163.com"; Student.prototype.hobby = "打球"; Student.prototype.showName = function () { console.log(`大家好,我叫:${this.name}`) } var stu = new Student(); stu.name = "李四"; //覆盖原型属性name console.log(stu); // Student { name: '李四' } console.log(stu.__proto__); //{ name: '张三', age: 18, email: 'zhangsan@163.com', hobby: '打球', showName: ƒ, … } console.log(stu.name, stu.hobby); //李四 打球[scode type="green" size="simple"]🙌经验分享：原型模式适合封装方法，构造函数模式适合封装属性，综合两种模式的优点就有了组合模式。[/scode]🛠使用组合模式优化原始案例：/** * 构造函数,用于创建Person对象 * @param {*} name 姓名 * @param {number} age 年龄 * @param {*} sex 性别 * @param {*} birthday 出生日期 * @returns person对象 */ function Person(name, age, sex, birthday) { //首字母大写，以区别于其他函数 this.name = name; this.age = age; this.sex = sex; this.birthday = birthday; } /** * 使用 prototype 添加方法 */ Person.prototype.sayHi = function () { console.log(`大家好,我是:${this.name},今年:${this.age}岁,我是${this.sex}生,我的出生日期是:${this.birthday}`) } //使用new关键字调用构造函数获得新的对象 var lisi = new Person("李四", 16, "男", "2006-1-18"); var wangwu = new Person("王五", 20, "男", "2002-2-10"); //输出打印 lisi.sayHi(); //大家好,我是:李四,今年:16岁,我是男生,我的出生日期是:2006-1-18 wangwu.sayHi(); //大家好,我是:王五,今年:20岁,我是男生,我的出生日期是:2002-2-10原型案例扩展：我们在使用日期对象时，获取的时间都需要手动格式化，我们今天给他扩展一个格式化的方法。/** * 给Date对象的原型添加一个格式化方法 * yyyy-MM-dd → 2022-10-01 * yy-MM-dd h:m:s → 22-10-01 11:5:21 * @param {*} fmt 年(y)可以用 1-4 个占位符、月(M)、日(d)、小时(h)、分(m)、秒(s)、季度(q) 可以用 1-2 个占位符，毫秒(S)只能用 1 个占位符(是 1-3 位的数字) * @returns 格式化结果 */ Date.prototype.format = function (fmt) { var o = { "M+": this.getMonth() + 1, //月份 "d+": this.getDate(), //日 "h+": this.getHours(), //小时 "m+": this.getMinutes(), //分 "s+": this.getSeconds(), //秒 "q+": Math.floor((this.getMonth() + 3) / 3), //季度 "S": this.getMilliseconds() //毫秒 }; if (/(y+)/.test(fmt)) { fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length)); } for (var k in o) { if (new RegExp("(" + k + ")").test(fmt)) { fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); } } return fmt; } //如何使用 var d = new Date(); console.log(d); //Sun Sep 04 2022 11:08:10 GMT+0800 (中国标准时间) console.log(d.format('yyyy-MM-dd hh:mm:ss')); //2022-09-04 11:08:10继承概述：在面向对象编程中，继承是其中一大重要特性。通过继承可以减少大量的重复代码，也可以解决程序在后期维护和扩展上所遇到的问题。在其他的变成语言中，实现继承的主要方式有两种：实现类继承和接口继承。在是在ES6出现之前，JavaScript中并没有类和接口的概念，在ES6之前只能通过原型链来实现继承。【在之后的笔记中，我们会将ES6中的继承，本次笔记暂时跳过】。原型链概述：在ECMAScript中将原型链做为实现继承的主要方法，它的基本思想是利用原型让一个应用类型继承另一个应用类型的属性和方法。在JavaScript中，每个构造函数拥有一个原型对象，原型对象包含一个指向构造函数的内部属性（constructor），实例都包含一个指向原型对象的内部属性（__proto__），假如一个原型对象等于另一个类型的实例，另一个类型的原型对象又等于另一个类型的实力，如此层层递进，就构成了实例与原型的链条，这就是原型链。演示案例：/** * 构造函数:Person */ function Person() { this.foot = 2; } /** * 使用 prototype 添加方法 * @returns 获取腿的个数 */ Person.prototype.getFoot = function () { return this.foot; } /** * 构造函数:Woman */ function Woman() { this.head = 1; } //使用原型对象继承 Woman.prototype = new Person(); /** * 使用 prototype 添加方法 * @returns 获取头的个数 */ Woman.prototype.getHead = function () { return this.head; } //创建对象 var woman1 = new Woman(); alert(woman1.getFoot()); //调用父类方法: 输出 2 alert(woman1 instanceof Woman); //true alert(woman1 instanceof Person); //true alert(woman1 instanceof Object); //true[scode type="share" size="simple"]⚠️注意：📍woman1调用 getFoot()的步骤：搜索实例 → 搜索 Woman.prototype → 搜索 Person.prototype，搜索的过程要一环一环地前行到原型链的末端才会停下来。📍所有函数的默认原型都是Object对象的实例。[/scode]方法重写/** * 构造函数:Person */ function Person() { this.foot = 2; } /** * 使用 prototype 添加方法 * @returns 获取腿的个数 */ Person.prototype.getFoot = function () { return this.foot; } /** * 构造函数:Woman */ function Woman() { this.head = 1; } //使用原型对象继承 Woman.prototype = new Person(); /** * 使用 prototype 添加方法 * @returns 获取头的个数 */ Woman.prototype.getHead = function () { return this.head; } /** * 重写父类方法: getFoot * @returns */ Woman.prototype.getFoot = function () { return false; } //创建对象 var woman1 = new Woman(); console.log(woman1.getFoot()); //调用方法: 输出 false[scode type="share" size="simple"]⚠️注意：Woman的实例调用 getFoot()方法时，调用的是重写后的方法，但是通过Person的实例调用 getFoot()方法时，还会调用原来的方法[/scode]对象继承🤔代码分析，下面的代码会输出什么？/** * 构造函数:Person */ function Person() { this.money = 200; this.myCar = []; } Person.prototype.showMyAssets = function () { console.log(`我的余额还有:${this.money},我的名下有:${this.myCar.length}辆车,它们分别是:${this.myCar.join(',')}`); } /** * 构造函数:Woman */ function Woman() { } //使用原型对象继承 Woman.prototype = new Person(); //创建对象 var woman1 = new Woman(); woman1.money = 500; woman1.myCar.push("自行车", "保时捷"); var woman2 = new Woman(); woman2.money = 800; woman2.myCar.push("桑塔纳", "兰博基尼", "特斯拉"); woman1.showMyAssets(); woman2.showMyAssets();[scode type="blue" size="simple"]💻输出结果：woman1：我的余额还有:500,我的名下有:5辆车,它们分别是:自行车,保时捷,桑塔纳,兰博基尼,特斯拉woman2：我的余额还有:800,我的名下有:5辆车,它们分别是:自行车,保时捷,桑塔纳,兰博基尼,特斯拉❓ 两个实例输出的信息一样，为什么？Person构造函数中包含引用类型值的原型，由于包含引用类型值的原型属性会被所有实例共享 ，在通过原型实现继承时，原型实际上会变成另一个类型的实例，因此，原先的实例属性也就变成了现在的原型属性了。❓ 如果Person提供了参数，我们该如何向父类型的构造函数中传参呢?使用原型链无法实现，更换新方案：借用构造函数、组合继承[/scode]借用构造函数概述： 借用构造函数的基本思想比较简单，实现方式是：在子类构造函数的内部调用父类的构造函数，可以通过 apply()方法或者 call()方法进行调用，其语法如下：语法：apply：apply(thisObj,[arg1,arg2...]) //传递参数可选，参数必须以数组形式传递 call: call(thisObj,arg1,arg2...) //传递参数可选，多个参数使用逗号隔开 案例演示：/** * 构造函数:Person */ function Person() { this.money = 200; this.myCar = []; } /** * 构造函数:Woman */ function Woman() { Person.call(this); //Women继承了Person } //创建对象 var woman1 = new Woman(); woman1.money = 500; woman1.myCar.push("自行车", "保时捷"); var woman2 = new Woman(); woman2.money = 800; woman2.myCar.push("桑塔纳", "兰博基尼", "特斯拉"); console.log(woman1.myCar); // ['自行车', '保时捷'] console.log(woman2.myCar); // ['桑塔纳', '兰博基尼', '特斯拉']案例扩展：/** * 构造函数:Person */ function Person() { this.money = 200; this.myCar = []; } Person.prototype.showMyAssets = function () { console.log(`我的余额还有:${this.money},我的名下有:${this.myCar.length}辆车,它们分别是:${this.myCar.join(',')}`); } /** * 构造函数:Woman */ function Woman() { Person.call(this); //Women继承了Person } //创建对象 var woman1 = new Woman(); woman1.money = 500; woman1.myCar.push("自行车", "保时捷"); var woman2 = new Woman(); woman2.money = 800; woman2.myCar.push("桑塔纳", "兰博基尼", "特斯拉"); //调用打印财产的方法 woman1.showMyAssets(); //error: woman1.showMyAssets is not a function 代码中断 woman2.showMyAssets();[scode type="red" size="simple"]上述代码已经完成了继承，为什么在使用 woman1.showMyAssets();的时候提示错误？💡在使用 apply()或 call()实现继承时，只能继承属性！[/scode]组合继承案例演示：/** * 构造函数:Person */ function Person() { this.money = 200; this.myCar = []; } Person.prototype.showMyAssets = function () { console.log(`我的余额还有:${this.money},我的名下有:${this.myCar.length}辆车,它们分别是:${this.myCar.join(',')}`); } /** * 构造函数:Woman */ function Woman() { Person.call(this); //Women继承了Person中的属性 } Woman.prototype = new Person(); //Women继承了Person中的方法 //创建对象 var woman1 = new Woman(); woman1.money = 500; woman1.myCar.push("自行车", "保时捷"); var woman2 = new Woman(); woman2.money = 800; woman2.myCar.push("桑塔纳", "兰博基尼", "特斯拉"); //调用打印财产的方法 woman1.showMyAssets(); //我的余额还有:500,我的名下有:2辆车,它们分别是:自行车,保时捷 woman2.showMyAssets(); //我的余额还有:800,我的名下有:3辆车,它们分别是:桑塔纳,兰博基尼,特斯拉⭐️案例扩展，实现参数的传递：/** * 构造函数:Person */ function Person(money) { this.money = money; this.myCar = []; } //定义方法,实现财产输出 Person.prototype.showMyAssets = function () { console.log(`我的余额还有:${this.money},我的名下有:${this.myCar.length}辆车,它们分别是:${this.myCar.join(',')}`); } /** * 构造函数:Woman */ function Woman(name, money) { Person.call(this, money); //Women继承了Person中的属性,并传递参数 this.name = name; } Woman.prototype = new Person(); //Women继承了Person中的方法 //定义方法,实现自我介绍和财产输出 Woman.prototype.showMyInfo = function () { console.log(`大家好,我叫:${this.name}`); this.showMyAssets(); //调用父类的方法 } //创建对象 var woman1 = new Woman('张三', 800); woman1.myCar.push("自行车", "保时捷"); var woman2 = new Woman('李四', 1200); woman2.myCar.push("桑塔纳", "兰博基尼", "特斯拉"); //调用自我介绍的方法 woman1.showMyInfo(); woman2.showMyInfo(); /** * 输出结果: * 大家好,我叫:张三 * 我的余额还有:800,我的名下有:2辆车,它们分别是:自行车,保时捷 * 大家好,我叫:李四 * 我的余额还有:1200,我的名下有:3辆车,它们分别是:桑塔纳,兰博基尼,特斯拉 */💡 类 【重要扩展】基础概念概述： JavaScript 语言中，生成实例对象的传统方法是通过构造函数。下面是一个例子。function Preson(age, name) { this.age = age; this.name = name; } Preson.prototype.sayHi = function () { console.log(`我叫：${this.name}，今年：${this.age}岁`); }; var p = new Preson(16, '王五'); p.sayHi(); // 我叫：王五，今年：16岁上面这种写法跟传统的面向对象语言（比如 C++ 和 Java）差异很大，很容易让新学习这门语言的程序员感到困惑。ES6 提供了更接近传统语言的写法，引入了 Class（类）这个概念，作为对象的模板。通过 class关键字，可以定义类。基本上，ES6 的 class可以看作只是一个语法糖，它的绝大部分功能，ES5 都可以做到，新的 class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用 ES6 的 class改写，就是下面这样。class Preson { constructor(age, name) { this.age = age; this.name = name; } sayHi() { console.log(`我叫：${this.name}，今年：${this.age}岁`); } } var p = new Preson(16, '王五'); p.sayHi(); // 我叫：王五，今年：16岁上面代码定义了一个“类”，可以看到里面有一个 constructor()方法，这就是构造方法，而 this关键字则代表实例对象。这种新的 Class 写法，本质上与本章开头的 ES5 的构造函数 Person是一致的。Person类除了构造方法，还定义了一个 sayHi()方法。注意，定义 sayHi()方法的时候，前面不需要加上 function这个关键字，直接把函数定义放进去了就可以了。另外，方法与方法之间不需要逗号分隔，加了会报错。ES6 的类，完全可以看作构造函数的另一种写法。class Preson { // ... } console.log(typeof Preson); // "function" console.log(Preson === Preson.prototype.constructor); // true上面代码表明，类的数据类型就是函数，类本身就指向构造函数。使用的时候，也是直接对类使用 new命令，跟构造函数的用法完全一致。class Bar { doStuff() { console.log('stuff'); } } const b = new Bar(); b.doStuff() // "stuff"构造函数的 prototype属性，在 ES6 的“类”上面继续存在。事实上，类的所有方法都定义在类的 prototype属性上面。class Person { constructor() { // ... } sayHi() { // ... } doWork() { // ... } } // 等同于 Person.prototype = { constructor() { }, sayHi() { }, doWork() { }, };上面代码中，constructor()、sayHi()、doWork()这三个方法，其实都是定义在 Person.prototype上面。因此，在类的实例上面调用方法，其实就是调用原型上的方法。class B {} const b = new B(); b.constructor === B.prototype.constructor // true上面代码中，b是 B类的实例，它的 constructor()方法就是 B类原型的 constructor()方法。静态方法和静态属性：在class中允许定义静态（static）方法和静态属性：class Foo { static myStaticProp = 42; //定义静态属性 static classMethod() { //定义静态方法 return 'hello'; } } Foo.classMethod() // 'hello' console.log(Foo.myStaticProp) // 42 var foo = new Foo(); foo.classMethod() // TypeError: foo.classMethod is not a function上面代码中，Foo类的 classMethod方法前有 static关键字，表明该方法是一个静态方法，可以直接在 Foo类上调用（Foo.classMethod()），而不是在 Foo类的实例上调用。如果在实例上调用静态方法，会抛出一个错误，表示不存在该方法。注意，如果静态方法包含 this关键字，这个 this指的是类，而不是实例。class Foo { static bar() { // 在静态方法中调用静态方法 this.baz(); } static baz() { // 静态方法 console.log('hello'); } baz() { // 实例方法 console.log('world'); } } Foo.bar() // hello上面代码中，静态方法 bar调用了 this.baz，这里的 this指的是 Foo类，而不是 Foo的实例，等同于调用 Foo.baz。另外，从这个例子还可以看出，静态方法可以与非静态方法重名。私有属性：ES2022正式为 class添加了私有属性，方法是在属性名之前使用 #表示。class IncreasingCounter { #count = 0; get value() { console.log('Getting the current value!'); return this.#count; } increment() { this.#count++; } }上面代码中，#count就是私有属性，只能在类的内部使用（this.#count）。如果在类的外部使用，就会报错。const counter = new IncreasingCounter(); counter.#count // 报错 counter.#count = 42 // 报错上面示例中，在类的外部，读取或写入私有属性 #count，都会报错。继承Class 可以通过 extends关键字实现继承，让子类继承父类的属性和方法。extends 的写法比 ES5 的原型链继承，要清晰和方便很多。class Person { } class Student extends Person { }上面示例中，Person是父类，Student是子类，它通过 extends关键字，继承了 Person类的所有属性和方法。class Person { constructor(name, age, sex) { this.name = name; this.age = age; this.sex = sex; } sayHi() { console.log(`大家好，我叫：${this.name},我是${this.sex}生,今年：${this.age}岁`); } myFoo() { console.log('这是父类中的方法'); } } class Student extends Person { constructor(name, age, sex, className) { super(name, age, sex); //调用父类中的构造函数 this.className = className; } sayHi() { console.log(`大家好，我叫：${this.name},我是${this.sex}生,今年：${this.age}岁,来自于：${this.className}班`); //super.myFoo(); //可以通过super调用父类的方法、属性 } } var per = new Person('李四', 18, '男'); per.sayHi(); // 大家好，我叫：李四,我是男生,今年：18岁 var stu = new Student("张三", 14, '男', '理工40'); stu.sayHi(); // 大家好，我叫：张三,我是男生,今年：14岁,来自于：理工40班上面示例中，constructor()方法，出现了 super关键字。super允许子类调用父类的构造函数、方法、属性。ES6 规定，子类必须在 constructor()方法中调用 super()，否则就会报错。这是因为子类自己的 this对象，必须先通过父类的构造函数完成塑造，得到与父类同样的实例属性和方法，然后再对其进行加工，添加子类自己的实例属性和方法。如果不调用 super()方法，子类就得不到自己的 this对象。class Person { /* ... */ } class Student extends Person { constructor() { } } let stu = new Student(); // ReferenceError上面代码中，Student继承了父类 Person，但是它的构造函数没有调用 super()，导致新建实例时报错。[scode type="blue" size="simple"]▶️ 开始学习下一章: JavaScript+Jquery+ES6入门到放弃之ECMAScript 6 基础[/scode]
前言在上篇文章中我们讲解了节点与节点之间的关系，如何访问节点以及修改节点样式等操作，但是在实际开发中，这些操作远远不够，我们将会在这一篇文章中继续讲解DOM节点的操作：操作节点的属性、创建和插入节点、删除和替换节点。操作节点属性获取节点属性语法：节点元素.getAttribute("属性名")[scode type="yellow" size="simple"]🎈经验分享： 使用 getAttribute()方法获取属性值时，如果属性不存在，则返回null[/scode]设置节点属性语法：节点元素.setAttribute("属性名","属性值")演示案例： 案例执行效果:创建和插入节点创建节点:方法名称描述document.createElement(tagName)创建一个标签名为tagName的新元素节点element.cloneNode( deep )复制某个指定节点。参数deep为布尔类型。若为true，则同时复制当前节点的所有子节点。若为false，则仅复制当前节点。插入节点：方法方法描述A.appendChild(B)把B节点追加至A的子节点列表的末尾parentNode.insertBefore(A, B)把A节点插入到B节点之前演示案例： 案例演示效果：删除和替换节点方法名称描述 (node为父级节点)node.removeChild( child )删除指定的子节点node.replaceChild(newChild,oldChild)用其他节点替换指定的子节点演示案例： 案例演示效果：获取元素位置offset属性和client属性offset概述： offset指偏移，即这个元素在文档中占用的所有显示宽度常见属性：属性描述offsetLeft返回当前元素左边界到它上级元素的左边界的距离，只读属性offsetTop返回当前元素上边界到它上级元素的上边界的距离，只读属性offsetParent返回元素的偏移容器，即对最近的动态定位的包含元素的引用offsetHeight返回元素的高度offsetWidth返回元素的宽度演示案例（此案例在：BOM操作-小球滚动案例基础之上完成）： 青鸟技术栈 开始 案例展示效果：client概述： 用于获取指定元素的宽度与高度，与offsetHeight、offsetWidth功能一致，但是获取元素宽度与高度时，不会考虑元素的边框大小。常见属性：属性描述clientWidth返回元素的可见宽度clientHeight返回元素的可见高度scroll属性概述： scroll指滚动，用来描述网页整体与浏览器之间的关系。应用场景： 在浏览网页时，浏览器的高度与宽度是固定的，但是在我们日常浏览的网页中，页面的高度往往大于浏览器的高度。如果我们需要获取用户在这个页面滚动的距离，则我们可以使用：scrollTop属性以及scrollLeft属性在获取。属性解释：属性描述scrollTop返回匹配元素的滚动条的垂直位置scrollLeft返回匹配元素的滚动条的水平位置scrollHeight在没有滚动条的情况下，返回元素内容的总高度scrollWidth在没有滚动条的情况下，返回元素内容的总宽度语法：/*标准写法*/ document.documentElement.scrollTop document.documentElement.scrollLeft /*chrome浏览器*/ document.body.scrollTop document.body.scrollLeft /*兼容写法*/ var sTop = document.documentElement.scrollTop || document.body.scrollTop;案例演示： 青鸟技术栈 返回顶部 [scode type="blue" size="simple"]▶️ 开始学习下一章: JavaScript+Jquery+ES6入门到放弃之面向对象[/scode]
DOM对象的定义DOM（Document Object Model）是W3C国际组织的一套Web标准。它定义了访问HTML文档对象的一套属性、方法和事件。使用JavaScript操作DOM时分为三个方面：DOM Core（DOM核心操作）、HTML-DOM、CSS-DOM，我们通过Web标准可以动态的增加、删除、修改页面中的节点数据，使交互更加方便。节点与节点间的关系节点可以理解为HTML文档中每个标签或者元素。在HTML中节点是这样规定的：整个文档是一个文档节点。每个html标签是一个元素节点。包含在html元素中的文本时文本节点。每个html属性是一个属性节点。注释属于注释节点。节点与节点之间的关系大致能够分为以下5中：父节点（parent）：元素的上一层的那个节点就是该元素的父节点，每个元素都有一个父节点。子节点（children）：元素下面第一层的所有节点都是该元素的子节点，一个元素可以有 >=0个节点。同辈节点（sibling）：同辈节点也叫兄弟节点，拥有相同的父节点的节点互为同辈节点。祖辈节点（ancestor）：祖辈节点也叫祖先节点，元素的父节点以及父节点的父节点、一次类推都是该节点的先辈节点。后代节点（descendant）：同理，一个节点的子节点、子节点的子节点、以此类推，都是该节点的后代节点。文档节点DOM树展示：节点信息所有的节点都会包含节点的某些信息的属性，常见属性有：nodeName（节点名称）元素节点的标签名称属性节点的属性名称文本节点的nodeName永远是 #text文档节点的nodeName永远是 #documentnodeValue（节点值）文本节点包含的文本属性节点的属性值对文档节点和元素节点不可用nodeType（节点类型）节点类型nodeType值Element (元素)ELEMENT_NODE(1)Attr(属性)ATTRIBUTE_NODE(2)Text (文本)TEXT_NODE (3)Comment (注释)COMMENT_NODE (8)Document (文档)DOCUMENT_NODE(9)演示案例： 青鸟技术栈-博客 口袋刷题小程序 在线考试 联系我们 访问节点使用document.getElement系列方法访问指定节点:getElementById()：根据ID属性获取指定元素getElementsByName()：根据Name属性值获取一组元素getElementsByTagName()：根据标签名称获取一组元素getElementsByClassName()：根据类样式名称获取一组数据根据层次关系访问节点：🟧 节点属性属性名称描述parentNode返回节点的父节点childNodes返回子节点集合，childNodes[i]firstChild返回节点的第一个子节点，最普遍的用法是访问该元素的文本节点lastChild返回节点的最后一个子节点nextSibling返回下一个节点previousSibling返回上一个节点案例演示： 关于我们 青鸟技术栈-博客 口袋刷题小程序 在线考试 联系我们 [scode type="red" size="simple"]⚠️注意： 在格式化文档所添加的换行、回车、缩进等字符构成文本节点，干扰节点访问，不推荐直接使用节点属性获取对应的节点信息。👍👍👍推荐使用element属性获取节点数据。[/scode]🟩 element属性属性名称描述parentElement获取当前元素的父级元素children获取当前元素的子元素，它返回一个 HTMLCollection 的集合firstElementChild返回节点的第一个子节点，最普遍的用法是访问该元素的文本节点lastElementChild返回节点的最后一个子节点nextElementSibling下一个节点previousElementSibling上一个节点案例演示： 关于我们 青鸟技术栈-博客 口袋刷题小程序 在线考试 联系我们 [scode type="blue" size="simple"]🙌经验分享： 在实际开发中，为了保证浏览器的最佳兼容性，我们推荐使用如下书写形式var oNext = oParent.nextElementSibling || oParent.nextSibling; //获取下一个同辈节点 var oPre = oParent.previousElementSibling || oParent.previousSibling; //获取上一个同辈节点 var oFirst = oParent. firstElementChild || oParent.firstChild; //获取第一个子节点 var oLast = oParent.lastElementChild || oParent.lastChild; //获取最后一个子节点[/scode]操作节点样式在HTML页面中CSS使用非常频繁，我们可以通过JavaScript动态添加、修改、删除HTML文档中的样式信息，以达到页面的动态特效。style属性在JavaScript中，可以使用style属性设置html元素的样式信息，style是一个对象，表示一个单独的样式声明，可以通过应用样式的文档或者元素访问style对象，使用style对象设置样式的语法格式如下：HTML-Element.style.样式属性 = "属性值";[scode type="yellow" size="simple"]温馨提示： 在css样式表中font-size表示字体大小，但是我们通过style属性设置字体大小时，我们需要将"-"删除，并且还需要将"-"后面的单词首字母修改为大写（驼峰命名法），例如：font-size → fontSize、background-color → backgroundColor...[/scode][scode type="red" size="simple"]⚠️注意：使用style属性添加的样式将会以行内样式的形式出现在对应的html标签中。[/scode]常见样式属性类别属性背景（background）backgroundColor、backgroundImage、backgroundRepeat文本（text）font、fontSize、fontWeight、textAlign、textDecoration、color边距（padding、margin）padding、paddingTop 、paddingBottom、paddingLeft、paddingRightmargin、marginTop、marginBottom、marginLeft、marginRight边框（border）border、borderTop、borderBottom、borderLeft、borderRight演示案例： 我的购物车1 最新加入的商品 倩碧经典三部曲套装（液体皂800ml+明肌2号水800ml+润肤乳85ml） ¥888.00×1 删除 共1件商品共计¥888.00 去购物车 演示效果：演示案例下载地址：className属性在HTML DOM中，className属性可以设置或返回元素的class样式。语法：HTML-Element.className = "定义好的样式名称";演示案例//** html代码无任何变化 ** //窗体加载完毕后执行脚本代码 window.onload = function(){ //获取购物车列表 var cartList = document.getElementById("cartList"); //将其默认显示为隐藏状态 cartList.style.display = "none"; //获取购物车元素为其绑定鼠标悬浮事件 var cart = document.getElementById("cart"); //绑定鼠标悬浮事件 cart.onmouseover = function(){ //使用class属性设置时需要提前拥有对应的class样式 this.className = "cartOver"; cartList.className = "cartListOver"; } //绑定鼠标离开事件 cart.onmouseleave = function(){ //使用class属性设置时需要提前拥有对应的class样式 this.className = "cartOut"; cartList.className = "cartListOut"; } //解决鼠标炫富至详细列表时，购物车列表隐藏的问题 cartList.onmouseover = function(){ cart.className = "cartOver"; this.className = "cartListOver"; } cartList.onmouseleave = function(){ cart.className = "cartOut"; this.className = "cartListOut"; } }获取样式在上面的文章中，我们讲解了如何给指定的html元素设定样式，那我们在开发过程中如何获取某个元素所拥有的样式信息呢？在JavaScript中提供了以下几种方式用于获取html元素的样式信息。style属性HTML-Element.style.样式属性; 在之前的内容我们可以通过style属性设置样式，当然，我们也可以通过style属性获取指定样式属性的值。getComputedStyle()方法document.defaultView.getComputedStyle(HTML-Element,null).属性; [scode type="red" size="simple"]⚡警告： getComputedStyle()方法在IE6、IE7、IE8中无法正常解析。[/scode]currentStyle属性HTML-Element.currentStyle.样式属性; 该属性为只读属性，只能用于获取指定元素的样式信息，不允许通过它对html元素修改样式。[scode type="red" size="simple"]⚡ 警告：仅支持IE5.0 或以后版本以及Opera浏览器！[/scode] 青鸟技术栈 青鸟技术栈 [scode type="blue" size="simple"]▶️ 开始学习下一章: JavaScript+Jquery+ES6入门到放弃之DOM应用[/scode]
BOM对象的定义当我们在 Web 中使用 JavaScript 的时候，BOM对象(浏览器对象模型)起着至关重要的作用。BOM 提供了很多对象，用于访问浏览器的功能。熟悉了解 BOM 对象对我们 WEB 开发有着重要的帮助，下面是我的一些理解和总结。BOM是指浏览器对象模型，它是对一系列在浏览器环境中使用对象的统称，这些对象提供了访问浏览器的功能。在BOM对象中，window对象是最顶层对象，在浏览器环境中它是一个Global全局对象，其它对象（如：DOM对象）对是这个对象的属性（子对象）。BOM对象是与内容无关，主要用于管理浏览器窗口及窗口之间的通讯。下面是BOM对象的组成结构：BOM对象可以实现： 弹出新的浏览器窗口，移动、关闭浏览器窗口以及调整窗口的大小，页面的前进、后退等相关操作。Window对象的常用属性window对象： 表示浏览器中打开的窗口属性名称作用说明history有关客户访问过的URL的信息location有关当前 URL 的信息localStorage在浏览器中存储 key/value 对。没有过期时间。【了解更多：localStorage】sessionStorage在浏览器中存储 key/value 对。 在关闭窗口或标签页之后将会删除这些数据。【了解更多：sessionStorage】screen只读属性，包含客户端显示屏幕的信息语法：设定值：window.属性名 = "属性值";获取值：var info = window.属性名;Window对象的常用方法方法名称作用说明 【💡：已学习】prompt()显示可提示用户输入的对话框 💡alert( )显示带有一个提示信息和一个确定按钮的警示框 💡confirm( )显示一个带有提示信息、确定和取消按钮的对话框 💡close( )关闭浏览器窗口open( )打开一个新的浏览器窗口，加载给定URL所指定的文档setTimeout( )在指定的毫秒数后调用函数或计算表达式setInterval( )按照指定的周期（以毫秒计）来调用函数或表达式clearTimeout( )用于停止执行setTimeout( )方法的函数代码clearInterval( )用于停止setInterval( )方法执行的函数代码演示案例：//关闭当前浏览窗体 window.close(); //打开一个新窗口 window.open("url地址","可选。指定target属性或窗口的名称","可选。参数字符串,一个逗号分隔的项目列表",replace) /** * 指定target属性或窗口的名称: * _blank → URL加载到一个新的窗口。默认 * _parent → URL加载到父框架 * _self → URL替换当前页面 * _top → URL替换任何可加载的框架集 * name → 窗口名称 * ------------------------------------- * 参数字符串支持以下值（在主流浏览器中，很多属性现已不再被支持）： * channelmode=yes|no|1|0 是否要在影院模式显示 window。默认是没有的。仅限IE浏览器 * directories=yes|no|1|0 是否添加目录按钮。默认是肯定的。仅限IE浏览器 * fullscreen=yes|no|1|0 浏览器是否显示全屏模式。默认是没有的。在全屏模式下的 window，还必须在影院模式。仅限IE浏览器 * height=pixels 窗口的高度。最小值为100 * width=pixels 窗口的宽度。最小值为100 * left=pixels 该窗口的左侧位置 * top=pixels 窗口顶部的位置。 * location=yes|no|1|0 是否显示地址字段。默认值是yes * menubar=yes|no|1|0 是否显示菜单栏。默认值是yes * resizable=yes|no|1|0 是否可调整窗口大小。默认值是yes * scrollbars=yes|no|1|0 是否显示滚动条。默认值是yes * status=yes|no|1|0 是否要添加一个状态栏。默认值是yes * titlebar=yes|no|1|0 是否显示标题栏，默认值是yes * toolbar=yes|no|1|0 是否显示浏览器工具栏。默认值是yes * ------------------------------------- * replace: boolean类型，可选。 * true：URL 替换浏览历史中的当前条目。 * false： URL 在浏览历史中创建新的条目 */ //其他方法的使用方式将会在后续的演示代码中体现如需了解更多window属性和方法，请访问：https://www.runoob.com/jsref/obj-window.htmlhistory对象概念： 保存用户上网的历史记录，可通过 window.history属性访问常见属性和方法： 类别 名称 说明 属性 length 返回历史记录列表中的网址数 方法 back() 加载 History 对象列表中的前一个URL forward() 加载 History 对象列表中的下一个URL go() 加载 History 对象列表中的某个具体URL 演示案例：history.back() ==> history.go(-1) //等价，相当于浏览器的后退 history.forward() ==> history.go(1) //等价，相当于浏览器的前进 //注意: go()方法的参数还可以是要访问的URL或URL的子串location对象概念： 包含有关当前URL的信息，可通过window.location属性访问常见属性和方法： 类别 名称 说明 属性 host 设置或返回主机名和当前URL的端口号 hostname 设置或返回当前URL的主机名 href 设置或返回完整的URL 方法 reload() 重新加载当前文档 replace() 用新的文档替换当前文档 document对象的常用方法概念： document对象代表整个HTML文档常见方法：方 法 名 称说 明getElementById()返回对拥有指定id的第一个对象的引用 【返回1个元素 或 null】getElementsByName()返回带有指定名称的对象的集合 【返回多个元素】getElementsByTagName()返回带有指定标签名的对象的集合 【返回多个元素】 getElementsByClassName()返回带有指定class名称的对象的集合 【返回多个元素】querySelector()返回文档中匹配指定的选择器（ID选择器、类选择器、标签选择器、属性选择器等CSS中所支持的选择器形式）的第一元素 💡querySelectorAll()返回文档中匹配的指定的选择器（ID选择器、类选择器、标签选择器、属性选择器等CSS中所支持的选择器形式）的所有元素节点列表 💡write()向文档写文本、HTML表达式或JavaScript代码演示案例：html文件代码： 产品名称：1+8 Plus 规格选择： JavaScript脚本代码：/** * 根据ID获取页面中的元素 */ function changeName() { document.getElementById("phone").innerHTML = "修改文本内容"; } /** * 根据标签名称获取页面中的元素 */ function all_input() { var aInput = document.getElementsByTagName("input"); var sStr = ""; for (var i = 0; i < aInput.length; i++) { sStr += aInput[i].value + " "; } document.getElementById("replace").innerHTML = sStr; } /** * 根据名称获取页面中的元素 */ function s_input() { var aInput = document.getElementsByName("size"); var sStr = ""; for (var i = 0; i < aInput.length; i++) { sStr += aInput[i].value + " "; } document.getElementById("replace").innerHTML = sStr; } /** * 清空页面中所有的元素 */ function clearAll() { document.write(""); }呈现效果：[scode type="yellow" size="simple"]⚠注意：innerHTML几乎所有的元素都有innerHTML属性，它是一个字符串，用来设置或获取位于对象起始和结束标签内的HTML。（获取HTML当前标签的起始和结束里面的内容）innerText可以获取或设置指定元素标记内的文本的值，从元素标记的开始到元素标记的结束（不包括HTML标记，不适用于input表单元素）[/scode]定时函数超时调用： window.setTimeout("调用的函数", 等待的毫秒数);案例：//设定定时器 var myTime = setTimeout("disptime()", 1000); //1秒(1000毫秒)后执行disptime()函数一次，执行结束后，将不再继续执行该定时器 //取消定时器 clearTimeout(myTime); //代码执行到此处时，myTime定时器将会停止间歇调用： window.setInterval("调用的函数", 间隔的毫秒数);案例：//定义定时器 var myTime = setInterval("disptime()", 1000); //每隔1秒(1000毫秒)执行一次disptime()函数，执行结束后，等待1000毫秒后将继续执行该定时器 //取消定时器 clearInterval(myTime);[scode type="red" size="simple"]⚠注意： 使用清除定时器函数时，需要与定义时的定时器函数对应，clearTimeout()函数不能清除setInterval()所创建出的定时器。[/scode]演示案例:需求描述：页面打开时，小球上显示“开始”，用户点击小球后，小球开始向右移动，且显示文字变为“停止”，当用户再次点击小球时，小球停止移动，显示文字变为“开始”。 青鸟技术栈 开始 案例效果演示:[scode type="red" size="simple"]🧐问题：当用户第二次点击开始时，元素将从最开始的地方开始移动，如何实现在原有的位置上继续移动呢？ 【查看下方解决方案，采用闭包的形式或者将 var left = 0; 提升为全局变量】[/scode]//窗体加载完毕后,执行此代码 【html代码以及css代码无变化】 window.onload = function () { //获取ID为ball的元素 var node = document.getElementById("ball"); //动态绑定一个点击事件 node.onclick = (function () { //创建lefe变量,用于存储元素偏移量 var left = 0; //创建time变量,用于存储setInterval()返回的 ID 值 var time; return function () { var txt = this.innerText; if (txt == "开始") { //创建定时器,每隔10毫秒,累加偏移量 time = setInterval(() => { left++; this.style.left = left + "px"; }, 10); this.innerText = "停止" } else { clearInterval(time); this.innerText = "开始" } } })(); }JavaScript内置对象常见内置对象Array： 用于在单独的变量名中存储一系列的值 【📢重要】String： 用于支持对字符串的处理Math： 用于执行常用的数学任务，包含若干个数字常量和函数Date： 用于操作日期和时间Array数组创建数组语法：var arr1 = new Array(); //创建一个空数组 var arr2 = new Array(size); //指定长度 var arr3 = new Array(element0, element1, …, elementN); //创建时给定初始值 /**************简单写法****************/ var arr1 = []; //创建一个空数组 var arr2 = [element0, element1, …, elementN]; //创建时给定初始值演示案例：var arr = new Array(3); //注意，在创建数组的时候，Array大小写不能错 arr[0] = 1; arr[1] = 3; arr[2] = 5; arr[4] = 7; //请问，此段代码是否会出错？ console.log(arr); //输出结果： [1, 3, 5, 空白, 7] 说明Array允许动态维护数组长度常见属性和方法： 类别 名称 描述 属性 length 设置或返回数组中元素的数目 方法 join() 把数组的所有元素放入一个字符串，通过逗号或指定的分隔符进行分隔 sort() 对数组排序 push() 向数组末尾添加一个或更多元素，并返回新的长度 forEach() 遍历数组，forEach()方法不会直接修改原始数组，但是回调函数可能会修改 forEach()方法语法：array.forEach(function(currentValue, index, arr), thisValue)参数解释与说明： 参数 描述 function(currentValue, index, arr) 必需。 数组中每个元素需要调用的函数。函数参数: currentValue：必需。指当前元素 index：可选。指当前元素的索引值 arr：可选。指当前元素所属的数组对象 thisValue 可选。传递给函数的值一般用 "this" 值。 如果这个参数为空， "undefined" 会传递给 "this" 值 演示案例： 青鸟技术栈 点击按钮将数组中的所有值乘以特定数字。 乘以: 点我 计算后的值: 演示效果：如需了解更多Array对象的属性和方法，请访问：https://www.runoob.com/jsref/jsref-obj-array.htmlDate日期概念： Date 对象用于处理日期与时间。创建 Date 对象： new Date()以下四种方法同样可以创建 Date 对象：var d = new Date(); var d = new Date(milliseconds); // 参数为毫秒 var d = new Date(dateString); var d = new Date(year, month, day, hours, minutes, seconds, milliseconds); /** * 参数解释： * milliseconds 参数是一个 Unix 时间戳（Unix Time Stamp），它是一个整数值，表示自 1970 年 1 月 1 日 00:00:00 UTC（the Unix epoch）以来的毫秒数。 * dateString 参数表示日期的字符串值。 * year, month, day, hours, minutes, seconds, milliseconds 分别表示年、月、日、时、分、秒、毫秒。 */常用方法：方 法说 明getDate()返回Date对象的一个月中的每一天，其值介于1～31之间getDay()返回Date对象的星期中的每一天，其值介于0～6之间getHours()返回Date对象的小时数，其值介于0～23之间getMinutes()返回Date对象的分钟数，其值介于0～59之间getSeconds()返回Date对象的秒数，其值介于0～59之间getMonth()返回Date对象的月份，其值介于0～11之间getFullYear()返回Date对象的年份，其值为4位数getTime()返回自某一时刻（1970年1月1日）以来的毫秒数toLocaleDateString()根据本地时间格式，把 Date 对象的日期部分转换为字符串。toLocaleTimeString()根据本地时间格式，把 Date 对象的时间部分转换为字符串。toLocaleString()根据本地时间格式，把 Date 对象转换为字符串。如需了解更多Date对象的属性和方法，请访问：https://www.runoob.com/jsref/jsref-obj-date.htmlMath对象概念： Math 对象用于执行数学任务。Math 对象并不像 Date 和 String 那样是对象的类，因此没有构造函数 Math()。常见属性：属性描述PI返回圆周率（约等于3.14159）常见方法：方法说 明示例ceil()对参数进行上舍入Math.ceil(25.5);返回26 Math.ceil(-25.5);返回-25floor()对参数进行下舍入Math.floor(25.5);返回25 Math.floor(-25.5);返回-26round()把参数四舍五入为最接近的数Math.round(25.5);返回26 Math.round(-25.5);返回-26random()返回0~1之间的随机数Math.random(); 例如：0.6273608814137365案例演示：var num = Math.floor(Math.random()*100+1); //返回1～100（含1和100）之间的整数如需了解更多Math对象的属性和方法，请访问：https://www.runoob.com/jsref/jsref-obj-math.html[scode type="blue" size="simple"]▶️ 开始学习下一章: JavaScript+Jquery+ES6入门到放弃之DOM操作[/scode]