[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"

原文地址:https://www.cnblogs.com/leokale-zz/p/11994121.html