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