Curious Passionate Dream-Chaser Hacker

JavaScript学习笔记(二)

WEB API

Web API 是浏览器提供的一套操作浏览器功能和页面元素的 API(BOM 和 DOM)。

BOM的顶级对象 window

对话框

  • alert():显示带有一段消息和一个确认按钮的警告框。
  • prompt():显示可提示用户输入的对话框。
  • confirm():显示带有一段消息以及确认按钮和取消按钮的对话框。

时间加载对象

  • onload
1
2
3
4
window.onload = function () {
// 当页面加载完成执行
// 当页面完全加载所有内容(包括图像、脚本文件、CSS 文件等)执行
}
  • onunload
1
2
3
window.onunload = function () {
// 当用户退出页面时执行
}

浏览器尺寸

1
2
3
4
5
6
7
var width = window.innerWidth
|| document.documentElement.clientWidth
|| document.body.clientWidth;

var height = window.innerHeight
|| document.documentElement.clientHeight
|| document.body.clientHeight;

定时器

  • setTimeout(function(){},1000) 方法在指定的毫秒数到达之后执行指定的函数,只执行一次。clearTimeout() 方法取消由 setTimeout() 方法设置的 timeout。
  • setInterval() 方法设置定时调用的函数也就是可以按照给定的时间(单位毫秒)周期调用函数,clearInterval() 方法取消由setInterval() 方法设置的 timeout。

DOM HTML

改变 HTML 输出流
docuument.write()

改变HTML内容
document.getElementById().innerHTML =

改变HTML属性
document.getElementById(id).attribute = new value

改变CSS样式
document.getElementById(id).style.property=new style

property是具体的属性比如说color

通过标签名找到HTML元素

1
2
3
4
5
6
7
8
9
10
11
12
<html>
<body>
<input type="text" />
<input type="text" />

<script>
document.getElementsByTagName("input")[0].value="hello"; // 下标为 [0] 表示选取第 1 个 input 标签
document.getElementsByTagName("input")[1].value="shiyanlou"; // 下标为 [1] 表示选取第 2 个 input 标签
</script>

</body>
</html>

DOM结点操作

创建结点

  • 创建元素节点:使用 createElement()方法 比如:
    var par = document.createElement("p");

  • 创建属性节点:使用 createAttribute()方法。

  • 创建文本节点:使用 createTextNode()方法。

插入子节点

  • appendChild ()方法向节点添加最后一个子节点。
  • insertBefore (插入的新的子节点,指定的子节点)方法在指定的子节点前面插入新的子节点。如果第二个参数没写或者为 null,则默认插入到后面。

删除结点
使用removeChild() 方法

1
2
父节点.removeChild(子节点);
node.parentNode.removeChild(node); //如果不知道父节点是什么,可以这样写

替换子节点
使用 node.replaceChild(newnode,oldnode) 方法。

设置节点的属性

  • 获取:getAttribute(名称)
  • 设置:setAttribute(名称, 值)
  • 删除:removeAttribute(名称)

DOM事件

事件三要素
事件由:事件源 + 事件类型 + 事件处理程序组成。

  • 事件源:触发事件的元素。
  • 事件类型:事件的触发方式(比如鼠标点击或键盘点击)。
  • 事件处理程序:事件触发后要执行的代码(函数形式,匿名函数)。
事件名 说明
onclick 鼠标单击
ondblclick 鼠标双击
onkeyup 按下并释放键盘上一个键时触发
onchange 文本内容或下拉菜单中的选项发生改变
onfocus 获得焦点,表示文本框等获得鼠标光标。
onblur 失去焦点,表示文本框等失去鼠标光标。
onmouseover 鼠标悬停,即鼠标停留在图片等的上方
onmouseout 鼠标移出,即离开图片等所在的区域
onload 网页文档加载事件
onunload 关闭网页时
onsubmit 表单提交事件
onreset 重置表单

例子1.鼠标单击事件
<p onclick="this.innerHTML='我爱学习,身体好好!'">请点击该文本</p>

例子2.鼠标双击事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8">
<title></title>

</head>

<body>

<h1 ondblclick="changetext(this)">请点击该文本</h1>
<script>
function changetext(id) {
id.innerHTML = "我爱学习,身体棒棒!"
}
</script>

</body>

</html>

异常处理

try-catch语句捕获异常

1
2
3
4
5
try{
//这里写可能出现异常的代码
}catch(err){
//在这里写,出现异常后的处理代码
}

需要注意以下几点:

  • 语句 try 和 catch 是成对出现的。
  • 如果在 try 中出现了错误, try 里面出现错误的语句后面的代码都不再执行, 直接跳转到 catch 中,catch 处理错误信息,然后再执行后面的代码。
  • 如果 try 中没有出现错误,则不会执行 catch 中的代码,直接执行后面的代码。通过 try-catch 语句进行异常捕获之后,代码将会继续执行,而不会中断。

throw语句

通过throw语句我们可以创建自定义错误

面向对象编程

什么是对象
ECMA-262 把对象(object)定义为“属性的无序集合,每个属性存放一个原始值、对象或函数”。我们可以从以下两个层次来理解对象到底是什么。

  • 对象是单个事物的抽象。比如一支笔,一本书,一辆车都可以是一个对象。
  • 对象是一个容器,封装了属性和方法。比如:一辆车。它的颜色,大小,重量等是它的属性,而启动,加速,减速,刹车等是它的方法。

什么是面向对象编程
面向对象程序设计(英语:Object-oriented programming,缩写:OOP)是种具有对象概念的程序编程典范,同时也是一种程序开发的抽象方针。它可能包含数据、属性、代码与方法。对象则指的是类的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性,对象里的程序可以访问及经常修改对象相关连的数据。在面向对象程序编程里,计算机程序会被设计成彼此相关的对象。

🌰举个栗子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<html>

<head>
<meta charset="UTF-8">
<title></title>
<style type="text/css">
div,p{
width: 200px;
height: 100px;
}
</style>
</head>
<body>
<div>你好吗?</div>
<p>我很好</p>
<div>测试一下嘛</div>
<p>好的啊</p>
<script>
function getTagname(tagName){
return document.getElementsByTagName(tagName);
}
function setStyle(arr){
for(var i = 0;i < arr.length;i++){
arr[i].style.backgroundColor = "red";
}
}
var divs = getTagname("div");
setStyle(divs);
var ps = getTagname("p");
setStyle(ps);
</script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8">
<title></title>
<style type="text/css">
div,
p {
width: 200px;
height: 100px;
}
</style>
</head>

<body>
<div>你好吗?</div>
<p>我很好</p>
<div>测试一下嘛</div>
<p>好的啊</p>
<script>
var test = {
getEle: {
//实际上本案例只需要写 tag,但是为了体现面向对象的思想,我们把获取获取节点的三种方式都写出来
tag: function(tagName) {
return document.getElementsByTagName(tagName);
},
id: function(idName) {
return document.getElementById(idName);
},
class: function(className) {
return document.getElementsByClassName(className);
}
},
//实际上本案例只需要写 setStyle,同样的为了体现面向对象编程的思想,我们可以设置添加移除修改样式的函数。
setCss: {
setStyle: function(arr) {
for(var i = 0; i < arr.length; i++) {
arr[i].style.backgroundColor = "red";
}
},

updateCss: function() {},
deleteCss: function() {}
// ...
}

};

var divs = test.getEle.tag("div");
test.setCss.setStyle(divs);
var ps = test.getEle.tag("p");
test.setCss.setStyle(ps);
</script>
</body>

</html>

构造函数

💨构造对象

1
2
3
4
5
6
7
8
9
function Student(name,age,gender){
this.name = name;
this.age = age;
this.gender = gender;
this.sayHi = function(){
console.log("hi,my name is "+this.name);
}
}
var s1 = new Student('zhangsan', 18, 'male');

是不是有点神奇?让我们再回忆一下工厂函数

1
2
3
4
5
6
7
8
9
10
11
12
function createStudent(name, age, gender) {
var student = new Object();
student.name = name;
student.age = age;
student.gender = gender;
student.sayHi = function(){
console.log("hi,my name is "+this.name);
}
return student;
}
var s1 = createStudent('zhangsan', 18, 'male');
var s2 = createStudent('lisi', 19, 'male');

来看看构造函数与工厂函数的区别:

  • 首先在构造函数内没有创建对象,而是使用 this 关键字,将属性和方法赋给了 this 对象。
  • 构造函数内没有 return 语句,this 属性默认下是构造函数的返回值。
  • 函数名使用的是大写的 Student。
  • 用 new 运算符和类名 Student 创建对象。

原型: prototype

在 JavaScript 中,每一个函数都有一个 prototype 属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
function Student(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
Student.prototype.sayHi = function(){
console.log("hi");
}
var s1 = new Student('zhangsan', 18, 'male');
s1.sayHi();//打印 hi
var s2 = new Student('lisi', 18, 'male');
s2.sayHi();//打印 hi
console.log(s1.sayHi == s2.sayHi);//结果为true

构造函数、实例、原型三者之间的关系

构造函数的 prototype 对象默认都有一个 constructor 属性,指向 prototype 对象所在函数

1
2
function F() {}
console.log(F.prototype.constructor === F);//结果为ture

通过构造函数得到的实例对象内部会包含一个指向构造函数的 prototype 对象的指针__proto__。__proto__属性最早是火狐浏览器引入的,用以通过实例对象来访问原型,这个属性在早期是非标准的属性。在控制台中运行下面的代码:

1
2
3
function F() {}
var a = new F();
console.log(a.__proto__ === F.prototype); //结果为true

实例对象可以直接访问原型对象成员。所有实例都直接或间接继承了原型对象的成员。

总结: 每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针 constructor,而实例都包含一个指向原型对象的内部指针__proto__ 。

属性搜索原则
属性搜索原则,也就是属性的查找顺序,在访问对象的成员的时候,会遵循以下原则:

  • 首先从对象实例本身开始找,如果找到了这个属性或者方法,则返回。
  • 如果对象实例本身没有找到,就从它的原型中去找,如果找到了,则返回。
  • 如果对象实例的原型中也没找到,则从它的原型的原型中去找,如果找到了,则返回。
  • 一直按着原型链查找下去,找到就返回,如果在原型链的末端还没有找到的话,那么如果查找的是属性则返回 undefined,如果查找的是方法则返回 xxx is not a function。
1
2
3
4
5
6
7
8
9
10
11
12
13
function Student(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
Student.prototype = {
hobby:"study",
sayHi:function(){
console.log("hi");
}
}
var s1 = new Student("wangwu",18,"male");
console.log(Student.prototype.constructor === Student);//结果为 false

这样写也有一个问题,那就是原型对象丢失了 constructor 成员。所以为了保持 constructor 成员的指向正确,建议的写法是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Student(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
Student.prototype = {
constructor: Student, //手动将 constructor 指向正确的构造函数
hobby:"study",
sayHi:function(){
console.log("hi");
}
}
var s1 = new Student("wangwu",18,"male");
console.log(Student.prototype.constructor === Student);//结果为 true

原型链继承

子承父业,JavaScript 中也有继承。接下来我们会学习原型链继承。原型链继承的主要思想是利用原型让一个引用类型继承另外一个引用类型的属性和方法。

1
2
3
4
5
6
7
8
9
10
11
12
function Student(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
Student.prototype.sayHi = function(){
console.log("hi");
}
var s1 = new Student("zhangsan",18,"male");
s1.sayHi(); //打印 hi
var s2 = new Student("lisi",18,"male");
s2.sayHi(); //打印 hi

函数进阶

call

call() 方法调用一个函数, 其具有一个指定的 this 值和分别地提供的参数(参数的列表)。语法为:

fun.call(thisArg, arg1, arg2, ...)

注:

  • thisArg 指的是在 fun 函数中指定的 this 的值。如果指定了 null 或者 undefined 则内部 this 指向 window,同时值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象。是一个可选项。
  • arg1, arg2, …指定的参数列表。也是可选项。
  • 使用调用者提供的 this 值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined。
  • call() 允许为不同的对象分配和调用属于一个对象的函数/方法。
  • call() 提供新的 this 值给当前调用的函数/方法。你可以使用 call 来实现继承:写一个方法,然后让另外一个新的对象来继承它(而不是在新对象中再写一次这个方法)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14

function foods() {
}
foods.prototype = {
price: "¥15",
say: function() {
console.log("My price is " + this.price);
}
}
var apple = new foods();
apple.say(); //My price is ¥15
var orange = new foods();
orange.say(); // My price is ¥15

定义了两遍say方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

function foods() {
}
foods.prototype = {
price: "¥15",
say: function() {
console.log("My price is " + this.price);
}
}
var apple = new foods();
orange = {
price: "¥10"
}
apple.say.call(orange); // My price is ¥10

在一个子构造函数中可以调用父构造函数的call()方法实现继承

1
2
3
4
5
6
7
8
9
10
11
12
function Father(name, age) {
this.name = name;
this.age = age;
}

function Son(name, age) {
Father.call(this, name, age);
this.hobby = 'study';
}

var S1 = new Son('zhangsan', 18);
S1; // Son {name: "zhangsan", age: 18, hobby: "study"}

apply()

apply() 方法与 call() 方法类似,唯一的区别是 call() 方法接受的是参数,apply() 方法接受的是数组。语法为:

fun.apply(thisArg, [argsArray])

  1. 使用apply()方法与push()方法联合将数组添加到另一个数组

    1
    2
    3
    4
    var array = ['a', 'b','c'];
    var nums = [1, 2, 3];
    array.push.apply(array, nums);
    array //["a", "b", "c", 1, 2, 3]

    注:concat() 方法连接数组,不会改变原数组,而是创建一个新数组。而使用 push 是接受可变数量的参数的方式来添加元素。使用 apply 则可以连接两个数组。

    2.与数组相关的内置函数联用

    1
    2
    3
    var numbers = [7, 10, 2, 1, 11, 9];
    var max = Math.max.apply(null, numbers);
    max; //11

    注:直接使用 max() 方法的写法为: Math.max(7, 10, 2, 1, 11, 9);

bind()

bind() 方法创建一个新的函数(称为绑定函数),在调用时设置 this 关键字为提供的值。并在调用新函数时,将给定参数列表作为原函数的参数序列的前若干项。语法为:

1
fun.bind(thisArg[, arg1[, arg2[, ...]]])

注:参数 thisArg :当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用 new 操作符调用绑定函数时,该参数无效。参数:arg1, arg2, …表示当目标函数被调用时,预先添加到绑定函数的参数列表中的参数。

1
2
3
4
5
6
7
8
9
var bin = function(){
console.log(this.x);
}
var foo = {
x:10
}
bin(); // undefined
var func = bin.bind(foo); //创建一个新函数把 'this' 绑定到 foo 对象
func(); // 10

新栗子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
this.num = 6;
var test = {
num: 66,
getNum: function() { return this.num; }
};

test.getNum(); // 返回 66

var newTest = test.getNum;
newTest(); // 返回 6, 在这种情况下,"this"指向全局作用域

// 创建一个新函数,将"this"绑定到 test 对象
var bindgetNum = newTest.bind(test);
bindgetNum(); // 返回 66

是不是很神奇?

1
2
3
4
5
6
7
8
9
var newTest = test.getNum;
newTest();

//上面这两行代码其实相当于:

var newTest(){
return this.num;
}
//所以 this 指向的是全局作用域,返回 6。

作用域

块级作用域

没有块级作用域!使用 {} 标记出来的代码块中声明的变量 num,是可以被 {} 外面访问到的。

函数作用域

字面意思,和c语言类似

全局作用域

我们不用 var 关键字,直接声明变量的话,那个变量就是全局变量,它的作用域就是全局作用域。

使用 windows 全局对象来声明,全局对象的属性也是全局变量。另外在所有的函数外部用 var 声明的变量也是全局变量,这是因为内层作用域可以访问外层作用域。

注:

  • 内层作用域可以访问外层作用域,反之不行。
  • 整个代码结构中只有函数可以限定作用域。
  • 如果当前作用规则中有名字了, 就不考虑外面的同名变量。
  • 作用域规则首先使用提升规则分析。

函数重名

1
2
3
4
5
6
7
8
9
func();
function func(){
console.log("Hello syl");
}

func();
function func(){
console.log("hi syl");
}

运行结果是 两遍 hi syl 相当于下面的函数覆盖了上面的函数然后执行了两遍

函数变量同名的时候:

1
2
3
console.log(foo);
function foo(){}
var foo = 6;

当出现变量声明和函数同名的时候,只会对函数声明进行提升,变量会被忽略。所以上面的代码相当于:

1
2
3
function foo(){};
console.log(foo);
foo = 6;

控制台输出[Function: foo]

闭包

闭包是指函数可以使用函数之外定义的变量。

简单的闭包

在 JavaScript 中,使用全局变量是一个简单的闭包实例。比如:

1
2
3
4
5
var num = 3;//直接  num = 3也行
function foo(){
console.log(num);
}
foo(); //打印 3

复杂的闭包

1
2
3
4
5
6
7
8
9
10
function f1(){
var num1 = 6;
function f2(){
var num2 = 7;
return num2;
}
return f2();
console.log(num1 + num2);
}
f1()

在上述代码中函数 f2 能够访问到它外层的变量 num,但是 f1 是不能访问 f2 中的变量的,因此我们可以把 num2 作为 f2 的返回值,再把 f2 作为返回值就可以访问到了。

arguments对象

在函数代码中,使用特殊对象 arguments,无需明确指出参数名,我们就能访问它们。第一个参数是 arguments[0],第二个参数是 arguments[1],以此类推。比如:

1
2
3
4
5
6
function foo() {
console.log(arguments[0]);
console.log(arguments[1]);

}
foo(2,3);//打印 2 3

还可以用 arguments 对象检测函数的参数个数,引用属性 arguments.length 即可。来看一个遍历参数求和的例子:

1
2
3
4
5
6
7
8
9
10
11
function add() {
var sum =0;
for(var i=0; i<arguments.length; i++){
sum += arguments[i];
}
return sum;
}
add(); // 0
add(1); // 1
add(1,2); // 3
add(1,2,3); // 6

Function对象

用 Function 对象创建函数的语法如下:

1
var function_name = new Function(arg1, arg2, ..., argN, function_body)

注:每个参数都必须是字符串,function_body 是函数主体,也就是要执行的代码。

例子:

1
2
var add = new Function("a","b","console.log(a+b);");
add(2,5); //打印7

Function 对象的 length 属性

函数属于引用类型,所以它们也有属性和方法。length 属性声明了函数期望的参数个数。

例子:

1
2
var add = new Function("a","b","console.log(a+b);");
console.log(add.length); // 打印2

Function 对象的方法

Function 对象也有与所有对象共享的 valueOf() 方法和 toString() 方法。这两个方法返回的都是函数的源代码。

例子:

1
2
3
var add = new Function("a","b","console.log(a+b);");
add.valueOf();
add.toString();

本文标题:JavaScript学习笔记(二)

文章作者:Hooo Jerry

发布时间:2019年07月31日 - 17:17

最后更新:2019年08月11日 - 22:25

原始链接:http://hoooJerry.com/2019/07/31/JavaScript学习笔记2/

许可协议:除特殊说明外,本博客所有文章均采用 CC BY-NC-ND 4.0协议 。转载请保留原文链接及作者。