JavaScript个人学习笔记总结 - 浏览器

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


内容网址
JavaScript个人学习笔记总结 - 快速入门https://blog.csdn.net/weixin_50594210/article/details/115112096
JavaScript个人学习笔记总结 - 函数https://blog.csdn.net/weixin_50594210/article/details/115113081
JavaScript个人学习笔记总结 - 标准对象https://blog.csdn.net/weixin_50594210/article/details/115112683
JavaScript个人学习笔记总结 - 面向对象编程https://blog.csdn.net/weixin_50594210/article/details/115113024
JavaScript个人学习笔记总结 - 浏览器https://blog.csdn.net/weixin_50594210/article/details/115113131
JavaScript个人学习笔记总结 - jQueryhttps://blog.csdn.net/weixin_50594210/article/details/115113299
JavaScript个人学习笔记总结 - 错误处理https://blog.csdn.net/weixin_50594210/article/details/115113442
JavaScript个人学习笔记总结 - underscorehttps://blog.csdn.net/weixin_50594210/article/details/115113498

浏览器

目前主流的浏览器分这么几种

  • IE 6~11:国内用得最多的IE浏览器,历来对W3C标准支持差。从IE10开始支持ES6标准;
  • Chrome:Google出品的基于Webkit内核浏览器,内置了非常强悍的JavaScript引擎——V8。由于Chrome一经安装就时刻保持自升级,所以不用管它的版本,最新版早就支持ES6了;
  • Safari:Apple的Mac系统自带的基于Webkit内核的浏览器,从OS X 10.7
    Lion自带的6.1版本开始支持ES6,目前最新的OS X 10.11 El
    Capitan自带的Safari版本是9.x,早已支持ES6;
  • Firefox:Mozilla自己研制的Gecko内核和JavaScript引擎OdinMonkey。早期的Firefox按版本发布,后来终于聪明地学习Chrome的做法进行自升级,时刻保持最新;
  • 移动设备上目前iOS和Android两大阵营分别主要使用Apple的Safari和Google的Chrome,由于两者都是Webkit核心,结果HTML5首先在手机上全面普及(桌面绝对是Microsoft拖了后腿),对JavaScript的标准支持也很好,最新版本均支持ES6。

1、浏览器对象

window

  window对象不但充当全局作用域,而且表示浏览器窗口。

  window对象有innerWidth和innerHeight属性,可以获取浏览器窗口的内部宽度和高度。内部宽高是指除去菜单栏、工具栏、边框等占位元素后,用于显示网页的净宽高。

定时器

  唯一标识 setTimeout(功能,毫秒值):设置一次性定时器。

  clearTimeout(标识):取消一次性定时器。

  唯一标识 setInterval(功能,毫秒值):设置循环定时器。

  clearInterval(标识):取消循环定时器。

加载事件

  window.onload:在页面加载完毕后触发此事件的功能。

<script>
        //一、定时器
        function fun(){
            alert("该起床了!");
        }
    
        //设置一次性定时器
        let d1 = setTimeout("fun()",3000);
        //取消一次性定时器
        clearTimeout(d1);
    
        //设置循环定时器
        let d2 = setInterval("fun()",3000);
        //取消循环定时器
        clearInterval(d2);
    
        //加载事件
        window.onload = function(){
            let div = document.getElementById("div");
            alert(div);
        }
    </script>

navigator

navigator对象表示浏览器的信息,最常用的属性包括:

  • navigator.appName:浏览器名称;
  • navigator.appVersion:浏览器版本;
  • navigator.language:浏览器设置的语言;
  • navigator.platform:操作系统类型;
  • navigator.userAgent:浏览器设定的User-Agent字符串。

  注意navigator的信息可以很容易地被用户修改,所以JavaScript读取的值不一定是正确的。很多初学者为了针对不同浏览器编写不同的代码,喜欢用if判断浏览器版本

screen

screen对象表示屏幕的信息,常用的属性有:

  • screen.width:屏幕宽度,以像素为单位;
  • screen.height:屏幕高度,以像素为单位;
  • screen.colorDepth:返回颜色位数,如8、16、24。

location

location对象表示当前页面的URL信息。例如,一个完整的URL:

http://www.example.com:8080/path/index.html?a=1&b=2#TOP

  可以用location.href获取。要获得URL各个部分的值,可以这么写:

	location.protocol; // 'http'
	location.host; // 'www.example.com'
	location.port; // '8080'
	location.pathname; // '/path/index.html'
	location.search; // '?a=1&b=2'
	location.hash; // 'TOP'

  要加载一个新页面,可以调用location.assign()。如果要重新加载当前页面,调用location.reload()方法非常方便。

<script>
    //1.定义方法。改变秒数,跳转页面
    let num = 5;
    function showTime() {
        num--;

        if(num <= 0) {
            //跳转首页
            location.href = "index.html";
        }

        let span = document.getElementById("time");
        span.innerHTML = num;
    }

    //2.设置循环定时器,每1秒钟执行showTime方法
    setInterval("showTime()",1000);
</script>

document

  document对象表示当前页面。由于HTML在浏览器中以DOM形式表示为树形结构,document对象就是整个DOM树的根节点。

  document的title属性是从HTML文档中的<title>xxx</title>读取的,但是可以动态改变:

先准备HTML数据:

<dl id="drink-menu" style="border:solid 1px #ccc;padding:6px;">
    <dt>摩卡</dt>
    <dd>热摩卡咖啡</dd>
    <dt>酸奶</dt>
    <dd>北京老酸奶</dd>
    <dt>果汁</dt>
    <dd>鲜榨苹果汁</dd>
</dl>

  用document对象提供的getElementById()getElementsByTagName()可以按ID获得一个DOM节点和按Tag名称获得一组DOM节点:

<script>
	var menu = document.getElementById('drink-menu');
	var drinks = document.getElementsByTagName('dt');
	var i, s;

	s = '提供的饮料有:';
	for (i=0; i<drinks.length; i++) {
    	s = s + drinks[i].innerHTML + ',';
	}
	console.log(s);
</script>

  document对象还有一个cookie属性,可以获取当前页面的Cookie

  Cookie是由服务器发送的key-value标示符。因为HTTP协议是无状态的,但是服务器要区分到底是哪个用户发过来的请求,就可以用Cookie来区分。当一个用户成功登录后,服务器发送一个Cookie给浏览器,例如user=ABC123XYZ(加密的字符串)…,此后,浏览器访问该网站时,会在请求头附上这个Cookie,服务器根据Cookie即可区分出用户。

  Cookie还可以存储网站的一些设置,例如,页面显示的语言等等。

JavaScript可以通过document.cookie读取到当前页面的Cookie:

document.cookie; // 'v=123; remember=true; prefer=zh'

  由于JavaScript能读取到页面的Cookie,而用户的登录信息通常也存在Cookie中,这就造成了巨大的安全隐患,这是因为在HTML页面中引入第三方的JavaScript代码是允许的:

<html>
    <head>
        <script src="http://www.foo.com/jquery.js"></script>
    </head>
    ...
</html>

  如果引入的第三方的JavaScript中存在恶意代码,则www.foo.com网站将直接获取到www.example.com网站的用户登录信息。

  为了解决这个问题,服务器在设置Cookie时可以使用httpOnly,设定了httpOnly的Cookie将不能被JavaScript读取。这个行为由浏览器实现,主流浏览器均支持httpOnly选项,IE从IE6 SP1开始支持。

  为了确保安全,服务器端在设置Cookie时,应该始终坚持使用httpOnly

history

  history对象保存了浏览器的历史记录,JavaScript可以调用history对象的back()forward (),相当于用户点击了浏览器的“后退”或“前进”按钮。

  这个对象属于历史遗留对象,对于现代Web页面来说,由于大量使用AJAX和页面交互,简单粗暴地调用history.back()可能会让用户感到非常愤怒。

  新手开始设计Web页面时喜欢在登录页登录成功时调用history.back(),试图回到登录前的页面。这是一种错误的方法。

  任何情况,你都不应该使用history这个对象了。

2、操作DOM

  DOM(Document Object Model): 文档对象模型。

  将 HTML 文档的各个组成部分,封装为对象。借助这些对象,可以对 HTML 文档进行增删改查的动态操作。

在这里插入图片描述

Element元素的获取操作
在这里插入图片描述

<script>
        //根据id属性值获取元素对象
        let a = document.getElementById("div1");
        //alert(a);
        //根据元素名称获取元素对象们,返回数组
        let b = document.getElementsByTagName("div")
        alert(b.length);
        //根据class属性值获取元素对象们,返回数组
        let c = document.getElementsByClassName("cls");
        alert(c.length);
        //根据name属性值获取元素对象们,返回数组
        let d = document.getElementsByName("username");
        alert(d.length);
        //获取当前元素的父元素
        let e = a.parentElement;
        alert(e);
    </script>

Element元素的增删改操作

在这里插入图片描述

<script>
    //1. createElement()   创建新的元素
    let option = document.createElement("option");
    //为option添加文本内容
    option.innerText = "深圳";

    //2. appendChild()     将子元素添加到父元素中
    let select = document.getElementById("s");
    select.appendChild(option);

    //3. removeChild()     通过父元素删除子元素
    //select.removeChild(option);

    //4. replaceChild()    用新元素替换老元素
    let option2 = document.createElement("option");
    option2.innerText = "杭州";
    select.replaceChild(option2,option);

</script>

Attribute属性的操作

在这里插入图片描述

<script>
    //1. setAttribute()    添加属性
    let a = document.getElementsByTagName("a")[0];
    a.setAttribute("href","https://www.baidu.com");

    //2. getAttribute()    获取属性
    let value = a.getAttribute("href");
    //alert(value);

    //3. removeAttribute()  删除属性
    //a.removeAttribute("href");

    //4. style属性   添加样式  <a style="color: red;">点我呀</a>
    //a.style.color = "red";

    //5. className属性   添加指定样式
    a.className = "aColor";

</script>

Text文本的操作

在这里插入图片描述

<script>
    //1. innerText   添加文本内容,不解析标签
    let div = document.getElementById("div");
    div.innerText = "我是div";
    //div.innerText = "<b>我是div</b>";

    //2. innerHTML   添加文本内容,解析标签
    div.innerHTML = "<b>我是div</b>";

</script>

DOM小结:

  • DOM(Document Object Model):文档对象模型

  Document:文档对象

  • Element:元素对象

  Attribute:属性对象

  • Text:文本对象
  • 元素的操作

  getElementById()
  getElementsByTagName()
  getElementsByName()
  getElementsByClassName()
  子元素对象.parentElement属性
  createElement()
  appendChild()
  removeChild()
  replaceChild()

  • 属性的操作

  setAtrribute()
  getAtrribute()
  removeAtrribute()
  style属性

  • 文本的操作

  innerText
  innerHTML

2.1 更新DOM

  拿到一个DOM节点后,可以对它进行更新。

  可以直接修改节点的文本,方法有两种:

  一种是修改innerHTML属性,这个方式非常强大,不但可以修改一个DOM节点的文本内容,还可以直接通过HTML片段修改DOM节点内部的子树:

<script>
	// 获取<p id="p-id">...</p>
	var p = document.getElementById('p-id');
	// 设置文本为abc:
	p.innerHTML = 'ABC'; // <p id="p-id">ABC</p>
	// 设置HTML:
	p.innerHTML = 'ABC <span style="color:red">RED</span> XYZ';
	// <p>...</p>的内部结构已修改
</script>

  用innerHTML时要注意,是否需要写入HTML。如果写入的字符串是通过网络拿到的,要注意对字符编码来避免XSS攻击。

  第二种是修改innerTexttextContent属性,这样可以自动对字符串进行HTML编码,保证无法设置任何HTML标签:

<script>
	// 获取<p id="p-id">...</p>
	var p = document.getElementById('p-id');
	// 设置文本:
	p.innerText = '<script>alert("Hi")</script>';
	// HTML被自动编码,无法设置一个<script>节点:
	// <p id="p-id">&lt;script&gt;alert("Hi")&lt;/script&gt;</p>
</script>

  两者的区别在于读取属性时,innerText不返回隐藏元素的文本,而textContent返回所有文本。另外注意IE<9不支持textContent

  修改CSS也是经常需要的操作。DOM节点的style属性对应所有的CSS,可以直接获取或设置。因为CSS允许font-size这样的名称,但它并非JavaScript有效的属性名,所以需要在JavaScript中改写为驼峰式命名fontSize

<script>
	// 获取<p id="p-id">...</p>
	var p = document.getElementById('p-id');
	// 设置CSS:
	p.style.color = '#ff0000';
	p.style.fontSize = '20px';
	p.style.paddingTop = '2em';
</script>

2.2 插入DOM

  如果这个DOM节点是空的,例如,<div></div>,那么,直接使用innerHTML = '<span>child</span>’就可以修改DOM节点的内容,相当于“插入”了新的DOM节点。

  如果这个DOM节点不是空的,那就不能这么做,因为innerHTML会直接替换掉原来的所有子节点。

  有两个办法可以插入新的节点。一个是使用appendChild,把一个子节点添加到父节点的最后一个子节点。例如:

<!-- HTML结构 -->
<p id="js">JavaScript</p>
<div id="list">
    <p id="java">Java</p>
    <p id="python">Python</p>
    <p id="scheme">Scheme</p>
</div>

<p id="js">JavaScript</p>添加到<div id="list">的最后一项:

var
    js = document.getElementById('js'),
    list = document.getElementById('list');
list.appendChild(js);

现在,HTML结构变成了这样:

<!-- HTML结构 -->
<div id="list">
    <p id="java">Java</p>
    <p id="python">Python</p>
    <p id="scheme">Scheme</p>
    <p id="js">JavaScript</p>
</div>

  因为我们插入的js节点已经存在于当前的文档树,因此这个节点首先会从原先的位置删除,再插入到新的位置。

  更多的时候我们会从零创建一个新的节点,然后插入到指定位置:

var
    list = document.getElementById('list'),
    haskell = document.createElement('p');
haskell.id = 'haskell';
haskell.innerText = 'Haskell';
list.appendChild(haskell);

这样我们就动态添加了一个新的节点:

<!-- HTML结构 -->
<div id="list">
    <p id="java">Java</p>
    <p id="python">Python</p>
    <p id="scheme">Scheme</p>
    <p id="haskell">Haskell</p>
</div>

  动态创建一个节点然后添加到DOM树中,可以实现很多功能。举个例子,下面的代码动态创建了一个

var d = document.createElement('style');
d.setAttribute('type', 'text/css');
d.innerHTML = 'p { color: red }';
document.getElementsByTagName('head')[0].appendChild(d);

insertBefore

  如果我们要把子节点插入到指定的位置怎么办?可以使用parentElement.insertBefore(newElement, referenceElement);,子节点会插入到referenceElement之前。

还是以上面的HTML为例,假定我们要把Haskell插入到Python之前:

<!-- HTML结构 -->
<div id="list">
    <p id="java">Java</p>
    <p id="python">Python</p>
    <p id="scheme">Scheme</p>
</div>

可以这么写:

var
    list = document.getElementById('list'),
    ref = document.getElementById('python'),
    haskell = document.createElement('p');
haskell.id = 'haskell';
haskell.innerText = 'Haskell';
list.insertBefore(haskell, ref);

新的HTML结构如下:

<!-- HTML结构 -->
<div id="list">
    <p id="java">Java</p>
    <p id="haskell">Haskell</p>
    <p id="python">Python</p>
    <p id="scheme">Scheme</p>
</div>

  可见,使用insertBefore重点是要拿到一个“参考子节点”的引用。很多时候,需要循环一个父节点的所有子节点,可以通过迭代children属性实现:

var
    i, c,
    list = document.getElementById('list');
for (i = 0; i < list.children.length; i++) {
    c = list.children[i]; // 拿到第i个子节点
}

2.3 删除DOM

  删除一个DOM节点就比插入要容易得多。

  要删除一个节点,首先要获得该节点本身以及它的父节点,然后,调用父节点的removeChild把自己删掉:

// 拿到待删除节点:
var self = document.getElementById('to-be-removed');
// 拿到父节点:
var parent = self.parentElement;
// 删除:
var removed = parent.removeChild(self);
removed === self; // true

  注意到删除后的节点虽然不在文档树中了,但其实它还在内存中,可以随时再次被添加到别的位置。

  当你遍历一个父节点的子节点并进行删除操作时,要注意,children属性是一个只读属性,并且它在子节点变化时会实时更新。

例如,对于如下HTML结构:

<div id="parent">
    <p>First</p>
    <p>Second</p>
</div>

当我们用如下代码删除子节点时:

var parent = document.getElementById('parent');
parent.removeChild(parent.children[0]);
parent.removeChild(parent.children[1]); // <-- 浏览器报错

  浏览器报错:parent.children[1]不是一个有效的节点。原因就在于,当<p>First</p>节点被删除后,parent.children的节点数量已经从2变为了1,索引[1]已经不存在了。

  因此,删除多个节点时,要注意children属性时刻都在变化。

3、操作表单

表单标签

在这里插入图片描述
在这里插入图片描述

<body>
    <!--
        表单标签:<form>
        属性:
            action-提交的路径
            method-提交的方式(get和post)
            autocomplete-记录补全(on和off)
    -->
    <!--get方式的表单-->
    <form action="#" method="get" autocomplete="off">
        用户名:<input type="text" name="username"/>
        <button type="submit">提交</button>
    </form>

    <!--post方式的表单-->
    <form action="#" method="post" autocomplete="off">
        用户名:<input type="text" name="username"/>
        <button type="submit">提交</button>
    </form>
</body>

GET与POST说明:

  post :指的是 HTTP POST 方法;表单数据会包含在表单体内然后发送给服务器。

  get :指的是 HTTP GET 方法;表单数据会附加在 action 属性的URI中,并以 ‘?’ 作为分隔符,然后这样得到的 URI 再发送给服务器。

表单项便签

在这里插入图片描述

<body>
    <!--
        表单项标签:<label>  表单元素说明
        属性:for属性,属性值必须和表单项标签id属性值一致

        表单项标签:<input>   多种类型数据
        属性:
            type-数据类型
            id-唯一标识
            name-提交服务器的标识
            value-默认的数据值
            placeholder-默认的提示信息
            required-是否必须

        按钮标签:<button>
        属性:
            type-按钮的类型(submit提交、reset重置、button普通按钮)
    -->
    <form action="#" method="get" autocomplete="off">
        <label for="username">用户名:</label>
        <input type="text" id="username" name="username" value="" placeholder=" 请在此处输入用户名" required/>
        <button type="submit">提交</button>
        <button type="reset">重置</button>
        <button type="button">按钮</button>
    </form>
</body>

HTML表单的输入控件主要有以下几种:

  • 文本框,对应的<input type="text">,用于输入文本;
  • 口令框,对应的<input type="password">,用于输入口令;
  • 单选框,对应的,用于选择一项;
  • 复选框,对应的<input type="checkbox">,用于选择多项;下拉框,对应的,用于选择一项;
  • 隐藏文本,对应的<input type="hidden">,用户不可见,但表单提交时会把隐藏文本发送到服务器。
<body>
    <!--
        type属性
        <input type="text"/>       普通文本输入框
        <input type="password"/>   密码输入框
        <input type="email"/>      邮箱输入框
        <input type="radio"/>      单选框。必须有相同的name属性值,value属性真实提交的数据,checked属性默认选中
        <input type="checkbox"/>   多选框。必须有相同的name属性值,value属性真实提交的数据,checked属性默认选中


        <input type="date"/>       日期框
        <input type="time"/>       时间框
        <input type="datetime-local"/>  本地日期时间框
        <input type="number"/>     数字框
        <input type="range"/>      滚动条数值框  min-最小值   max-最大值  step-步进值
        <input type="search"/>     可清除文本框
        <input type="tel"/>        电话框
        <input type="url"/>        网址框
        <input type="file"/>       文件上传框
        <input type="hidden"/>     隐藏域   value属性设置实际提交的值
    -->
    <form action="#" method="get" autocomplete="off">
        <label for="username">用户名:</label>
        <input type="text" id="username" name="username"/>  <br/>

        <label for="password">密码:</label>
        <input type="password" id="password" name="password"/> <br/>

        <label for="email">邮箱:</label>
        <input type="email" id="email" name="email"/> <br/>

        <label for="gender">性别:</label>
        <input type="radio" id="gender" name="gender" value="men"/><input type="radio" name="gender" value="women"/><input type="radio" name="gender" value="other"/>其他<br/>

        <label for="hobby">爱好:</label>
        <input type="checkbox" id="hobby" name="hobby" value="music" checked/>音乐
        <input type="checkbox" name="hobby" value="game"/>游戏 <br/>

        <label for="birthday">生日:</label>
        <input type="date" id="birthday" name="birthday"/> <br/>

        <label for="time">当前时间:</label>
        <input type="time" id="time" name="time"/> <br/>

        <label for="time">当前时间2:</label>
        <input type="date" id="aa" name="time" value="1997/04/09"/> <br/>
        <script>
            $(document).ready(function () {
                var time = new Date();
                var day = ("0" + time.getDate()).slice(-2);
                var month = ("0" + (time.getMonth() + 1)).slice(-2);
                var today = time.getFullYear() + "-" + (month) + "-" + (day);
                $('#date_info').val(today);
            })
        </script>

        <label for="insert">注册时间:</label>
        <input type="datetime-local" id="insert" name="insert"/> <br/>

        <label for="age">年龄:</label>
        <input type="number" id="age" name="age"/> <br/>

        <label for="range">心情值(1~10):</label>
        <input type="range" id="range" name="range" min="1" max="10" step="1"/> <br/>

        <label for="search">可全部清除文本:</label>
        <input type="search" id="search" name="search"/> <br/>

        <label for="tel">电话:</label>
        <input type="tel" id="tel" name="tel"/> <br/>

        <label for="url">个人网站:</label>
        <input type="url" id="url" name="url"/> <br/>

        <label for="file">文件上传:</label>
        <input type="file" id="file" name="file"/> <br/>

        <label for="hidden">隐藏信息:</label>
        <input type="hidden" id="hidden" name="hidden" value="itheima"/> <br/>

        <button type="submit">提交</button>
        <button type="reset">重置</button>
    </form>
</body>

获取值

  如果我们获得了一个<input>节点的引用,就可以直接调用value获得对应的用户输入值:

// <input type="text" id="email">
var input = document.getElementById('email');
input.value; // '用户输入的值'

  这种方式可以应用于textpasswordhidden以及select。但是,对于单选框和复选框,value属性返回的永远是HTML预设的值,而我们需要获得的实际是用户是否“勾上了”选项,所以应该用checked判断:

// <label><input type="radio" name="weekday" id="monday" value="1"> Monday</label>
// <label><input type="radio" name="weekday" id="tuesday" value="2"> Tuesday</label>
var mon = document.getElementById('monday');
var tue = document.getElementById('tuesday');
mon.value; // '1'
tue.value; // '2'
mon.checked; // true或者false
tue.checked; // true或者false

设置值

  设置值和获取值类似,对于text、password、hidden以及select,直接设置value就可以:

// <input type="text" id="email">
var input = document.getElementById('email');
input.value = 'test@example.com'; // 文本框的内容已更新
对于单选框和复选框,设置checked为true或false即可。

提交表单

  最后,JavaScript可以以两种方式来处理表单的提交(AJAX方式在后面章节介绍)。

  方式一是通过<form>元素的submit()方法提交一个表单,例如,响应一个<button>的click事件,在JavaScript代码中提交表单:

<!-- HTML -->
<form id="test-form">
    <input type="text" name="test">
    <button type="button" onclick="doSubmitForm()">Submit</button>
</form>

<script>
function doSubmitForm() {
    var form = document.getElementById('test-form');
    // 可以在此修改form的input...
    // 提交form:
    form.submit();
}
</script>

  这种方式的缺点是扰乱了浏览器对form的正常提交。浏览器默认点击<button type="submit">时提交表单,或者用户在最后一个输入框按回车键。因此,第二种方式是响应<form>本身的onsubmit事件,在提交form时作修改:

<!-- HTML -->
<form id="test-form" onsubmit="return checkForm()">
    <input type="text" name="test">
    <button type="submit">Submit</button>
</form>

<script>
function checkForm() {
    var form = document.getElementById('test-form');
    // 可以在此修改form的input...
    // 继续下一步:
    return true;
}
</script>

  注意要return true来告诉浏览器继续提交,如果return false,浏览器将不会继续提交form,这种情况通常对应用户输入有误,提示用户错误信息后终止提交form

在检查和修改时,要充分利用来传递数据。

  例如,很多登录表单希望用户输入用户名和口令,但是,安全考虑,提交表单时不传输明文口令,而是口令的MD5。普通JavaScript开发人员会直接修改<input>

<!-- HTML -->
<form id="login-form" method="post" onsubmit="return checkForm()">
    <input type="text" id="username" name="username">
    <input type="password" id="password" name="password">
    <button type="submit">Submit</button>
</form>

<script>
function checkForm() {
    var pwd = document.getElementById('password');
    // 把用户输入的明文变为MD5:
    pwd.value = toMD5(pwd.value);
    // 继续下一步:
    return true;
}
</script>

  这个做法看上去没啥问题,但用户输入了口令提交时,口令框的显示会突然从几个变成32个(因为MD5有32个字符)。

要想不改变用户的输入,可以利用实现:

<!-- HTML -->
<form id="login-form" method="post" onsubmit="return checkForm()">
    <input type="text" id="username" name="username">
    <input type="password" id="input-password">
    <input type="hidden" id="md5-password" name="password">
    <button type="submit">Submit</button>
</form>

<script>
function checkForm() {
    var input_pwd = document.getElementById('input-password');
    var md5_pwd = document.getElementById('md5-password');
    // 把用户输入的明文变为MD5:
    md5_pwd.value = toMD5(input_pwd.value);
    // 继续下一步:
    return true;
}
</script>

  注意到id为md5-password<input>标记了name="password",而用户输入的id为input-password<input>没有name属性。没有name属性的<input>的数据不会被提交。

4、操作文件

  在HTML表单中,可以上传文件的唯一控件就是<input type="file">

  注意:当一个表单包含<input type="file">时,表单的enctype必须指定为multipart/form-datamethod必须指定为post,浏览器才能正确编码并以multipart/form-data格式发送表单的数据。

  出于安全考虑,浏览器只允许用户点击<input type="file">来选择本地文件,用JavaScript对<input type="file">的value赋值是没有任何效果的。

  通常,上传的文件都由后台服务器处理,JavaScript可以在提交表单时对文件扩展名做检查,以便防止用户上传无效格式的文件:

<script>
	var f = document.getElementById('test-file-upload');
	var filename = f.value; // 'C:\fakepath\test.png'
	if (!filename || !(filename.endsWith('.jpg') || filename.endsWith('.png') || filename.endsWith('.gif'))) {
    	alert('Can only upload image file.');
    	return false;
	}
</script>

File API

  由于JavaScript对用户上传的文件操作非常有限,尤其是无法读取文件内容,使得很多需要操作文件的网页不得不用Flash这样的第三方插件来实现。

  随着HTML5的普及,新增的File API允许JavaScript读取文件内容,获得更多的文件信息。

HTML5的File API提供了FileFileReader两个主要对象,可以获得文件信息并读取文件。

<script>
	var
    	fileInput = document.getElementById('test-image-file'),
    	info = document.getElementById('test-file-info'),
    	preview = document.getElementById('test-image-preview');
	// 监听change事件:
	fileInput.addEventListener('change', function () {
    	// 清除背景图片:
    	preview.style.backgroundImage = '';
    	// 检查文件是否选择:
    	if (!fileInput.value) {
        	info.innerHTML = '没有选择文件';
        	return;
    	}
    	// 获取File引用:
    	var file = fileInput.files[0];
    	// 获取File信息:
    	info.innerHTML = '文件: ' + file.name + '<br>' +
                     	'大小: ' + file.size + '<br>' +
                     	'修改: ' + file.lastModifiedDate;
    	if (file.type !== 'image/jpeg' && file.type !== 'image/png' && file.type !== 'image/gif') {
        	alert('不是有效的图片文件!');
        	return;
    	}
    	// 读取文件:
    	var reader = new FileReader();
    	reader.onload = function(e) {
        	var
            	data = e.target.result; // 'data:image/jpeg;base64,/9j/4AAQSk...(base64编码)...'            
        	preview.style.backgroundImage = 'url(' + data + ')';
    	};
    	// 以DataURL的形式读取文件:
    	reader.readAsDataURL(file);
	});
</script>

  上面的代码演示了如何通过HTML5的File API读取文件内容。以DataURL的形式读取到的文件是一个字符串,类似于data:image/jpeg;base64,/9j/4AAQSk...(base64编码)...,常用于设置图像。如果需要服务器端处理,把字符串base64,后面的字符发送给服务器并用Base64解码就可以得到原始文件的二进制内容。

回调

  上面的代码还演示了JavaScript的一个重要的特性就是单线程执行模式。在JavaScript中,浏览器的JavaScript执行引擎在执行JavaScript代码时,总是以单线程模式执行,也就是说,任何时候,JavaScript代码都不可能同时有多于1个线程在执行。

  在JavaScript中,执行多任务实际上都是异步调用,比如上面的代码:

reader.readAsDataURL(file);

  就会发起一个异步操作来读取文件内容。因为是异步操作,所以我们在JavaScript代码中就不知道什么时候操作结束,因此需要先设置一个回调函数:

reader.onload = function(e) {
    // 当文件读取完成后,自动调用此函数:
};

  当文件读取完成后,JavaScript引擎将自动调用我们设置的回调函数。执行回调函数时,文件已经读取完毕,所以我们可以在回调函数内部安全地获得文件内容。

5、AJAX

在这里插入图片描述

  • AJAX(Asynchronous JavaScript And XML):异步的 JavaScript 和 XML。
  • 本身不是一种新技术,而是多个技术综合。用于快速创建动态网页的技术。
  • 一般的网页如果需要更新内容,必需重新加载个页面。
  • 而 AJAX 通过浏览器与服务器进行少量数据交换,就可以使网页实现异步更新。也就是在不重新加载整个页 面 的情况下,对网页的部分内容进行局部更新。

  最早大规模使用AJAX的就是Gmail,Gmail的页面在首次加载后,剩下的所有数据都依赖于AJAX来更新。

  用JavaScript写一个完整的AJAX代码并不复杂,但是需要注意:AJAX请求是异步执行的,也就是说,要通过回调函数获得响应。

  在现代浏览器上写AJAX主要依靠XMLHttpRequest对象:

<script>
	function success(text) {
    	var textarea = document.getElementById('test-response-text');
    	textarea.value = text;
	}

	function fail(code) {
    	var textarea = document.getElementById('test-response-text');
    	textarea.value = 'Error code: ' + code;
	}

	var request = new XMLHttpRequest(); // 新建XMLHttpRequest对象

	request.onreadystatechange = function () { // 状态发生变化时,函数被回调
    	if (request.readyState === 4) { // 成功完成
        	// 判断响应结果:
        	if (request.status === 200) {
            	// 成功,通过responseText拿到响应的文本:
            	return success(request.responseText);
        	} else {
            	// 失败,根据响应码判断失败原因:
            	return fail(request.status);
        	}
    	} else {
        	// HTTP请求还在继续...
    	}
	}

	// 发送请求:
	request.open('GET', '/api/categories');
	request.send();

	alert('请求已发送,请等待响应...');
</script>

对于低版本的IE,需要换一个ActiveXObject对象:

<script>
	function success(text) {
    	var textarea = document.getElementById('test-ie-response-text');
    	textarea.value = text;
	}

	function fail(code) {
    	var textarea = document.getElementById('test-ie-response-text');
    	textarea.value = 'Error code: ' + code;
	}

	var request = new ActiveXObject('Microsoft.XMLHTTP'); // 新建Microsoft.XMLHTTP对象

	request.onreadystatechange = function () { // 状态发生变化时,函数被回调
    	if (request.readyState === 4) { // 成功完成
        	// 判断响应结果:
        	if (request.status === 200) {
            	// 成功,通过responseText拿到响应的文本:
            	return success(request.responseText);
        	} else {
            	// 失败,根据响应码判断失败原因:
            	return fail(request.status);
        	}
    	} else {
        	// HTTP请求还在继续...
    	}
	}

	// 发送请求:
	request.open('GET', '/api/categories');
	request.send();

	alert('请求已发送,请等待响应...');
</script>

如果想把标准写法和IE写法混在一起,可以这么写:

<script>
	var request;
	if (window.XMLHttpRequest) {
    	request = new XMLHttpRequest();
	} else {
    	request = new ActiveXObject('Microsoft.XMLHTTP');
	}
</script>

  通过检测window对象是否有XMLHttpRequest属性来确定浏览器是否支持标准的XMLHttpRequest。注意,不要根据浏览器的navigator.userAgent来检测浏览器是否支持某个JavaScript特性,一是因为这个字符串本身可以伪造,二是通过IE版本判断JavaScript特性将非常复杂。

  当创建了XMLHttpRequest对象后,要先设置onreadystatechange的回调函数。在回调函数中,通常我们只需通过readyState === 4判断请求是否完成,如果已完成,再根据status === 200判断是否是一个成功的响应。

  XMLHttpRequest对象的open()方法有3个参数,第一个参数指定是GET还是POST,第二个参数指定URL地址,第三个参数指定是否使用异步,默认是true,所以不用写。

  注意,千万不要把第三个参数指定为false,否则浏览器将停止响应,直到AJAX请求完成。如果这个请求耗时10秒,那么10秒内你会发现浏览器处于“假死”状态。

  最后调用send()方法才真正发送请求。GET请求不需要参数,POST请求需要把body部分以字符串或者FormData对象传进去。

AJAX实现方式

  • 原生 JavaScript 实现 AJAX:

在这里插入图片描述

<script>
        $("#username").keyup(function () {
            //创建核心对象
            let xmlHttp = new XMLHttpRequest();
            //获取用户名框里的值
            let username = $(this).val();
            //打开链接
            xmlHttp.open("GET","userServlet?username="+username,true)
            //发送请求
            xmlHttp.send();

            //处理响应
            xmlHttp.onreadystatechange = function () {
                console.log(xmlHttp.readyState+":"+xmlHttp.status)//在控制台打印
                if(xmlHttp.readyState == 4 && xmlHttp.status == 200){
                    //将响应的数据显示到span标签中
                    $("#uSpan").html(xmlHttp.responseText);
                }
            }

        });
    </script>
  • GET方式实现AJAX

在这里插入图片描述

<script>
       //get方法实现AJAX
       //绑定事件
       $("#username").keyup(function () {
           // let username = $(this).val();
            $.get(
                //请求的资源路径
                "userServlet",
                //请求的参数
                "username=" + $(this).val(),
                //回调函数
                function (data) {
                    $("#uSpan").html(data);
                },
                //响应数据形式
                "text"
            );
        });

       /*// 成功回来以后调用的函数
       //1.为用户名绑定失去焦点事件
       $("#username").blur(function () {
           //val()方法是获取文本框中输入的值,与js对象的value属性是一样的
           let username = $("#username").val();

           //2.jQuery的GET方式实现AJAX
           $.get(
               //请求的资源路径
               "userServlet",
               //请求参数
               "username=" + username,
               //回调函数
               function (data) {
                   // 业务逻辑
                   //将响应的数据显示到span标签
                   $("#uSpan").html(data);
               },
               //响应数据形式
               "text"
           );
       });*/
    </script>
  • POST方式实现AJAX

在这里插入图片描述

 <script>
       //post方法实现AJAX
       //绑定事件
       $("#username").keyup(function () {
           $.post(
               //请求资源路径
               "userServlet",
               //请求参数
               "username=" + $(this).val(),
               //响应功能
               function (data) {
                   $("#uSpan").html(data)
               },
               //预期的返回数据类型
               "text"
           );
       });
    </script>
  • 通用方式实现AJAX

在这里插入图片描述

 <script>
        $("#username").keyup(function () {
            $.ajax({
                //请求资源路径
                url : "userServlet",
                //是否异步
                async : true,
                //请求参数
                data : {username : $(this).val()},
                //请求方式
                type : "get",
                //预期响应数据类型
                dataType : "text",
                //响应成功执行的功能
                success:function (data) {
                    $("#uSpan").html(data);
                },
                //响应失败执行的功能
                error:function () {
                    alert("操作失败!!!")
                }
            });
        });
    </script>

小结:

  1. AJAX(Asynchronous JavaScript And XML): 异步的 JavaScript 和 XML。

  2. 通过浏览器与服务器进行少量数据交换,就可以使网页实现异步更新。也就是在不重新加载整个页面的情况 下,对网页的部 分内容进行局部更新。

  3. 同步和异步
      同步: 服务器端在处理过程中,无法进行其他操作。
      异步: 服务器端在处理过程中,可以进行其他操作。

  4. GET 方式实现:$.get();

  5. POST 方式实现:$.post();

  url: 请求的资源路径。
  data: 发送给服务器端的请求参数,格式可以是key=value,也可以是 js 对象。
  callback: 当请求成功后的回调函数,可以在函数中编写我们的逻辑代码。
  type: 预期的返回数据的类型,取值可以是 xml, html, js, json, text等。

  1. 通用方式实现:$.ajax();

  url: 请求的资源路径。
  async: 是否异步请求,true-是,false-否 (默认是 true)。
  data: 发送到服务器的数据,可以是键值对形式,也可以是 js 对象形式。
  type: 请求方式,POST 或 GET (默认是 GET)。
  dataType: 预期的返回数据的类型,取值可以是 xml, html, js, json, text等。
  success: 请求成功时调用的回调函数。
  error: 请求失败时调用的回调函数。

安全限制

  默认情况下,JavaScript在发送AJAX请求时,URL的域名必须和当前页面完全一致。

  完全一致的意思是,域名要相同(www.example.com和example.com不同),协议要相同(httphttps不同),端口号要相同(默认是:80端口,它和:8080就不同)。有的浏览器口子松一点,允许端口不同,大多数浏览器都会严格遵守这个限制。

  那是不是用JavaScript无法请求外域(就是其他网站)的URL了呢?方法还是有的,大概有这么几种:

  一是通过Flash插件发送HTTP请求,这种方式可以绕过浏览器的安全限制,但必须安装Flash,并且跟Flash交互。不过Flash用起来麻烦,而且现在用得也越来越少了。

  二是通过在同源域名下架设一个代理服务器来转发,JavaScript负责把请求发送到代理服务器:

'/proxy?url=http://www.sina.com.cn'

  代理服务器再把结果返回,这样就遵守了浏览器的同源策略。这种方式麻烦之处在于需要服务器端额外做开发。

  第三种方式称为JSONP,它有个限制,只能用GET请求,并且要求返回JavaScript。这种方式跨域实际上是利用了浏览器允许跨域引用JavaScript资源:

<html>
<head>
    <script src="http://example.com/abc.js"></script>
    ...
</head>
<body>
...
</body>
</html>

JSONP通常以函数调用的形式返回,例如,返回JavaScript内容如下:

foo('data');

  这样一来,我们如果在页面中先准备好foo()函数,然后给页面动态加一个<script>节点,相当于动态读取外域的JavaScript资源,最后就等着接收回调了。

因此我们需要首先在页面中准备好回调函数:

 <script>
	function refreshPrice(data) {
    	var p = document.getElementById('test-jsonp');
    	p.innerHTML = '当前价格:' +
        	data['0000001'].name +': ' + 
        	data['0000001'].price + ';' +
        	data['1399001'].name + ': ' +
        	data['1399001'].price;
	}
</script>

最后用getPrice()函数触发:

 <script>
	function getPrice() {
    	var
        	js = document.createElement('script'),
        	head = document.getElementsByTagName('head')[0];
    	js.src = 'http://api.money.126.net/data/feed/0000001,1399001?callback=refreshPrice';
    	head.appendChild(js);
}
</script>

就完成了跨域加载数据。

CORS

  CORS全称Cross-Origin Resource Sharing,是HTML5规范定义的如何跨域访问资源。

  了解CORS前,我们先搞明白概念:

  Origin表示本域,也就是浏览器当前页面的域。当JavaScript向外域(如sina.com)发起请求后,浏览器收到响应后,首先检查Access-Control-Allow-Origin是否包含本域,如果是,则此次跨域请求成功,如果不是,则请求失败,JavaScript将无法获取到响应的任何数据。

用一个图来表示就是:

在这里插入图片描述

  假设本域是my.com,外域是sina.com,只要响应头Access-Control-Allow-Originhttp://my.com,或者是*,本次请求就可以成功。

  可见,跨域能否成功,取决于对方服务器是否愿意给你设置一个正确的Access-Control-Allow-Origin,决定权始终在对方手中。

  上面这种跨域请求,称之为“简单请求”。简单请求包括GET、HEAD和POST(POST的Content-Type类型 仅限application/x-www-form-urlencoded、multipart/form-datatext/plain),并且不能出现任何自定义头(例如,X-Custom: 12345),通常能满足90%的需求。

  无论你是否需要用JavaScript通过CORS跨域请求资源,你都要了解CORS的原理。最新的浏览器全面支持HTML5。在引用外域资源时,除了JavaScript和CSS外,都要验证CORS。例如,当你引用了某个第三方CDN上的字体文件时:

/* CSS */
@font-face {
  font-family: 'FontAwesome';
  src: url('http://cdn.com/fonts/fontawesome.ttf') format('truetype');
}

  如果该CDN服务商未正确设置Access-Control-Allow-Origin,那么浏览器无法加载字体资源。

  对于PUT、DELETE以及其他类型如application/json的POST请求,在发送AJAX请求之前,浏览器会先发送一个OPTIONS请求(称为preflighted请求)到这个URL上,询问目标服务器是否接受:

OPTIONS /path/to/resource HTTP/1.1
Host: bar.com
Origin: http://my.com
Access-Control-Request-Method: POST

服务器必须响应并明确指出允许的Method:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://my.com
Access-Control-Allow-Methods: POST, GET, PUT, OPTIONS
Access-Control-Max-Age: 86400

  浏览器确认服务器响应的Access-Control-Allow-Methods头确实包含将要发送的AJAX请求的Method,才会继续发送AJAX,否则,抛出一个错误。

  由于以POSTPUT方式传送JSON格式的数据在REST中很常见,所以要跨域正确处理POSTPUT请求,服务器端必须正确响应OPTIONS请求。

6、Promise

  在JavaScript的世界中,所有代码都是单线程执行的。

Promise对象的几个重要的点

  1. promise有三种状态, 等待(pending)、已完成(fulfilled)、已拒绝(rejected)
  2. promise的状态只能从“等待”转到“完成”或者“拒绝”,不能逆向转换,同时“完成”和“拒绝”也不能相互转换
  3. promise必须有一个then方法,而且要返回一个promise,供then的链式调用,也就是可thenable的
  4. then接受俩个回调(成功与拒绝),在相应的状态转变时触发,回调可返回promise,等待此promise被resolved后,继续触发then链

  由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现:

<script>
	function callback() {
    	console.log('Done');
	}
	console.log('before setTimeout()');
	setTimeout(callback, 1000); // 1秒钟后调用callback函数
	console.log('after setTimeout()');
 </script>

观察上述代码执行,在Chrome的控制台输出可以看到:

before setTimeout()
after setTimeout()
(等待1秒后)
Done

  可见,异步操作会在将来的某个时间点触发一个函数调用。

  AJAX就是典型的异步操作。以上一节的代码为例:

<script>
	request.onreadystatechange = function () {
    	if (request.readyState === 4) {
        	if (request.status === 200) {
            	return success(request.responseText);
        	} else {
            	return fail(request.status);
        	}
    	}
	}
</script>

  把回调函数success(request.responseText)fail(request.status)写到一个AJAX操作里很正常,但是不好看,而且不利于代码复用。

  有没有更好的写法?比如写成这样:

var ajax = ajaxGet('http://...');
ajax.ifSuccess(success)
    .ifFail(fail);

  这种链式写法的好处在于,先统一执行AJAX逻辑,不关心如何处理结果,然后,根据结果是成功还是失败,在将来的某个时候调用success函数或fail函数。

  看一个最简单的Promise例子:生成一个0-2之间的随机数,如果小于1,则等待一段时间后返回成功,否则返回失败:

<script>
	function test(resolve, reject) {
    	var timeOut = Math.random() * 2;
    	log('set timeout to: ' + timeOut + ' seconds.');
    	setTimeout(function () {
        	if (timeOut < 1) {
            	log('call resolve()...');
            	resolve('200 OK');
        	}
        	else {
            	log('call reject()...');
            	reject('timeout in ' + timeOut + ' seconds.');
        	}
    	}, timeOut * 1000);
	}
</script>

  这个test()函数有两个参数,这两个参数都是函数,如果执行成功,我们将调用resolve('200 OK'),如果执行失败,我们将调用reject('timeout in ' + timeOut + ' seconds.')。可以看出,test()函数只关心自身的逻辑,并不关心具体的resolvereject将如何处理结果。

  有了执行函数,我们就可以用一个Promise对象来执行它,并在将来某个时刻获得成功或失败的结果:

<script>
	var p1 = new Promise(test);
	var p2 = p1.then(function (result) {
    	console.log('成功:' + result);
	});
	var p3 = p2.catch(function (reason) {
    	console.log('失败:' + reason);
	});
</script>

  变量p1是一个Promise对象,它负责执行test函数。由于test函数在内部是异步执行的,当test函数执行成功时,我们告诉Promise对象:

<script>
	// 如果成功,执行这个函数:
	p1.then(function (result) {
    	console.log('成功:' + result);
	});
</script>

test函数执行失败时,我们告诉Promise对象:

p2.catch(function (reason) {
    console.log('失败:' + reason);
});

Promise对象可以串联起来,所以上述代码可以简化为:

new Promise(test).then(function (result) {
    console.log('成功:' + result);
}).catch(function (reason) {
    console.log('失败:' + reason);
});

  可见Promise最大的好处是在异步执行的流程中,把执行代码和处理结果的代码清晰地分离了:

在这里插入图片描述

  Promise还可以做更多的事情,比如,有若干个异步任务,需要先做任务1,如果成功后再做任务2,任何任务失败则不再继续并执行错误处理函数。

  要串行执行这样的异步任务,不用Promise需要写一层一层的嵌套代码。有了Promise,我们只需要简单地写:

job1.then(job2).then(job3).catch(handleError);

其中,job1job2job3都是Promise对象。

  除了串行执行若干异步任务外,Promise还可以并行执行异步任务。

  试想一个页面聊天系统,我们需要从两个不同的URL分别获得用户的个人信息和好友列表,这两个任务是可以并行执行的,用Promise.all()实现如下:

<script>
	var p1 = new Promise(function (resolve, reject) {
    	setTimeout(resolve, 500, 'P1');
	});
	var p2 = new Promise(function (resolve, reject) {
    	setTimeout(resolve, 600, 'P2');
	});
	// 同时执行p1和p2,并在它们都完成后执行then:
	Promise.all([p1, p2]).then(function (results) {
    	console.log(results); // 获得一个Array: ['P1', 'P2']
	});
</script>

  有些时候,多个异步任务是为了容错。比如,同时向两个URL读取用户的个人信息,只需要获得先返回的结果即可。这种情况下,用Promise.race()实现:

<script>
	var p1 = new Promise(function (resolve, reject) {
    	setTimeout(resolve, 500, 'P1');
	});
	var p2 = new Promise(function (resolve, reject) {
    	setTimeout(resolve, 600, 'P2');
	});
	Promise.race([p1, p2]).then(function (result) {
    	console.log(result); // 'P1'
	});
</script>

  由于p1执行较快,Promise的then()将获得结果’P1’。p2仍在继续执行,但执行结果将被丢弃。

  如果我们组合使用Promise,就可以把很多异步任务以并行和串行的方式组合起来执行。


内容网址
JavaScript个人学习笔记总结 - 快速入门https://blog.csdn.net/weixin_50594210/article/details/115112096
JavaScript个人学习笔记总结 - 函数https://blog.csdn.net/weixin_50594210/article/details/115113081
JavaScript个人学习笔记总结 - 标准对象https://blog.csdn.net/weixin_50594210/article/details/115112683
JavaScript个人学习笔记总结 - 面向对象编程https://blog.csdn.net/weixin_50594210/article/details/115113024
JavaScript个人学习笔记总结 - 浏览器https://blog.csdn.net/weixin_50594210/article/details/115113131
JavaScript个人学习笔记总结 - jQueryhttps://blog.csdn.net/weixin_50594210/article/details/115113299
JavaScript个人学习笔记总结 - 错误处理https://blog.csdn.net/weixin_50594210/article/details/115113442
JavaScript个人学习笔记总结 - underscorehttps://blog.csdn.net/weixin_50594210/article/details/115113498

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

改变世界的李

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值