JS笔记

JS基础

一、初识js

1. 书写位置

  • 内嵌式的js
  • 外部引入
  • 行内样式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<head>
<style></style>
<!-- 2.内嵌式的js -->
<script>
// alert('沙漠骆驼');
</script>
<!-- 3. 外部js script 双标签 -->
<script src="my.js"></script>
</head>

<body>
<!-- 1. 行内式的js 直接写到元素的内部 -->
<!-- <input type="button" value="唐伯虎" onclick="alert('秋香姐')"> -->
</body>

2. 注释

1
2
3
4
5
6
<script>
// 1. 单行注释 ctrl + /
/* 2. 多行注释 默认的快捷键 shift + alt + a
2. 多行注释 vscode 中修改多行注释的快捷键: ctrl + shift + /
*/
</script>

3. 输入输出语句

1
2
3
4
5
6
7
8
9
10
<script>
// 这是一个输入框
prompt('请输入您的年龄');
// alert 弹出警示框 输出的 展示给用户的
alert('计算的结果是');
// console 控制台输出 给程序员测试用的
console.log('我是程序员能看到的');
// 在页面中显示
document.write("hello,word")
</script>

二、变量

1. 数据类型

1.1 简单数据类型

  • Number, Null, Boolean, String, Undefined,Symbol ,Bigint
1
2
3
4
5
6
7
<script>
var num = 10
var str = '10'
var bool = false
var und = undefined
var nul = null
</script>

1.2 复杂数据类型

  • Object,Array,Funtion

2. 数据类型的转换

2.1 转字符串

1
2
3
4
5
6
7
8
9
// 1. 把数字型转换为字符串型 变量.toString()
var num = 10;
var str = num.toString();
console.log(str);
console.log(typeof str);
// 2. 我们利用 String(变量)
console.log(String(num));
// 3. 利用 + 拼接字符串的方法实现转换效果 隐式转换
console.log(num + '');

2.2 转数字型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 1. parseInt(变量)  可以把 字符型的转换为数字型 得到是整数
// console.log(parseInt(age));
console.log(parseInt('3.14')); // 3 取整
console.log(parseInt('3.94')); // 3 取整
console.log(parseInt('120px')); // 120 会去到这个px单位
console.log(parseInt('rem120px')); // NaN
// 2. parseFloat(变量) 可以把 字符型的转换为数字型 得到是小数 浮点数
console.log(parseFloat('3.14')); // 3.14
console.log(parseFloat('120px')); // 120 会去掉这个px单位
console.log(parseFloat('rem120px')); // NaN
// 3. 利用 Number(变量)
var str = '123';
console.log(Number(str));
console.log(Number('12'));
// 4. 利用了算数运算 - * / 隐式转换
console.log('12' - 0); // 12
console.log('123' - '120');
console.log('123' * 1);

2.3 转布尔型

  • Boolean()

  • 代表空或者否定的值会转化为false,如:’ ‘,0,NaN,null,undefined

1
2
3
4
5
6
7
8
9
console.log(Boolean('')); // false
console.log(Boolean(0)); // false
console.log(Boolean(NaN)); // false
console.log(Boolean(null)); // false
console.log(Boolean(undefined)); // false
console.log('------------------------------');
console.log(Boolean('123'));
console.log(Boolean('你好吗'));
console.log(Boolean('我很好'));

3. 获取数据类型

typeof()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var num = 10;
console.log(typeof num); // number
var str = 'pink';
console.log(typeof str); // string
var flag = true;
console.log(typeof flag); // boolean
var vari = undefined;
console.log(typeof vari); // undefined
var timer = null;
console.log(typeof timer); // object
// prompt 取过来的值是 字符型的
var age = prompt('请输入您的年龄');
console.log(age);
console.log(typeof age);

三、运算符

1. 算数运算符

+ - * / %

1
2
3
4
5
6
7
8
9
10
11
12
13
14
console.log(1 + 1); // 2
console.log(1 - 1); // 0
console.log(1 * 1); // 1
console.log(1 / 1); // 1
// 1. % 取余 (取模)
console.log(4 % 2); // 0
console.log(5 % 3); // 2
console.log(3 % 5); // 3
// 2. 浮点数 算数运算里面会有问题
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.07 * 100); // 7.000000000000001
// 3. 我们不能直接拿着浮点数来进行相比较 是否相等
var num = 0.1 + 0.2;
console.log(num == 0.3); // false

2. 比较运算符

> < == >= <= != === !==

1
2
3
4
5
6
7
8
9
10
11
console.log(3 >= 5); // false
console.log(2 <= 4); // true
//1. 我们程序里面的等于符号 是 == 默认转换数据类型 会把字符串型的 数据转换为数字型 只要求值相等就可以
console.log(3 == 5); // false
console.log('pink老师' == '刘德华'); // flase
console.log(18 == 18); // true
console.log(18 == '18'); // true
console.log(18 != 18); // false
// 2. 我们程序里面有全等 一模一样 要求 两侧的值 还有 数据类型完全 一致才可以 true
console.log(18 === 18);
console.log(18 === '18'); // false

3. 逻辑运算符

&& || !

1
2
3
4
5
6
7
8
// 1. 逻辑与 &&  and 两侧都为true  结果才是 true  只要有一侧为		false  结果就为false 
console.log(3 > 5 && 3 > 2); // false
console.log(3 < 5 && 3 > 2); // true
// 2. 逻辑或 || or 两侧都为false 结果才是假 false 只要有一侧 为true 结果就是true
console.log(3 > 5 || 3 > 2); // true
console.log(3 > 5 || 3 < 2); // false
// 3. 逻辑非 not !
console.log(!true); // false

4. 赋值运算符

= += -= *= /=

1
2
3
4
5
6
7
8
9
var num = 10;
// num = num + 1; num++
// num = num + 2; // num += 2;
// num += 2;
num += 5;
console.log(num);
var age = 2;
age *= 3;
console.log(age);

5. 一元运算符

++ --

  • 前置递增 ++num 先自加,再返回值
  • 后置递增 num++ 先返回原值,后自加1

6. 优先级

!> 算数 > 关系 > 与、或 > 赋值

四、选择结构

1. if

1.1 if单分支

如果 if 里面的条件表达式结果为真 true 则执行大括号里面的 执行语句

如果if 条件表达式结果为假 则不执行大括号里面的语句 则执行if 语句后面的代码

1
2
3
if (条件表达式) {
// 执行语句
}

1.2 if双分支

如果表达式结果为真 那么执行语句1 否则 执行语句2

1
2
3
4
5
if (条件表达式) {
// 执行语句1
} else {
// 执行语句2
}

1.3 if多分支

如果条件表达式1 不满足,则判断条件表达式2 满足的话,执行语句2 以此类推

如果上面的所有条件表达式都不成立,则执行else 里面的语句

1
2
3
4
5
6
7
8
9
if (条件表达式1) {
// 语句1;
} else if (条件表达式2) {
// 语句2;
} else if (条件表达式3) {
// 语句3;
} else {
// 最后的语句;
}

1.4 三元表达式

如果条件表达式结果为真 则 返回 表达式1 的值 如果条件表达式结果为假 则返回 表达式2 的值

1
2
3
// 条件表达式 ? 表达式1 : 表达式2
var num = 10;
var result = num > 5 ? '是的' : '不是的

2. switch

利用我们的表达式的值 和 case 后面的选项值相匹配 如果匹配上,就执行该case 里面的语句 如果都没有匹配上,那么执行 default里面的语句

1
2
3
4
5
6
7
8
9
10
11
switch (表达式) {
case value1:
执行语句1;
break;
case value2:
执行语句2;
break;
...
default:
执行最后的语句;
}

特点:

  • 我们开发里面 表达式我们经常写成变量
  • 我们num 的值 和 case 里面的值相匹配的时候是 全等 必须是值和数据类型一致才可以 num === 1
  • break 如果当前的case里面没有break 则不会退出switch 是继续执行下一个case

五、循环结构

1. for

1
2
3
4
5
6
// for (初始化变量; 条件表达式; 操作表达式) {
// // 循环体
// }
for (var i = 1; i <= 100; i++) {
console.log('你好吗');
}

2. while

当条件表达式结果为true 则执行循环体 否则 退出循环

1
2
3
4
5
6
7
8
// while (条件表达式) {
// // 循环体
// }
var num = 1;
while (num <= 100) {
console.log('好啊有');
num++;
}

3. do while

跟while不同的地方在于 do while 先执行一次循环体 在判断条件 如果条件表达式结果为真,则继续执行循环体,否则退出循环

1
2
3
4
5
6
7
8
// do {
// 循环体
// } while (条件表达式)
var i = 1;
do {
console.log('how are you?');
i++;
} while (i <= 100)

4. continue

continue 关键字 退出本次(当前次的循环) 继续执行剩余次数循环

1
2
3
4
5
6
for (var i = 1; i <= 5; i++) {
if (i == 3) {
continue; // 只要遇见 continue就退出本次循环 直接跳到 i++
}
console.log('我正在吃第' + i + '个包子');
}

5. break

break 退出整个循环

1
2
3
4
5
6
for (var i = 1; i <= 5; i++) {
if (i == 3) {
break;
}
console.log('我正在吃第' + i + '个包子');
}

六、数组

数组(Array) :就是一组数据的集合 存储在单个变量下的优雅方式

1. 创建数组

1
var arr = new Array();   // 利用new 创建数组
1
var arr = [];  //   利用数组字面量创建数组 []

2. 获取数组元素

1
2
3
var arr1 = [1, 2, 'pink老师', true];
console.log(arr1[2]); // pink老师
console.log(arr1[3]); // true

3. 数组常用方法

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
var arr = ["李白", 123, false, undefined, null, true, 9, 5, 7, 13]
// 在数组末尾增加 push()
arr.push("剑仙")
console.log(arr);
// 在数组末尾删除 pop()
arr.pop()
console.log(arr);
// 在数组开头删除 shift()
arr.shift()
console.log(arr);
// 删除 splice()
arr.splice(1, 2)
console.log(arr);
// 修改 splice()
arr.splice(1, 0, "诗圣")
console.log(arr);
arr.splice(1, 3, 10)
console.log(arr);


// 数组转化为字符串 join() 产生一个新数组,不会改变原数组 以xx隔开
var arr1 = arr.join("1")
console.log(arr1);
// 合并两个数组 concat() 产生一个新数组,不会改变原数组
var arr3 = arr.concat(arr1)
console.log(arr3);
// 数组的截取 slice() 产生一个新数组,不会改变原数组
var arr4 = arr.slice(1, 3)
console.log(arr4);
// 数组的翻转 reverse() 产生一个新数组,不会改变原数组
var arr5 = arr.reverse()
console.log(arr5);
// 数组排序 sort() 产生一个新数组,不会改变原数组
var arr6 = arr.sort(function (a, b) {
return a - b
})
console.log(arr6);

4. 冒泡排序

1
2
3
4
5
6
7
8
9
10
11
12
var arr = [4, 1, 2, 3, 5];
for (var i = 0; i <= arr.length - 1; i++) {
for (var j = 0; j <= arr.length - i - 1; j++) {
if (arr[j] < arr[j + 1]) {
var temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}

}
}
console.log(arr);

5. 数组最大值

1
2
3
4
5
6
7
8
var a = [2, 5, 7, 343, 22, 23, 65, 234]
var max = 0
for (var i = 0; i < 8; i++) {
if (a[i] > a[i + 1]) {
max = a[i]
}
}
console.log(max);

6. 数组去重

1
2
3
4
5
6
7
8
9
10
var arr = [1, 2, 3, 4, 5, 2, 3, 4, 2, 23, 4, 5, 2, 2, 3, 54, 3, 3, 4]
for (var i = 0; i < arr.length - 1; i++) {
for (var j = i + 1; j < arr.length - 1; j++) {
if (arr[i] == arr[j]) {
arr.splice(j, 1)
j-- // 因为每次删除都会改变数组的长度,所以删了之后要是下标-1,之后再+1回到原来下标
}
}
}
console.log(arr);

7. 反转数组

1
2
3
4
5
6
var arr = ['red', 'green', 'blue', 'pink', 'purple', 'hotpink'];
var newArr = [];
for (var i = arr.length - 1; i >= 0; i--) {
newArr[newArr.length] = arr[i]
}
console.log(newArr);

8. 遍历数组

1. for of

  • 不同于forEach的是,for of 循环可以随时退出
1
2
3
4
var arr = ['red', 'green', 'blue', 'pink', 'purple', 'hotpink'];
for(let i of arr) {
console.log(i) //red green blue pink purplr hotpink
}

2. forEach

  • 可以通过return跳出本次循环,执行下一次循环
1
2
3
4
5
6
7
var arr = [1, 2, 3, 4, 5, 6]
arr.forEach((item) => {
if (item === 3) {
return
}
console.log(item) // 1 2 4 5 6
})

3. some

  • 不会对空数组进行检测
  • 不会改变原数组
  • 检测数组里的每一个值是否满足条件,如果有一个满足就返回true,否则返回false
1
2
3
4
5
6
var arr = [1, 2, 3, 4, 5, 6]
const result = arr.some((item) => {
console.log(item) // 1 2 3
return item === 3
})
console.log(result) // true

4. every

  • 不会对空数组进行检测
  • 不会改变原数组
  • 检测数组里的每一个值是否满足条件,如果有一个不满足就返回false,否则返回true
1
2
3
4
5
6
var arr = [1, 2, 3, 4, 5, 6]
const result = arr.every((item) => {
console.log(item) // 1
return item === 3
})
console.log(result) // false

5. map

  • 不会对空数组进行检测
  • 不会改变原数组
  • 按照原始数组元素顺序依次处理,返回一个新数组
1
2
3
4
5
6
var arr = [1, 2, 3, 4, 5, 6]
const result = arr.map((item) => {
console.log(item) // 1 2 3 4 5 6
return item * item
})
console.log(result) // [1 4 9 16 25 36]

6. filter

  • 不会对空数组进行检测
  • 不会改变原数组
  • 对数组进行渲染,返回符合条件的数组
1
2
3
4
5
6
var arr = [1, 2, 3, 4, 5, 6]
const result = arr.filter((item) => {
console.log(item) // 1 2 3 4 5 6
return item > 3
})
console.log(result) // [4,5,6]

7. find()

  • 不会对空数组进行检测

  • 不会改变原数组

  • 找到符合条件的第一项,没有找到返回undefined

1
2
3
4
5
6
var arr = [1, 2, 3, 4, 5, 6]
const result = arr.find((item) => {
console.log(item) // 1 2 3 4 5 6
return item > 3
})
console.log(result) // 4

七、函数

1. 声明函数

1
2
3
4
function sayHi() {
console.log('hi~~');
}
sayHi();
1
2
3
4
var sayHi = function () {
console.log('hi~~');
}
sayHi();

2. 带参数的函数

1
2
3
4
5
6
7
8
9
// function 函数名(形参1,形参2...) { // 在声明函数的小括号里面是 形参 (形式上的参数)
// }

// 函数名(实参1,实参2...); // 在函数调用的小括号里面是实参(实际的参数)
function cook(aru) {
console.log(aru);
}
cook('酸辣土豆丝');
cook('大肘子');
  • 如果实参的个数和形参的个数一致 则正常输出结果
  • 如果实参的个数多于形参的个数 会取到形参的个数
  • 如果实参的个数小于形参的个数 多于的形参定义为undefined 最终的结果就是 NaN

3. 函数的返回值

1
2
3
4
5
6
7
8
9
10
// function 函数名() {
// return 需要返回的结果;
// }
// 函数名();
//只要函数遇到return 就把后面的结果 返回给函数的调用者 函数名() = return后面的结果
function getResult() {
return 666;
}
getResult(); // getResult() = 666
console.log(getResult());
  • return 后面的代码不会被执行
  • 返回的结果是最后一个值
  • 我们的函数如果有return 则返回的是 return 后面的值,如果函数么有 return 则返回undefined

4. arguments伪数组

所有函数都内置了一个arguments对象(只有函数才有arguments),arguments对象中存储了传递的所有实参;当不知道传入的实参的个数(实参个数会变,或者不清楚具体几个),就可以不设置形参,在函数体内部直接用arguments去获得传入的实参

特点:

  • 具有length属性

  • 按索引方式存储数据

  • 不具有数组的push,pop等方法(没有真正的数组的方法)

1
2
3
4
5
function f1() {
console.log(arguments)
}
f1(1, 2, 3, 4);

八、对象

对象: 无序的相关属性和方法的集合,所有的事物都是对象

1. 声明对象

1.1 字面量创建对象

1
2
3
4
5
6
7
8
9
var obj = {
uname: '张三疯',
age: 18,
sex: '男',
sayHi: function() {
console.log('hi~');

}
}

1.2 利用new Object() 创建对象

1
2
3
4
5
6
7
8
9
10
11
12
13
var obj = new Object(); // 创建了一个空的对象
obj.uname = '张三疯';
obj.age = 18;
obj.sex = '男';
obj.sayHi = function() {
console.log('hi~');

}
// (1) 我们是利用 等号 = 赋值的方法 添加对象的属性和方法
// (2) 每个属性和方法之间用 分号结束
console.log(obj.uname);
console.log(obj['sex']);
obj.sayHi();

1.3 利用构造函数创建对象

1
2
3
4
5
6
7
8
9
10
11
12
13
function Star(uname, age, sex) {
this.name = uname;
this.age = age;
this.sex = sex;
this.sing = function(sang) {
console.log(sang);

}
}
var ldh = new Star('刘德华', 18, '男'); // 调用函数返回的是一个对象
// console.log(typeof ldh);
console.log(ldh.name);
console.log(ldh['sex']);

2. new关键字内部

1
2
3
4
5
// new关键字执行过程
// 1. new 构造函数可以在内存中创建了一个空的对象
// 2. this 就会指向刚才创建的空对象
// 3. 执行构造函数里面的代码 给这个空对象添加属性和方法
// 4. 返回这个对象

3. 遍历对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var obj = {
name: 'pink老师',
age: 18,
sex: '男',
fn: function() {}
}
// for in 遍历我们的对象
// for (变量 in 对象) {

// }
for (var k in obj) {
console.log(k);
console.log(obj[k]);
}
// 我们使用 for in 里面的变量 我们喜欢写 k 或者 key

4. Date对象

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
// 日期的方法
let myDate = new Date()
// 获取日期对象
console.log(myDate)
// 可以通过日期对象获取年月日
console.log(myDate.getFullYear())
// js从0月开始
console.log(myDate.getMonth())
// 获取多少号
console.log(myDate.getDate())
// 星期几
console.log(myDate.getDay())
// 时分秒
console.log(myDate.getHours());
console.log(myDate.getMinutes());
console.log(myDate.getSeconds());
console.log(myDate.getMilliseconds());
// 获取本地日期
console.log(myDate.toLocaleDateString());
// 获取本地时间
console.log(myDate.toLocaleString());
function getDate(date1, date2) {
let time1 = new Date(date1)
let time2 = new Date(date2)
console.log(time1)
console.log(time2)
return (time2 - time1) / 1000 / 60 / 60 / 24
}
console.log(getDate("2021-7-12", "2035-7-15"))

5. for in 和for of 的区别

  • for-in只是获取数组的索引;而for-of会获取数组的值

  • for-in会遍历对象的整个原型链,性能差;而for-of只遍历当前对象,不会遍历原型链

  • 对于数组的遍历,for-in会返回数组中所有可枚举的属性(包括原型链上可枚举的属性);for-of只返回数组的下标对应的属性值

  • for-of适用遍历数组/字符串/map/set等有迭代器对象的集合,但是不能遍历普通对象(obj is not iterable)

九、字符串

1. 字符串的常用方法

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
// 字符串的长度                length
console.log(str1.length)
// 通过下标获取某个字符 str.charAt()
let str5 = '我是一个字符串'
console.log(str5.charAt(3))
// 截取字符串 str.slice()
console.log(str5.slice(2, 4))

// 返回字符串第一次出现的位置 没有出现过返回-1 str.indexOf()
console.log(str5.indexOf('123'))
console.log(str5.search('个'))
// indexOf和search的区别 indexOf只能匹配字符串
// search可以匹配 正则和字符串

// 替换字符串 str.replace()
let str6 = 'sldjfsljflskjfdslfjdsl'
let str7 = str6.replace('s', '我的')
console.log(str7, str6)

// 分割字符串转化为数组 str.split()
console.log(str6.split('s'))

// 表单里面比较重要的的 str.trim()
let str8 = ' 我的字符 '
console.log(str8.length)
console.log(str8.trim().length)

// 判断字符串是否有某个字符 str.includes()
console.log(str8.includes('我'))
// 多一嘴 数组也是一样的方法
let arr = [1, 2, 3, 45, 3, 1, '123', 12]
console.log(arr.includes(123))

十、正则

1. 正则规则

语法 描述
i 忽略大小写
g 全局
\d 数字 [0-9]
\w [0-9,a-z,A-Z]
\s 空格
\D 非数字 [^0-9]
\W 非单词 【^a-zA-Z0-9】
\S 非空格
\ 转义字符
+ 代表一个或者十多个
? {0,1} 可以有可以没有 可以有0个也可以有1个
* 可以没有 有的话可以使无限个
{n} {5} 只能有5个
{n,m} 则代表 最少有n个最多有m个
{n,} 最少有n个 多则不限
| ^ $ 或者 开头 结尾
/^1[3-9]\d{9}$/ 开头第一个字符为1 第二个字符为3~9之间 第三个字符0到9的数字一定要有9个就结束了

2. 正则比较

1
2
3
4
5
6
7
8
9
10
// let reg = 手机的规则1开头第二个数字是3到9 然后又9位的数字组成
let reg = /^1[3-9]\d{9}$/

// 正则作比较的方法 test match
// 1. test()方法
let phoneNumber = prompt('请输入一个手机号')
console.log((reg.test(phoneNumber)));

// 2. match()方法
// match 方法 不满足规则的时候 返回空 null 满足则返回数组

DOM总结

一、 获取元素方式

1. 传统获取元素方式

方法 描述
document.getElementById() 获取指定id的一个第一个对象的引用
document.getElementsByClassName() 返回文档中所有指定类名的元素集合,作为 NodeList 对象
document.getElementsByTagName() 返回带有指定标签名的对象集合
document.getElementsByName() 返回带有指定名称的对象集合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<div id="mydiv">mydiv</div>
<div class="mydivClass">mydiv</div>
<div name="mydivName">mydiv</div>
<div>mydiv</div>
<div id="mydiv2">
<p class="myP">我是P</p>
<p class="myP">我是P</p>
<p class="myP">我是P</p>
<p>我是P</p>
<p>我是P</p>
</div>

<script>
// dom操作 文档对象模型
// Document Object model
// 获取dom节点 :js获取html标签
let div = document.getElementById('mydiv') //通过id获取dom节点
let div1 = document.getElementsByClassName('mydivClass') //通过class获取dom节点
let div2 = document.getElementsByName('mydivName') // 通过name属性获取dom节点
let div3 = document.getElementsByTagName('div') //通过标签获取dom节点
</script>

2. H5新增获取元素方式

方法 描述
document.querySelector() 获取得到的是一个节点 如果参数可以选中多个节点 那么返回该选择器匹配的第一个节点
document.querySelectorAll() 获取得到的是一个集合
1
2
let div4 = document.querySelector('#mydiv2 p')
let div5 = document.querySelectorAll('#mydiv2 p:not(.myP)')

3. 两种获取元素的区别

区别:

  • query方法获取元素获取的是静态节点
  • getElement获取元素获取的是动态节点
  • 即:getElement获取的节点会随DOM的变化而变化,而query获取之后就不会再改变

二、 节点操作

1. 获取子节点

方法 描述
dom.previousElementSibling 获取某个节点的哥哥元素的节点
dom.nextElementSibling 获取某个节点的弟弟元素的节点
dom.childNodes 获取所有的子节点
dom.childElementCount 返回所有的子元素的个数
dom.firstChild 获取父元素下的第一个子节点
dom.firstElementChild 获取父元素下的第一个子节点(IE6,7,8不支持)
dom.lastChild 获取父元素下的最后一个子节点
dom.lastElementChild 获取父元素下的最后一个子节点(IE6,7,8不支持)

2. 创建节点

方法 描述
dom.createElement() 创建元素节点
dom.createTextNode() 创建文本节点
dom.appendChild() 添加节点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div id="mydiv2">
<div id="mydiv3" onclick="removeMydiv3(this)"></div>
</div>
<button onclick="createDiv()">点击创建</button>

<script>
// 创建节点 createElement() createTextNode()
function createDiv() {
let newP = document.createElement('p')
let str = document.createTextNode('我是新加的')
newP.appendChild(str)
let mydiv1 = document.getElementById('mydiv1')
mydiv1.appendChild(newP)
}
</script>

3. 删除节点

方法 描述
dom.removeChild() 删除父元素的子元素节点
dom.remove() 删除元素节点本身
1
2
3
4
5
6
7
8
function removeDiv () {
// 删除节点要通过父元素删除子元素
mydiv2.removeChild(mydiv3)
}
function removeMydiv3 (mythis) {
let ziji = document.getElementById('mydiv3')
console.log(mythis)
}

4. 添加删除 class

方法 描述
dom.className = 'class' 覆盖整个class类
dom.classList.add('class') 添加一个class类
dom.className = '' 删除所有的class
dom.classList.remove('class') 删除指定的class类

5. 添加属性

dom.setAttribute('属性名','属性值')

1
img.setAttribute('src','./images/close-16x16.png')

6. 添加标签

dom.innerHTML()

1
mydiv.innerHTML +=  `<div class='item'>${name} <img onclick = 'delZiji(this)' src="./images/close-16x16.png" alt=""></div>`

三、DOM操作

方法 描述
dom.insertBefore() 插入节点;(要插入的节点,插到那里去)
dom.replaceChild() 替换节点;(新节点,旧节点)
dom.cloneNode() 克隆节点;(boolen)
dom.getAttribute() 获取元素属性
dom.removeAttribute() 移除元素属性
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
58
59
<p id="wenzi">1232131</p>
<div id="mydiv">
我是div
<div id="div1">我是div1</div>
</div>
<button onclick="insertDiv()">插入节点</button>
<button onclick="replaceDiv()">替换节点</button>
<button onclick="cloneDiv()">克隆节点</button>
<button onclick="getattr()">获取属性</button>
<button onclick="removeAttr()">删除属性</button>
<button onclick="innerDiv()">html</button>

<script>
// DOM操作
// 插入节点 insertBefore()
function insertDiv() {
let mydiv = document.getElementById('mydiv')
let textP = document.getElementById('wenzi')
let div1 = document.getElementById('div1')
// 父元素.insertBefore(要插入的节点,插到那里去)
mydiv.insertBefore(textP, div1)
}
// 替换节点 replaceChild()
function replaceDiv() {
let mydiv = document.getElementById('mydiv')
let textP = document.getElementById('wenzi')
let div1 = document.getElementById('div1')
// 父元素。replaceChild(新节点,旧节点)
mydiv.replaceChild(textP, div1)
}
// 克隆节点 cloneNode()
function cloneDiv() {
let mydiv = document.getElementById('mydiv')

let div1 = document.getElementById('div1')
let div2Node = div1.cloneNode(true)
mydiv.appendChild(div2Node)
}
// 获取元素属性 getAttribute()
function getattr() {
let mydiv = document.getElementById('mydiv')
console.log(mydiv.getAttribute('id'))
}
// 移除元素属性 removeAttribute()
function removeAttr() {
let mydiv = document.getElementById('mydiv')
mydiv.removeAttribute('id')

}
// 其他获取或者是设置属性的方法
// 节点.id 这样可以设置或者是获取id的值
// 节点。src 同理
// 节点。href 同理
// 节点。className 同理
function innerDiv() {
let mydiv = document.getElementById('mydiv')
mydiv.innerHTML = '<p>我是p标签</p>'
}
</script>

四、绑定事件

1. 传统方法绑定

dom.事件类型 = function() {}

2. 现代方法绑定

addEventListener(事件名字,事件处理函数,布尔值)

1
2
3
4
5
6
7
8
9
10
11
12
13
// 传统方法绑定
let div1 = document.getElementById('div1')
div1.onclick = () => {
console.log(11);
}
// 现代方法绑定
// addEventListener(事件名字,事件处理函数,布尔值)
let div2 = document.getElementById('div2')
div2.addEventListener('click', () => {
console.log(222);
})

// 区别: 传统绑定会被覆盖只能绑定一次,现代绑定 可以多次绑定

3. 区别

传统绑定会被覆盖只能绑定一次,现代绑定 可以多次绑定

4. 常见的事件类型

方法 描述
onclick 点击事件
onmousedown 鼠标按下
onmouseup 鼠标抬起
onmouseover 鼠标移入
onmouseout 鼠标移出
onkeydown 键盘按下
onkeyup 键盘抬起
onkeypress 键盘按下(只是监听非功能)
onscroll 窗口改变事件
onload 页面加载完毕时间

BOM总结

一、location对象

方法 描述
location.href 返回当前页面的完整的URL地址
location.search 返回URL后面的参数(类似于”?name = lcy&age=20“)
location.protocol 返回页面使用的协议(通常是httphttps)
location.host 返回页面的域名及端口号
location.hostname 返回页面的域名
location.port 返回页面的端口号
location.pathname 返回页面中的路径或者文件名
location.origin 返回URL源地址
location.hash 返回URL 散列值(#后面的内容)

二、window对象

方法 描述
window.innerHeight 浏览器的高度
window.innerWidth 浏览器的宽度
screenLeft 浏览器距离屏幕的左侧位置
screenTop 浏览器距离屏幕的上边距位置
window.document.documentElement.scrollTop 获取滚动条位置。距离顶部
history.forward() 前进一页
history.back() 后退一页

JS高级总结

1. IIFE

概述:自运行匿名函数,该函数在值创建后就会自动运行,不需要调用也无法调用,生命周期只有一次。

特点:函数执行完毕后就会销毁,不会污染区全局。只执行一次。

1
2
3
4
5
(function (a) {
var a = 1
console.log(arguments);
console.log(a);
})('自运行函数') // 2

2. 递归函数

概述:在函数内部通过名字调用自己本身的一种场景。满足一定的条件就必须停止函数的调用,否则容易陷入死循环。

应用:阶乘,树形菜单,省市区级联选择

1
2
3
4
5
6
7
8
9
10
11
// 递归从 1 成到 10  
function add (n) {
if (n == 1) {
return 1
}
return n * add(n - 1)
}
const a = add(10)
console.log(a); // 3628800

// 相当于 return 10*9*8*7*6*5*4*3*2*1

3. 惰性载入

概述:主要用来减少代码每次执行时的重复性分支判断,通过对对象的重新定义来屏蔽原来的对象的分支判断。

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
let event = {
on: function (dom, type, fn) {
console.log('先判断浏览器');
if (window.addEventListener) { // 现代浏览器
// 判断浏览器后就应该重写 event.on
event.on = function (dom, type, fn) {
dom.addEventListener(type, fn, false)
}
} else if (window.attachEvent) { // 高版本IE
event.on = function (dom, type, fn) {
dom.attachEvent('on' + type, fn)
}
} else { // 兼容所有写法
event.on = function (dom, type, fn) {
dom['on' + type] = fn
}
}

// 保证首次调用能正常监听
return event.on(dom, type, fn)
}
}

// 添加事件
event.on(btn1, 'click', function () {
console.log('btn1被点击了');
})
event.on(btn2, 'click', function () {
console.log('btn2被点击了');
})
event.on(btn3, 'click', function () {
console.log('btn3被点击了');
})

4. 作用域

变量和函数所在的作用范围,离开这个范围就无法使用。

JS执行环境

执行环境也成为执行上下文,在程序运行的时候,会生成一个管理函数和变量的对象,他决定了变量和函数的生命周期,以及访问其他数据的权限。

全局执行环境

最外围的执行环境,任何不在函数中的代码都在全局只想上下文中、

函数执行环境

每当一个函数被调用的时候,都会为该函数创建一个全新的上下文环境并推入执行栈中,在代码执行完毕后,将该环境弹出(即销毁)。

根据执行环境划分作用域

  • 全局作用域:在函数外部分代码,在任意地方都可以使用
  • 函数作用域:只有在函数被定义是才会创建,饱汉子啊父级函数作用域/全局作用域内
  • 块级作用域:``ES6可以通过 letconst` 关键字声明变量,就有可能形成块级作用域。

由于作用域的限制,每段独立的执行代码块只能访问自己作用域和外层作用域中的变量,无法访问内层作用域的变量。

作用域链:当可执行代码内部访问变量时,会先查找本地作用域,如果找到目标变量及返回,否则会去父级作用域继续查找。。。一直找到全局作用域。

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
var a = 3
function fn () {
console.log(a); // 3
var b = 4
console.log(b); // 4
}
fn() // 执行完成函数被销毁
console.log(b); // not defined


var a = 3
function fn () {
console.log(a); // 3
console.log(b); // 初始化前无法访问
let b = 4 // 不会进行预解析 变量提升
}
fn() // 执行完成函数被销毁
console.log(b); // not defined


// ES5
for (var i = 0; i < 5; i++) {
console.log(i); // 0 1 2 3 4
}
console.log(i); // 5

// ES6
for (let j = 0; j < 5; j++) {
console.log(j, '内部'); // 0 1 2 3 4
}
console.log(j, '外部'); // not defined


// 作用域链
var a = 5
function foo () {
console.log(a); // 5
var b = 3
function bar () {
var b = 2
console.log(b); // 2
}
bar()
}
foo()
const arr = [1, 2, 3]
console.log(arr);

5. 闭包(计算属性)

概述:闭包是指嵌套在一个函数内部中的函数。一般情况下函数内可以访问函数外的变量,但是函数外无法访问函数内的变量,但是通过必报这个手段就可以在函数外访问函数内部的变量。

特点

  • 函数嵌套函数
  • 内层函数访问了外层函数的局部变量,导致该变量常驻内存
  • 将内部函数暴露出去(return或者window),实现函数外访问函数内的变量

作用

  • 延长变量的生命周期
  • 创建私有变量

问题

一般函数运行结束后,内部产生的函数和变量都会被释放,所以每次运行函数都是全新的。

但是由于使用闭包后,会导致函数中部分的变量保留到内存中,会增加内存的消耗,所以闭包不能乱用。

实现手段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// window暴露在全局
(function () {
var a = 100 // 外层函数的局部变量

window.getA = function () {
return a
}

window.addA = function () {
a++
}
})()
console.log(getA()); // 100

// return 暴露在全局
const game = (function () {
var a = 100 // 外层函数的局部变量

return function () {
return a
}

})()
console.log(game()); // 100

5.1 封装一个缓存工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function createCache () {
const data = {} // 局部变量,外部无法访问
return {
set (key,val) {
data[key] = val
},
get (key) {
return data[key]
}
}
}
const cache = createCache()
cache.set('token','qwer') // qwer
cache.set('user','admin') // admin
console.log(data) not defined

5.2 计算属性 简易原理

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
// 实现一个首字母大写
function toUpper (str) {
console.log('计算过程复杂');
return str.charAt(0).toUpperCase() + str.substr(1)
}

// 缓存函数
function cached (fn) {
const cache = {} // 缓存数据

return function (str) {
var hit = cache[str] // 从缓存中取出数据

// 如果该字符串第一次执行,那么执行 cache[str] = fn(str)
return hit || (cache[str] = fn(str))
}
}

// 调用缓存函数
const result = cached(toUpper)
console.log(result); // function
console.log(result('helloWord')); // HelloWord
result('helloWord')
result('helloWord')
result('helloWord')
result('helloWord')
result('helloWord') // 只打印一次 计算过程复杂

5.3 任务队列

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
58
59
60
61
62
63
64
65
66
67
// 所有的任务都必须排队,先进先出,即必须等到上一个任务完成,才能开始下一个任务
function queue () {
const awaiting = [] // 任务队列
let isRunning = false // 当亲是否有任务正在执行
function done (task, resolve, reject) {
task()
.then(res => { // 成功
resolve(res)
})
.catch(err => { // 失败
reject(err)
})
.finally(() => { // 最终
// 等待任务队列,如果队列中有任务则触发,否则设置 isRunning 为false ,表示任务处理完毕
if (awaiting.length) {
const next = awaiting.shift() // 最开始进入数组的元素,肯定排在最前面
done(next.task, next.resolve, next.reject)
} else {
isRunning = false
}
})
}

return function (task) { // 接受task作为参数
return new Promise((resolve, reject) => {
if (isRunning) {
awaiting.push({ task, resolve, reject })
} else {
isRunning = true
done(task, resolve, reject) // 执行任务
}
})
}
}

// 任务1
const task1 = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('task1')
}, 3000)
})
}

// 任务2
const task2 = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('task2')
}, 1000)
})
}

// 任务3
const task3 = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('task3')
}, 0)
})
}

const myQueue = queue()
// 先完成任务1,在完成任务2
myQueue(task1).then(res => console.log(res))
myQueue(task2).then(res => console.log(res))
myQueue(task3).then(res => console.log(res))

5.4 闭包案例

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
// 1. 
function a (i) {
var i;
alert(i);
};
a(10); // 弹框10


// 2.
function a (i) {
alert(i); // 弹框10
alert(arguments[0]); // 弹框10
var i = 2;
alert(i); // 弹框2
alert(arguments[0]); // 弹框2
};
a(10);


// 3.
var i = 10;
function a () {
alert(i); // 弹框undefined
var i = 2;
alert(i); // 弹框2
};
a();


// 4.
var i = 10;
function a () {
alert(i); // 弹框10
function b () {
var i = 2;
alert(i); // 弹框2
}
b();
};
a();


// 5.
var scope = "global scope";
function checkscope () {
var scope = "local scope";
function f () {
return scope;
}
return f();
}
checkscope(); // local scope

function checkscope2 () {
var scope = "local scope";
function f () {
return scope;
}
return f;
}
checkscope2()(); // local scope


// 6.
var uniqueInteger = (function () {
var counter = 0;
return function () {
return counter++;
}
}());
uniqueInteger(); // 0 先返回再+1



function counter () {
var n = 0;
return {
count: function () { return n++ },
reset: function () { n = 0; }
};
}
var c = counter();
var d = counter();
c.count(); // 0
d.count(); // 0
c.reset(); // undefined
c.count(); // 0
d.count(); // 1


// 7.
function constfunc (v) {
return function () {
return v;
};
}
var funcs = [];
for (var i = 0; i < 10; i++) {
funcs[i] = constfunc(i);
}
funcs[5]();//值是多少?// 5

function constfunc () {
var funcs = [];
for (var i = 0; i < 10; i++) {
funcs[i] = function () { return i; };
}
return funcs;
}
var funcs = constfunc();
funcs[5]();// 值是多少?// 10 全局环境为10

6. 内存泄漏

概述JS创建变量时会给变量分配内存,对于不再使用的变量没有及时释放,会导致内存占用越来越高,有时候会导致进程崩溃。(全局变量、闭包、DOM元素的引用、定时器)

原因:内存泄漏是指我们无法通过JS访问某个对象,而垃圾回收机制却认为该对象还在被引用,因此垃圾回收机制不会释放该对象,导致该块内存永远无法使用,积少成多,系统会越来越卡以至于崩溃。

解决:避免使用全局变量,手动删除定时器和DOM、removeEventListener移除事件监听。

7. 垃圾回收机制

概述:当浏览器中不再使用的变量,浏览器就会自动将他们回收,这个过程成为垃圾回收。

标记清除法:垃圾回收机制获取根节点并标记他们,然后访问并标记所有来自它们的引用,然后在访问这些对象并标记它们的引用…如此递进结束后若发现有没有标记的(不可达的)进行删除,进入执行环境的不能进行删除

引用计数法:当声明一个变量并赋值时,值+1,当该值被取代时-1,为0时进行删除。

8. 浅拷贝和深拷贝

浅拷贝:只会拷贝第一层,若第一层的属性还是对象或者数组,也是直接拷贝地址。改变新数据会使原数据也跟着改变。

方法扩展运算符... Object.assign() Array.map()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var obj1 = {
name: 'jack',
children: [
{
name: 'john'
}
]
}
var obj2 = { ...obj1 }
// 或者
var obj 2 = Object.assign({},obj1)\

obj1.age = 18
obj1.children.push({
name: 'lily'
})

console.log(obj1, obj2);

深拷贝:在内存中重新开辟一块空间,将原数据拷贝一份存入,与原数据无关联。

方法:JSON.parse(JSON.stringify()) lodash_.cloneDeep() 递归遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var obj1 = {
name: 'jack',
children: [
{
name: 'john'
}
]
}
var obj2 = JSON.parse(JSON.stringify(obj1)) // 无法拷贝undefined,方法
obj1.age = 18
obj1.children.push({
name: 'lily'
})

console.log(obj1, obj2);

9. 堆和栈

概述:堆和栈都是运行时内存分配的一个数据区域,两者都是临时存放数据的地方。

基本数据类型:所有的值都保存在内存中,访问的方式是按值访问。

基本数据类型的值被复制的时候,会在栈中创建一个新值,然后把值复制到新变量分配的位置上,两个值互不影响。

引用数据类型:引用数据类型的值是保存在堆内存中的,变量中保存的是一个指针,该指针放在栈中,直接复制的时候,两者互相影响。

10. 面向对象

面向过程:以过程为中心

面向对象:以事物为中心

方法过载:在构造函数直接创建的方法,在实例化所有的对象都会拥有一个独立的方法,不同的对象的同一个方法在不同的对空间中,导致浪费内存。

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
// 方式一
var obj = new Object()

// 方式二
var obj = {
name:'jack',
age:18
}

// 方式三 工厂函数
// 每次调用工厂函数都会在内部创建一个对昂,每一个对象都是一个全新的对象,各个对象之间没有任何的关系
function stu(name,age) {
let obj = {}
obj.name = name
obj.age = age
return obj
}
let s1 = stu()

// 方式四 构造函数
function Stu (name,age) {
// 隐藏代码:let obj = {}
// 隐藏代码:this = obj
this.name = name
this.age = age
this.sayHi = function () {
console.log('hello')
}
// return this
}
let s1 = new Stu('jack',18)

// 原型
function Stu (name,age) {
this.name = name
this.age = age
}
Stu.prototype.sayHi = function () {
console.log('Hi')
}
let s1 = new Stu('jack',18)

10.1 封装

概述:通过封装可以实现隐藏对象内部实现的细节,然后对外提供一致的接口

意义:1. 将不需要公开的数据进行私有化处理,外部就无法直接访问

      2. 可以提供一些特权方法对属性进行访问或者处理
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
(function () {
var _age = Symbol()
var _name = Symbol()

// 构造函数,ES5的类
function User (name, age, sex) {
this[_name] = name // 私有属性 Symbol()独一无二
this[_age] = age
this.sex = sex

this.getName = function () {
return _name
}
this.setName = function (newName) {
_name = newName
}
}
User.prototype.getAge = function () {
if (this.sex === '女') {
return '保密'
} else {
return this[_age]
}
}
// 静态属性
User.version = '1.0.0'

// 暴露
window.User = User
})()

var u1 = new User('jack', 18, '女')
var u2 = new User('chou', 38, '男')
console.log(u1.getAge()); // 保密
console.log(u2.getAge()); // 38
console.log(u2.name); // undefined
u2.setName('曹操')
console.log(u2.getName()); // 曹操
console.log(u1);

10.2 继承

概述:将多个构造函数中的类似代码提取出来,从而减少冗余代码的目的

意义:1. 子类实例化的对象必须拥有弗雷所有的属性和方法

​ 2. 子类实例化的对象也要属于父类

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
// 父级构造函数
function Father (name, age) {
this.name = name
this.age = age
}

Father.prototype.sayHi = function () {
console.log(this.name + 'hi');
}


// 1. 构造函数继承 会独享所有属性,包括引用属性(重点是函数)
function Son (name, age, className) {
this.className = className

}
// 实例化
var s1 = new Son('李白', 18)
s1.sayHi() // not a function
console.log(s1.name); 李白

//2. 原型继承 类的实例共享了父类构造函数的引用属性
function Son (name, age, className) {
this.className = className
}
Son.prototype = new Father()
Son.prototype.constructor = Son

// 实例化
var s1 = new Son('李白', 18, '高三1班')
s1.sayHi()
console.log(s1);


// 3. 组合式继承 调用了两次父类的构造函数,造成了不必要的消耗,父类方法可以复用
function Son (name, age, className) {
this.className = className
// 第一步、更改this指向 实现继承父类所有的属性和方法
Father.apply(this, arguments)
}

// 第二步、继承父类原型上的属性和方法
Son.prototype = new Father()

// 第三步、找回子类的构造函数
Son.prototype.constructor = Son

// 实例化
var s1 = new Son('李白', 18, '高三1班')
s1.sayHi()
console.log(s1);


// 4. 寄生组合继承
function Son (name, age, course) {
this.course = course

// 第一步 子类继承父类的所有的属性和方法
Father.apply(this, arguments)
}

// 第二步 子类继承父类原型方法
function inheritance (Child, Father) {
function F () { }
F.prototype = Father.prototype
Child.prototype = new F() // F 是空函数 几乎不占据内存
Child.prototype.constructor = Child
}
inheritance(Son, Father) // 调用后实现继承
var s1 = new Son('jack', 18, 'js')
s1.sayHi()
console.log(s1);


// 5. class 继承
class Student extends Person { // 子类继承父类
constructor(name, age, className) {
super(name, age) // 子类构造必须调用 super , 理解为父类的构造函数
this.className = className
}
}

var s1 = new Student('lucy', 12, '三年级一班')
console.log(s1);

class Vue {
constructor() {
this.state = {
msg: 'hi'
}
}
get fn () { // 当访问实例的fn属性时,会触发该方法,必须设置返回值
return this.state.msg
}
set fn (newVal) { // 当修改实例的fn属性时,就会触发该方法
this.state.msg = newVal
}
}

var vue = new Vue()
vue.fn = 'hello'
console.log(vue.fn);

10.3 多态

概述:方法可以根据传递的参数类型、参数个数等进行区别,然后返回不同的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 多态
function Father () {
this.show = function () {
console.log('这是父级');
}

}
function Son () {
this.show = function () {
console.log('这是子级');
}
}
function input (obj) {
obj.show()
}
input(new Father)
input(new Son)

11. New关键字

  • 自动创建了一个空对象
  • 修改函数的上下文为空对象,其函数内部的 this 表示该空对象
  • 执行函数体,为该对象添加属性及方法
  • 如果没有返回其他对象,name会自动返回创建的空对象

12. 原型

概述:在JS中,每一个函数都有一个prototype属性,这个属性其实也是一个指针(地址),会指向函数的原型对象,原型队形上所有的属性和方法都可以被实例共享。

作用:解决方法过载的问题,对类的功能进行扩展。

__proto___:每一个实例对象都有一个该属性,会指向它的构造函数的源相对向上。

constructor:每一个原型对象都有一个constructor属性,该属性指向构造函数

12.1 原型链

概述:当访问对象属性的时候,如果对象本身不存在该属性,那么就会在原型对象上查找该属性,如果原型对象上也没有,就会在原型对象的原型上查找,如此循环,指导找到该属性或者达到顶级。对象查找属性的过程所经过的过程构成一个链条,称为原型链。

13. this

13.1 概述

在函数被调用时,会创建一个活动记录(执行上下文),这个记录会包含函数在哪里调用(调用栈)。函数调用的名字、函数参数等信息,而this就是这个上下文中的一个属性,会在函数执行的过程中用到,在非严格模式下总是会指向一个对象,严格模式下可以是任意值。他指向最后调用他的对象。

13.2 this指向

1. 默认绑定

在严格模式下绑定到undefined,非严格模式下绑定到全局对象window

1
2
3
4
5
6
function foo() {
// 'use strict'; // 严格模式
console,log(this) // window
}
window.foo() // foo 函数调用者是window

2. 隐式绑定

当函数引用有上下文对象时,则会把函数中的this绑定到这个上下文对象。

1
2
3
4
5
6
7
8
var obj = {
name:'jack',
sayHi() {
console.log(this)
}
}
obj.sayHi() // obj

3. 构造函数的this

当函数通过new关键字调用,函数内部的this指向新创建的对象

1
2
3
4
5
6
7
8
9
10
function Student() {
this.name = '';
}
Student.prototype.sayHi = function() {
console.log(this.name);
}
var s = new Student();
// s.name
// s.sayHi()

4. 显示绑定

使用callbindapply来指定this指向。

4.1 fn.calll(obj,参数1,参数2,...)

fn中的this指向call的第一个参数。

  • 会立即执行一次fn方法

  • fn中的this临时性修改为obj

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var p1 = {
    name: '周瑜',
    phone(h) {
    console.log(this.name + '给小乔打' + h + '个小时的电话');
    }
    }
    // p1.phone();

    var p2 = {
    name: '曹操'
    }
    p1.phone.call(p2, 10);

4.2 fn.apply(obj,[参数1,参数2,参数3...])

作用与call完全相同,只是传递参数的方式发生改变。

4.3 fn.bind(obj,参数1)

永久性的修改函数中this的指向,一般用在回调函数中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var p2 = {
name: '曹操'
}

var p1 = {
name: '周瑜',
phone(h) {
setTimeout(function() {
console.log(this.name + '给小乔打' + h + '个小时的电话');
}.bind(p2), 0)
}
}
p1.phone(1);

4.4 总结

  • call、apply都是立即执行一次
  • bind被动执行
  • bind会永久性修改this指向
  • call和apply的区别在于 参数传递

5. 箭头函数

箭头函数没有this的绑定,只能通过箭头函数所在的作用域来决定他的值,所以箭头函数中的this始终指向你定义函数时this的指向。

1
2
3
4
5
6
7
8
9
10
var obj = {
name: '章三',
sayHi() {
setTimeout(() => {
console.log(this.name);
}, 0)
}
}
obj.sayHi();

14. JS的设计模式(原理)

14.1 简单工厂模式

概述:为了解决多个类似对象声明的问题

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
function UserFactory (role) {
if(this instanceof UserFactory) { // 判断是否使用new关键词进行调用
if(!this[role]) { // 参数传递是否正确
throw new Erroe('参数错误,可选参数为 SuperAdmin,Admin,User')
}
return new this[role]()
}else { // 判断没有使用 new 关键字 那就重新 new 本身
return new UserFactory(role)
}
}

UserFactory.prototype.SuperAdmin = function () {
this.name = '超级管理员'
this.viewPage = ['首页', '权限', '应用数据', '学生管理']
}

UserFactory.prototype.Admin = function () {
this.name = '管理员'
this.viewPage = ['首页', '应用数据', '学生管理']
}

UserFactory.prototype.User = function () {
this.name = '用户'
this.viewPage = ['首页']
}

// 调用方式
var a = new UserFactory('Admin')
console.log(a);
var b = UserFactory('Admin')
console.log(b);

14.2 单例模式(vuex,modal)

概述:在某些情况下,一些对象只需要一个实例,而不能创建很多个,比如全局缓存、模态框、store,如果实例已经创建,则直接返回。

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
// 单例 只允许一个实例对象
const SingLeton = (function () {
let instance

return function () {
if (instance) {
return instance
}
modal()
return instance = this
}
})()

// 单例1 - 生成模态框
function modal () {
var div = document.createElement('div')
div.style.width = '300px'
div.style.height = '150px'
div.style.backgroundColor = '#ccc'
document.body.appendChild(div)
}

const btn = document.getElementById('btn')
btn.onclick = function () {
new SingLeton()
}

// 单例2 - 全局数据存储对象 vuex的实现
function Store (state) {
this.state = { ...state }
this.setState = function () { }
this.getState = function () { }
}

let createStore = (function () {
let instance

return function (state) {
if (!(this instanceof createStore)) {
return new createStore(state)
}
if (instance) {
return instance
}
return instance = new Store(state)
}
})()

const store = createStore({
num: 0
})
const store1 = new createStore({
msg: 'hi'
})
console.log(store);
console.log(store1);

14.3 策略模式(element表单验证)

概述:定义了一系列的算法,把它们封装起来,并且是他们可以相互的替换。

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
// 案例一-返回今天星期几
var w = new Date().getDay()
var week = ['星期天', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六',]
console.log(week[w]);

// 案例二-年终奖分配
var reward = {
A: function (salary) {
return salary * 2
},
B: function (salary) {
return salary * 1
},
C: function (salary) {
return 0
},
D: function (salary) {
return -salary
},
}

console.log(reward['A'](6000));
console.log(reward['B'](6000));
console.log(reward['C'](6000));
console.log(reward['D'](6000));


// 案例三-表单验证策略
const strategies = {
isEmpty: function (val, err) {
if (val === '') {
return err
}
},
minLength: function (val, len, errMsg) {
if (val.length < len) {
return errMsg
}
}
}

document.getElementById('btn').onclick = function () {
var user = document.getElementById('user').value
var pwd = document.getElementById('pwd').value

var userStrategy = strategies.isEmpty(user, '请输入账号')
var pwdStrategy = strategies.isEmpty(pwd, '请输入密码')
var pwdLength = strategies.minLength(pwd, 6, '密码不能小于6位')
var err = userStrategy || pwdStrategy || pwdLength
if (err) {
return alert(err)
}
}

14.4 观察者模式(双向数据绑定)

概述:观察者模式是指一个对象维持一系列依赖于它的对象,当有状态发生变更的时候,就会去通知这一系列的对象去更新。

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
// 观察者
function Observe (name) {
this.name = name
}
Observe.prototype.update = function (msg) { // 更新
console.log(this.name + '收到' + msg + '通知了');
}

// 主题:楼盘
function Subject (name) {
this.name = name
this.observes = [] // 收集所有关注该楼盘的对象
}
Subject.prototype.add = function (observer) { // 添加关注着
this.observes.push(observer)
}
Subject.prototype.notify = function (msg) { // 通知
this.observes.forEach(item => {
item.update(this.name + '-' + msg)
})
}
Subject.prototype.remove = function (observer) { // 移除某一个
for (let i = 0; i < this.observes.length; i++) {
if (this.observes[i] === observer) {
this.observes.splice(i, 1)
}
}
}
Subject.prototype.clear = function (observer) {// 移除所有
this.observes = []
}


// 创建观察者
const jack = new Observe('jack')
const lilei = new Observe('李磊')
const lucy = new Observe('lucy')

// 创建主题
const hengda = new Subject('恒大')
const wanda = new Subject('万达')

// 观察者关注恒大楼盘
hengda.add(jack)
hengda.add(lucy)
// 观察者管着万达楼盘
wanda.add(lilei)
wanda.add(lucy)

// 发布新楼盘
hengda.notify('破产了')
wanda.notify('新楼盘')

14.5 发布订阅者模式(V2原理,eventBus)

概述:是指希望接受通知的对象给予一个主题通过自定义事件订阅,发布事件的对象通过事件中心去通知所有的主题订阅者。

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
function EventBus () {
this.handleEvents = {}
}
EventBus.prototype.on = function (type,callback) {
// 订阅
if (!this.handleEvents[type]) { // 如果不存在事件,则初始化为一个数组
this.handleEvents[type] = []
}
// 将回调函数添加进去
this.handleEvents[type].push(callback)
}
EventBus.prototype.emit = function (type, ...args) {
// 发布
if (this.handleEvents[type]) { // 如果存在事件
const handleEvent = this.handleEvents[type].slice() // 浅拷贝

handleEvent.forEach(callback => {
callback.apply(this, args) // 确保 this 指针正确
});
}
}

// 订阅房源消息
const bus = new EventBus() // 实践中心
bus.on('wanda', function (msg) {
console.log('lucy订阅的' + msg);
})
bus.on('wanke', function (msg) {
console.log('jack订阅的' + msg);
})
bus.on('hengda', function (msg) {
console.log('乐磊订阅的' + msg);
})

// 发布消息
bus.emit('wanda', '破产了')
bus.emit('wanke', '有新楼盘了')
bus.emit('hengda', '有新楼盘了')

15. defineProperty

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var obj = {
name: 'jack'
}
// 参数:劫持对象,对象属性,属性值
Object.defineProperty(obj, 'age', {
//value:18, // 属性值
//writeable:true, // 是否可以被修改 默认为false,不可以
configurable: true, // 是否可以被删除
enumerable: true, // 是否可以枚举
get: function () { // 访问 age 属性时会调用该方法,返回值就是age的值
return 20
},
set: function () { // 修改age的值得时候,会调用该方法

}
})
console.log(obj);

15.1 单个数据劫持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let data = {
msg: 'Hi'
}

let vm = {}

Object.defineProperty(vm, 'msg', {
get () {
return data.msg
},
// 修改属性值
set (newValue) {
if (newValue === data.msg) {
return
}
data.msg = newValue
document.querySelector('#app').innerHTML = data.msg
}

})

15.2 多个数据劫持

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
let data = {
msg: 'Hi',
num: 0
}

let vm = {}

// Object.keys 返回对象的所有的属性
// Object.values 返回对象的所有的属性值
function defineReact () {
const arr = Object.keys(data)
arr.forEach(key => {
Object.defineProperty(vm, key, {
get () {
return data[key]
},
set (newValue) {
if (newValue === data[key]) {
return
}
data[key] = newValue

// 试图更新
document.querySelector('#app').innerHTML = data[key]
}
})
})
}
defineReact()

ES6总结

1. Symbol

概述:表示独一无二的值

特点

  • Symbol()函数返回值一定是唯一的。
  • Symbol()可以作为对象属性的标识。
  • 调用Symbol()函数不需要加new
  • Symbol()作为对象属性时,不能被for in (Object.keys) 访问。
  • 在其它模块中,只有通过定义的Symbol变量进行访问。

用法

1
2
3
4
5
6
7
8
9
10
11
12
13
var s1 = Symbol(); // 每次调用该函数一定会得到不同的值
var s2 = Symbol();
console.log(s1 == s2); // false

var s3 = Symbol('foo'); // 带上参数与不带参数一样
var s4 = Symbol();
console.log(s3 == s4); // false

const id = Symbol();
let obj = {
[id]: '183910238'
}

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let t1 = {
name: 'jack',
age: 12
};

let t2 = {
name: 'lily',
city: '成都'
};

// 对象合并时,属性相同则会发生覆盖
const t3 = {
...t1,
...t2
}

console.log(t3); // {name: 'lily', age: 12, city: '成都'}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 使用 Symbol 解决对象属性被覆盖的问题
const name = Symbol('name');
const age = Symbol('age');
let t1 = {
[name]: 'jack',
[age]: 12
};

let t2 = {
name: 'lily',
city: '成都'
};

// 对象合并时,属性相同则会发生覆盖
// Object.assign({}, t1, t2)
const t3 = {
...t1,
...t2
}

console.log(t3); // {name: 'lily', city: '成都', Symbol(name): 'jack', Symbol(age): 12}

2. class

概述:classES6提出来的一个概念,更加接近传统语言中的类的写法,作为对象模板

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// ES5
function Father () {
this.name = ''
}
Father.prototype.sayHi = function () { }

Father.version = '1.0.0'

var p1 = new Father()
p1.name
p1.sayHi()
Father.version


// ES6
class Person {
constructor(name,age) { // 构造函数
this.name = name
this.age = age
}
sayHi() { // 原型对象上的方法
console.log('这是ES6的class')
}
static version = '1.0.0'
}
let p2 = new Person('jack,18')
console.log(p2.name) // jack
console.log(Person.version) // 1.0.0
p2.sayHi() // 这是ES6的class


// ES7
class Son {
name = ''
age = ''
sayHi () {
console.log('hi');
}
static version = '1.0.0'
}


// class 继承
class Student extends Person { // 子类继承父类
constructor(name, age, className) {
super(name, age) // 子类构造必须调用 super , 理解为父类的构造函数
this.className = className
}
}

var s1 = new Student('lucy', 12, '三年级一班')
console.log(s1);

// 简单Vue实例的实现
class Vue {
constructor() {
this.state = {
msg: 'hi'
}
}
get fn () { // 当访问实例的fn属性时,会触发该方法,必须设置返回值
return this.state.msg
}
set fn (newVal) { // 当修改实例的fn属性时,就会触发该方法
this.state.msg = newVal
}
}

var vue = new Vue()
vue.fn = 'hello'
console.log(vue.fn);

JS手写题总结

1. 防抖节流

防抖:触发高频事件N秒后只会执行一次,如果N秒内事件再次触发,则会重新计时。

应用场景:

  • search搜索联想,用户在不断输入值时,用房都来节约请求
  • window触发resize的时候,不断地调整浏览器窗口会不断的触发这个事件,用防抖来让其只触发一次

简洁版:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function debounce(func, wait) {
var timeout;
return function () {
var context = this;
var args = arguments;
clearTimeout(timeout)
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
}

// 使用
var node = document.getElementById('layout')
function getUserAction(e) {
console.log(this, e) // 分别打印:node 这个节点 和 MouseEvent
node.innerHTML = count++;
};
node.onmousemove = debounce(getUserAction, 1000)

最终版:

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
// 支持this event
// 支持立即执行 函数可能有返回值 支持取消功能
function debounce(func, wait, immediate) {
var timeout, result;

var debounced = function () {
var context = this;
var args = arguments;

if (timeout) clearTimeout(timeout);
if (immediate) {
// 如果已经执行过,不再执行
var callNow = !timeout;
timeout = setTimeout(function(){
timeout = null;
}, wait)
if (callNow) result = func.apply(context, args)
} else {
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
return result;
};

debounced.cancel = function() {
clearTimeout(timeout);
timeout = null;
};

return debounced;
}

节流:在规定的时间内,只能触发一次函数。如果这个时间内触发多次,则只会执行最后一次。

应用场景:

  • 鼠标不断点击触发,mousedown只触发一次。
  • 监听滚动事件,比如是否滑到底部自动加载更多,用节流来判断

简洁版:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function throttle(func, wait) {
var context, args;
var previous = 0;

return function() {
var now = +new Date();
context = this;
args = arguments;
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
}
}

最终版:

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
// 支持取消节流 传入第三个参数 控制是否立即执行以及结束调用是否还要执行一次
function throttle(func, wait, options) {
var timeout, context, args, result;
var previous = 0;
if (!options) options = {};

var later = function() {
previous = options.leading === false ? 0 : new Date().getTime();
timeout = null;
func.apply(context, args);
if (!timeout) context = args = null;
};

var throttled = function() {
var now = new Date().getTime();
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
};

throttled.cancel = function() {
clearTimeout(timeout);
previous = 0;
timeout = null;
}
return throttled;
}

2、Promise