[Python自学] day-16 (JS、作用域、DOM、事件)
一、JS中的三种函数
1.普通函数
function func(){ console.log("Hello World"); }
func()
2.匿名函数
setInterval(function(){ console.log(123) },5000)
中间的function()就是匿名函数。
3.自运行函数
(function(arg){ console.log(arg) })(1)
(1)是传递给arg的参数,该函数会在导入JS时自动执行。(可以用来作为容器,包含第三方库,避免命名冲突)
二、序列化
将一个对象转化为字符串形式:
var li = [1,2,3,4,5]; JSON.stringify(li); //"[1,2,3,4,5]"
将一个字符串转换为对象:
var str = "[1,2,3,4,5]"; JSON.parse(str); //[1,2,3,4,5]
三、URL转义
当URL中出现特殊字符(包含中文、空格等),在存入cookie或写入磁盘时,需要将其转义。如下代码:
url = "https://search.jd.com/Search?keyword=手机" newurl = encodeURI(url) // "https://search.jd.com/Search?keyword=%E6%89%8B%E6%9C%BA"
反转义:
url = "https://search.jd.com/Search?keyword=%E6%89%8B%E6%9C%BA"; newurl = decodeURI(url); // "https://search.jd.com/Search?keyword=手机"
encodeURLComponent和decodeURLComponent:
url = "https://search.jd.com/Search?keyword=手机"; newurl = encodeURIComponent(url); // "https%3A%2F%2Fsearch.jd.com%2FSearch%3Fkeyword%3D%E6%89%8B%E6%9C%BA" url = "https%3A%2F%2Fsearch.jd.com%2FSearch%3Fkeyword%3D%E6%89%8B%E6%9C%BA"; newurl = decodeURIComponent(url); // "https://search.jd.com/Search?keyword=手机"
和encodeURL不同的是encodeURLComponent会将所有的其他符号,例如":" "/" "?"等转换为%n的形式。
除了encodeURL 、encodeURLComponent意外,还有escape、unescape:
url = "https://search.jd.com/Search?keyword=手机"; escape(url); //"https%3A//search.jd.com/Search%3Fkeyword%3D%u624B%u673A"
url = "https%3A//search.jd.com/Search%3Fkeyword%3D%u624B%u673A"; unescape(url); //"https://search.jd.com/Search?keyword=手机"
一样可以达到差不多的效果。
四、JS中的eval
在Python中我们学过两个可以通过字符串执行程序的函数,eval()和exec():
Python中的eval,只能执行表达式,eval(表达式):
val = eval("1+2") print(val)
exec(执行代码):
li = [1, 2, 3, 4, 5] loop = """ for i in li: print(i) """ exec(loop)
在JS中,eval()是Python中eval和exec的合体:
val = eval("1+10") console.log(val) li = [1,2,3,4,5] eval("for(var i = 0;i<li.length;i++){console.log(li[i]);}")
五、JS中的时间
d = new Date() //Fri Dec 06 2019 12:59:38 GMT+0800 (中国标准时间)
获取年份、月份、日、时、分、秒:
d.getFullYear(); // 2019年 d.getYear(); // 119,返回的是2019-1900的值 d.getMonth(); //注意月份总是少1,也就是12月获取到的是11 d.getDate(); // 6 (12月6日) d.getDay(); // 5 (星期五) d.getHours(); // 13 (下午1点) d.getMinutes(); // 39 (39分) d.getSeconds(); // 50 (50s)
修改时间属性:
d.setFullYear(2022); // 设置为2022年 d.setMonth(3); // 设置为4月 d.setDate(23); // 设置为23日 ......
六、JS的作用域(重要)
1.首先看一下其他变成语言的作用域,例如C++、JAVA:
void Func(){ if(1 == 1){ int age = 22; } cout<<age<<endl; }
Func(); //报错
上述C++代码中,age实在if的作用域里面的,所以无法在if之外使用。
总结:C++、C#、JAVA等变成语言的作用域是以大括号决定的。
2.Python的作用域
def func(): if 1 == 1: age = 12 print(age) func() //正常执行
Python的作用域是以函数为范围的,if判断是在func函数内部,所以与print是在一个作用域,只要在执行print时,age存在,则不会报错。
3.JS的作用域
JS的作用域与Python比较相似,也是以函数作为作用域的。
function func() { if (1 == 1) { var name = "alex" } console.log(name); } func(); //正常运行
4.JS中的作用域链
当函数中定义函数时:
// 全局变量 myname = "jone"; function func() { // func函数作用域中的局部变量 var myname = "Tony"; function inner() { // inner函数作用域中的局部变量 var myname = "alex"; console.log(myname); } inner(); } func();
inner函数执行时,首先会从自己函数内部查询是否存在myname变量,如果存在则直接打印。所以上述代码打印alex。
如果inner函数内部不存在myname,则会查找上一层函数作用域中的myname,打印Tony。
如果inner和func的作用域都没有myname,则会打印全局变量myname,打印jone。
5.考虑下面特殊情况
// 全局变量 myname = "jone"; function func() { // func函数作用域中的局部变量 var myname = "Tony"; function inner() { console.log(myname); } return inner; } ret = func(); ret();
如上述代码所示,我们在执行func()的时候,inner函数实际上是没有被执行的,func函数返回了inner函数对象,用ret接收。
此时,我们运行ret(),相当于运行inner(),那么此时,inner中打印的myname是打印jone还是Tony???
实验结果,打印Tony,为什么呢??
答案是:在JS中,函数的作用域和作用域链都是在函数执行之前就已经建立了(也就是函数定义后,解释器解释到这个函数定义,并对其进行编译的时候)。所以,虽然这里的ret指向inner函数,看似处于func函数外部,但实际上作用域已经在之前就确定了,所以调用ret的时候,找myname还是在func函数内部找。
6.变量覆盖的情况
function func() { // func函数作用域中的局部变量 var myname = "Tony"; function inner() { console.log(myname); } var myname = "Leo"; return inner; } ret = func(); ret();
我们可以看到在inner函数后面,我们重新定义了myname,这里的myname实际上覆盖了前面的Tony(这个过程是在确定作用域的时候就覆盖了)。多以当我们执行ret的时候,func作用域中的myname就是Leo,所以ret会输出Leo。
7.变量的提前声明
在JS中如果直接调用未定义的变量会报错:
function func(){ console.log(myage); }
但是如果在func函数中存在myage的定义(可能在console.log之后):
function func(){ console.log(myage); // 这里打印undefined var myage = 22; }
console.log(myage)打印undefined。
这是因为当解释器在生成作用域时,会检查一个函数作用域中所有的变量,然后在前面自动声明一下该变量,类似于:
function func(){ var myage; console.log(myage); var myage = 22; }
代码中,var myage;就是声明了变量myage,但没有赋值,此时打印该变量就是undefined。这就叫做变量的提前声明。
七、JS的面向对象
// 定义一个JS类 function Foo(){ this.name = "Leo"; this.printName = function(){ console.log(this.name); } } //创建一个Foo实例对象 f = new Foo(); f.printName();
从上述代码中可以看到,JS中类的定义和函数定义很相似,不同的是变量使用this,这里的this就相当于Python中的self,他指向对象本身(Foo创建出的对象)。
在上述代码中,如果我们创建多个实例对象,每个对象除了有自己的this.name属性以外,在内存空间中各自还有一份this.printName代码。所以这是不合理的。我们改写一下:
// 定义一个JS类 function Foo(arg) { this.name = arg; } // 定义Foo的原型(原型只会定义一次) Foo.prototype = { 'printName': function () { console.log(this.name); } } //创建两个Foo实例对象 f1 = new Foo("Alex"); f2 = new Foo("Leo"); f1.printName(); f2.printName();
使用定义Foo的原型,来将成员方法定义成一份(存放于类的一个单独空间,而不是每个对象中)。
总结:JS的面向对象中,我们只要看到方法中的变量用this.来开头,就表示这个函数定义的是一个类,并且这个函数可以认为是该类的构造函数。然后所有的成员方法都定义到类的原型中。
八、DOM
DOM中对标签的查找,参照 https://www.cnblogs.com/leokale-zz/p/10314031.html。
innerText和innerHTML:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Test1</title> </head> <body> <div id = 'd1'> <a id = "a1">Hello</a> <a id = 'a2'>World</a> </div> <script> d1 = document.getElementById("d1"); console.log(d1.innerText); console.log(d1.innerHTML); </script> </body> </html>
得到的输出结果:
Hello World <a id="a1">Hello</a> <a id="a2">World</a>
可以看出innerText会将所有的HTML标签全部屏蔽掉,只打印div中的所有文本信息。而innerHTML就打印div下的所有标签和内容。
修改innerText内容:
a1 = document.getElementById("a1");
a1.innerText = "Hi";
页面上显示"Hi World"
修改innerHTML内容:
d1 = document.getElementById("d1");
d1.innerHTML = "<a href='http://www.baidu.com'>BAIDU</a>";
页面显示BAIDU超链接。
用value来获取Input类控件的值:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Test1</title> </head> <body> <div> <input id='in1' type="text" value = 'Leo'/> </div> <script> in1 = document.getElementById('in1'); in1.value = 'Kale'; </script> </body> </html>
这样,就将文本框中的值从'Leo'修改为了'Kale'。
使用value来取select控件的值:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Test1</title> </head> <body> <select id = "sele1"> <option value="11">成都</option> <option value="12">简阳</option> <option value="13" selected="selected">青白江</option> <option value="14">新都</option> </select> <script> sele1 = document.getElementById('sele1'); console.log(sele1.value); </script> </body> </html>
"青白江"是默认被选中的,所以在console中打印13。我们就可以通过该取到的值来判断控件选中的是哪个选项。
select标签还有一个特殊的selectedIndex值,表示当前选中的选项的index:
sele1 = document.getElementById('sele1');
console.log(sele1.selectedIndex);
默认是"青白江",对应的index为2(从0开始),也就是第三个。
value获取textarea控件的值:
<textarea id = 'ta1'>my name is leo</textarea> <script> ta1 = document.getElementById('ta1'); console.log(ta1.value); </script>
对于textarea来说,他的value的值是写在标间之间的。
九、DOM样式操作
我们在JS中通过DOM可以操作标签的样式。
1.通过className操作
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Test1</title> <style> .c1{ color: red; } .c2{ background-color: gray; } </style> </head> <body> <textarea id = 'ta1'>my name is leo</textarea> <script> obj = document.getElementById('ta1'); obj.className = "c1"; </script> </body> </html>
通过obj.className = 'c1'将textarea中文本的样色设置为红色。
效果如下:
2.通过classList设置多个style:
obj = document.getElementById('ta1'); obj.classList.add("c1"); obj.classList.add("c2");
效果:
剔除classList中的样式:
obj.classList.remove("c2");
3.通过style直接操作样式细节
obj = document.getElementById('ta1'); obj.style.backgroundColor = 'pink'; obj.style.color = 'blue';
效果如下:
注意,在html和css中,background-color是用"-"隔开的,而在JS中,我们要将其变为backgroundColor,这是个规律。
十、DOM属性操作
DOM对标签的属性进行操作:
obj = document.getElementById('ta1'); obj.setAttribute('myname', 'Leo'); //<textarea id = 'ta1' myname = 'Leo'>my name is leo</textarea> obj.removeAttribute('myname'); // <textarea id="ta1">my name is leo</textarea> obj.attributes; // NamedNodeMap {0: id, 1: myname, id: id, myname: myname, length: 2}
十一、JS动态添加标签
在标签内部动态添加其他标签:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Test1</title> </head> <body> <!-- "+"按钮,点一下,添加一个input框 --> <input type="button" value="+" onclick="addTag();"/> <div id='d1'> <p><input type="text"/></p> <!-- 点击"+"按钮,就在这里添加一个 <p><input type="text"/></p> --> </div> <script> function addTag() { var obj = document.getElementById('d1'); var tag = "<p><input type=\"text\" /></p>"; obj.insertAdjacentHTML("beforeEnd", tag); } </script> </body> </html>
效果:
注意,obj.insertAdjacentHTML("beforeEnd",tag),这里的参数有4种:
beforeBegin、beforeEnd、afterBegin、afterEnd。
分别表示:该标签的前面(外部)、该标签的最后(内部)、该标签的最前面(内部)、该标签的后面(外部)。
在上面代码中,我们向div中动态添加的tag是用字符串形式给出的。我们也可以使用DOM来生成标签:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Test1</title> </head> <body> <!-- "+"按钮,点一下,添加一个input框 --> <input type="button" value="+" onclick="createAndAddTag();"/> <div id='d1'> <p><input type="text"/></p> <!-- 点击"+"按钮,就在这里添加一个 <p><input type="text"/></p> --> </div> <script> function createAndAddTag(){ var ptag = document.createElement("p"); var tag = document.createElement("input"); tag.setAttribute('type','text'); ptag.appendChild(tag) var obj = document.getElementById('d1'); obj.appendChild(ptag); } </script> </body> </html>
效果和使用字符串是一样的,但要注意这种方式添加标签要使用obj.appendChild(),因为在这里tag是对象。
十二、利用DOM提交表单
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Test1</title> </head> <body> <form id="form1" action="http://www.baidu.com"> <input type = "text" /> <input type="submit" value="提交"/> <a onclick="submitForm();">提交</a> </form> <script> // 点击a标签,出发该函数 function submitForm(){ obj = document.getElementById('form1'); obj.submit(); } </script> </body> </html>
上述代码通过<a>标签实现了表单的提交,功能和点击提交按钮是一样的,除了<a>标签,任何标签都可以通过JS来实现submit功能。
十三、信息提示
console.log():打印在浏览器F12的console中。
<body> <input type="button" value="输出" onclick="printMsg();"/> <script> function printMsg(){ console.log("print on console") } </script> </body>
alert():在页面中弹出警告框。
<body> <input type="button" value="输出" onclick="printMsg();"/> <script> function printMsg(){ alert("This is a alert window!") } </script> </body>
confirm():弹出一个确认框。
<body> <input type="button" value="输出" onclick="printMsg();"/> <script> function printMsg(){ var ret = confirm("Are you sure?"); console.log(ret); } </script> </body>
当我们选择确定时,返回值ret为true,点击取消时,返回值是false。
十四、JS获取当前URL并实现跳转
<body> <input type="button" value="打印当前URL" onclick="printURL();"/> <input type="button" value="跳转到百度" onclick="jumpBaidu();"/> <script> // 打印当前URL到console中 function printURL(){ var url = location.href; console.log(url); } // 修改当前URL,即跳转到指定的URL function jumpBaidu(){ location.href = "http://www.baidu.com"; } </script> </body>
如果点击"跳转到百度",浏览器则会直接跳转到baidu首页:
页面刷新:
<body> <input type="button" value="页面刷新" onclick="refreshPage();"/> <script> function refreshPage(){ location.reload(); // 相当于 location.href = location.href; } </script> </body>
十五、定时器
使用setInterval()设置定时循环执行:
<body> <input type="button" value="开始执行" onclick="startLoop();"/> <input type="button" value="结束" onclick="endLoop();"/> <script> function startLoop(){ // 开始1s打印一个Hello obj = setInterval(function(){ console.log("Hello"); },1000) } function endLoop(){ clearInterval(obj); } </script> </body>
点击开始执行按钮,则开始循环在console中打印Hello。点击停止按钮,则停止循环打印。
使用clearInterval来清楚定时器。
只执行一次的定时器:
setTimeout(function(){ console.log("timeout"); },5000);
打开页面后(出发这个函数),等待5s,会在console中打印timeout字符串。
该函数主要用于一些需要延迟处理的效果,例如QQ邮箱删除邮件后,会给你5秒钟的时间来撤销,5秒之后,撤销提示就会消失,就是使用这种方式来实现的。
同样的,使用setTimeout来设置定时器,也可以使用clearTimeout来清除定时器(在等待时间完之前,否则就没意义了)。
十六、事件
事件就是我们为一个控件加上的行为,例如点击、光标、鼠标悬停等。
具体的事件,可以参照 HTML事件属性。
我们在写前段代码的时候,尽量将代码写成 行为 样式 结构 相分离的形式。
什么叫行为、样式、结构相分离??
就是在html标签中,我们不要使用style属性设置样式(css样式),不要使用onclick等属性设置事件(行为)。这样就将css、JS、HTML分离了。
例子:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Test1</title> </head> <body> <div style="background-color: red;" onclick="f1();">Hello</div> <script> function f1(){ console.log("This is function f1()."); } </script> </body> </html>
上述代码就是行为、样式、结构没有分离的形式,在业界被称为DOM0,是比较low的形式。
我们将其修改为行为、样式、结构相分离的形式:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Test1</title> <style> #div1{ background-color: red; } </style> </head> <body> <div id="div1">Hello</div> <script> var d1 = document.getElementById('div1'); d1.onclick = function(){ console.log("This is onclick function."); } </script> </body> </html>
这样的好处是什么?
当HTML中有大量的标签需要添加事件,我们无需在每个标签中添加事件属性。例如表格,表格中有大量的标签,而且可以规律的获取(循环index),如果表格中每一个格子td都需要一个事件,那么我们就可以采用这种方式:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Test1</title> <style> #tb,td{ border: 1px solid black; } #tb td{ width: 100px; } </style> </head> <body> <table id="tb"> <tr><td>1</td><td>2</td><td>3</td></tr> <tr><td>1</td><td>2</td><td>3</td></tr> <tr><td>1</td><td>2</td><td>3</td></tr> </table> <script> var trs = document.getElementsByTagName('tr'); for(var i = 0;i<trs.length;i++){ var tds = trs[i].children; console.log(tds); for(var k = 0;k<tds.length;k++){ var td = tds[k]; td.onmouseover = function () { this.style.backgroundColor = "red"; } td.onmouseout = function () { this.style.backgroundColor = ""; } } } </script> </body> </html>
上述代码事件了,为每一个td添加一个onmouseover和onmouseout事件。
特别注意:在事件函数中,我们使用this.style.backgroundColor来设置样式,这里this代表调用这个事件函数的对象,当我们的鼠标移到某个td上时,就是这个td调用的这个函数。
这里一定注意,this不能替换为td,因为在循环绑定事件的时候,这个匿名函数是没被执行的,当循环到最后一个td时,这个function就变成了 最后的td.style.backgroundColor = "red"。那么在以后触发事件,调用该函数的时候,一直都是最后一个td发生样式的变化,和初衷违背。
记住:谁调用事件,this就是谁。。。
十七、事件的三种绑定方式
这里聊一下三种绑定方式中的this。
第一种(DOM0不推荐):
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>test2</title> </head> <body> <input type="button" value="点击" onclick="clickOn(this);" /> <script> function clickOn(ths){ //这是的参数ths,就是通过onClick传递过来的this,this代表当前被点击的按钮 ths.style.backgroundColor = "green"; } </script> </body> </html>
第二种(DOM1推荐):
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>test2</title> </head> <body> <input id="ipt1" type="button" value="点击" /> <script> var ipt1 = document.getElementById("ipt1"); ipt1.onclick = function(){ this.style.backgroundColor='red'; } </script> </body> </html>
这种通过DOM来给标签绑定事件的方式,匿名函数中的this就直接代表当前动作标签。
第三种(DOM2比较高级):
这种绑定方式可以为一个标签同时绑定多个事件(事件可以相同)。
<body> <input id="ipt1" type="button" value="点击" /> <script> var ipt1 = document.getElementById("ipt1"); ipt1.addEventListener("click",function(){console.log("aaa")},false); ipt1.addEventListener("click",function(){console.log("bbb")},false); </script> </body>
可以看到,我们为按钮绑定了2个click事件,也就是说,我们在点击按钮时,console中会同时打印aaa和bbb。
在addEventListener函数中,第一个参数是事件的类型,第二个参数是绑定的函数,第三个参数是模式(分为捕捉型true和冒泡型false)。
捕捉型和冒泡型:
捕捉型就是从最上层先触发事件,然后依次往下。冒泡型就是从底层标签开始触发事件,依次往上。例如:
<head> <meta charset="UTF-8"> <title>test2</title> <style> #d1{ background-color: red; width: 300px; height: 400px; } #d2{ background-color: pink; width: 150px; height: 200px; } </style> </head> <body> <div id="d1"> <div id="d2"></div> </div> <script> var div1 = document.getElementById("d1"); var div2 = document.getElementById("d2"); div1.addEventListener("click",function(){console.log("div1")},false); div2.addEventListener("click",function(){console.log("div2")},false); </script> </body>
上述代码中,div1中有div2,div2属于底层标签,div1是上层标签。如下图,粉色为div2,红色为div1。
当我们第三个参数使用false时,我们点击粉色部分(粉色部分是重叠的),这里先打印div2,后打印div1。
当我们第三个参数使用true时,我们点击粉色部分(粉色部分是重叠的),这里先打印div1,后打印div2。
十八、JS的词法分析(重要理论)
函数在定义的时候解释器会对其进行词法分析,分析的时候会由一个active object(AO)来记录函数中有哪些变量,例如形参、实参、局部变量、函数声明表达式等。
例如:
<script> function func1(age){ console.log(age); var age = 27; console.log(age); function age(){} console.log(age); } func1(3); </script>
我们来看看func1的词法分析过程:
1.AO分析形参,发现有一个形参age,所以AO.age == undefined
2.AO分析实参,发现实参为3,所以AO.age == 3
3.AO分析局部变量,发现有一个age,但是函数未运行,无法赋值27,所以AO.age == undefined (这里覆盖了前面的AO.age==3)
4.AO分析函数声明表达式,发现有一个age(),所以AO.age==function age()
5.分析完毕后,函数开始执行
6.执行时遇到第一个console.log(age),所以打印f age(){}
7.然后遇到age赋值27,所以AO.age ==27
8.再遇到第二个console.log(age),所以打印27
9.跳过函数声明
10.遇到第三个console.log(age),所以还是打印27
最终在console的打印结果为:
我们再用此理论分析之前的一个简单例子:
function func2(){ console.log(name); var name='alex'; } func2();
分析过程:
1.AO发现没有形参,也没有实参。
2.AO发现局部变量name,但这个阶段是布管值的,所以AO.name == undefined
3.函数执行
4.遇到console.log(name),所以打印undefined
5.遇到局部变量赋值,AO.name == "alex"