1.变量的定义:
我们向系统申请了一个地方,这个地方门牌号是我们定义好的属性名,然后把属性值赋予属性名。如果重复给同一属性名赋值,最后赋值的一个将成为最终的属性值。
命名规则:
a. 变量名必须以英文字母,_,或$开头
b. 变量名可以为英文字母、_、$或者数字
c. 不可以用系统的关键字和保留字作为变量名
2.值类型-数据类型
不可改变原始值:(有五种,存放在stack / 栈中。first in,last out) (不可改变的含义是每次赋值都会重新开辟一个stack)
Number String Boolean undefined null(null用来占位置的)
引用值:(引用值的值存放在堆里面,栈里面存放的是该引用值堆的地址)
object array function
3.js常见的错误
①低级错误
var a = 1:
如果英文写成了中文或者多写括号少些括号等低级错误,机器会在执行一行一行代码之前通篇扫描中检查出来,一行代码都不会被执行。
②逻辑错误
let a = 1;
document.write(a);
document.write(b);
document.write(20);
第一次通篇扫描扫不出来,在执行一行解释一行时,会发现b未定义,报错,20也不会被输出。
③一个代码块的错误不会影戏其他代码块。
4.js运算符
①运算操作符
a. “+”
第一个作用是两个Number的数字相加
第二个作用是任何东西加字符串都等于字符串
b. - * / % = ()
// 特殊的除法
0/0
// 结果为NaN
1/0
// 结果为Infinity
0/1
// 结果为0
- * / % = ()的优先级
()的优先级最高 = 的优先级最低
备注:Infinity不可能传给后端,Infinity要先变成字符串,再传给后端,后端进行解析
c. ++ -- += -= *= /= %=
let c = c + 1; 等价于 let c += 1;
let a = 10;
let b = ++a - 1 + a++;
document.write(b + "" + a);
// 执行结果:b = 11 -1 + 11; a = 10 + 1 + 1 = 12
// ++a 是a先加 a++ 是a后加
// 小栗子
如果让let a = 123, b = 456;中的a和b互换值
a = a + b;
b = a - b;
a = a - b;
②比较运算符
> < == >= <= !=
// 小栗子
var a = 'a' > 'b';
document.write(a) // 输出为false
reason: 字符串比较的是Ascll码,其中a的的ascll码 小于b的ascll 'a' = "A" + 32 = 113 ; 'A' = 81
//根据上面的结论再来一个 小栗子
var a = '10' > '8'
document.write(a) // 输出为false
reason:先是'1' 跟'8'比较,小于,直接返回false, 否则一直比下去返回值
// 特殊规则
a = 1 == 1; 输出为true
a = undefined == undefined 输出为true
a = undefined == null 输出为true
a = Infinity == Infinity 输出为true
a = NaN == NaN 输出为false == > NaN不等于任何东西,一急眼自己都不认识
③逻辑运算符(重点)
&& || !
&& ===> 会先看&&前面的表达式转换为Boolean,如果只有两个表达式的话,第一个为false,那么直接返回第一个值的结果,否则直接返回第二个值的结果。
// 碰到假就停下返回
// 小栗子 (&& 可以当做短路来判断逻辑)
var a = 1 && 2 + 2; 输出的结果为4
var a = 0 && 2 + 2;输出的结果为0
2 > 1 && document.write('你真棒') 输出的结果是'你真棒';
|| ===> 如果只有两个表达式的话,如果第一个值是true, 那么直接返回第一个值的结果,否则返回第二个值的结果
// || 碰到真就返回
! ===> 取反 把这个东西转换为Boolean取反再取反
// 小栗子
var data = {};
var a = !! data;
document.write(a); 输出为true
5.条件语句
if ,if else
// if 和 && 的转换小栗子
if( 1 > 2) {
document.write('a');
}
等价于
1 > 2 && document.write('a');
switch case
// 根据浏览器端输入的值的不同执行不同的操作
let n = parseInt(window.prompt('input'));
switch(n) {
case 'a':
console.log('a');
break;
case 'b':
console.log('b');
break;
case 'c':
console.log('c');
}
条件语句中的continue 和 break
// continue
for(var a = 0; a < 100; a++) {
if(a % 7 === 0 || a % 10 === 7) {
continue;
}
console.log(a);
}
// for循环中如果a逢7或是7的倍数那么跳出本次循环,输出其他的小于100的数字
// break
// 判断今天输入的date是星期几
let date = parseInt(window.prompt("input"));
switch(date) {
case "monday":
case "tuesday":
case "wednesday":
case "thursday":
case "friday":
console.log("工作日");
break;
case "monday":
case "sunday":
console.log("休息日");
}
// 上述程序如果没有break,会一直走完case "sunday"
6.循环语句
①for循环
// 如何方便地打印10个a
for(var a = 0; a < 10; a++) {
document.write(a)
}
// 原理分析
第一步 var a = 0;
第二步 if(a < 10) { document.write(a) }
第三步 a++;
第四步 if(a < 10) { document.write(a) }
第五步 a++;
第六步 if(a < 10) { document.write(a) }
... 直到 a = 10 退出循环 输出 1 2 3 4 5 6 7 8 9
// 以上的for循环还可以写成下面的
var a = 0;
for(;a < 10;) {
document.write(a);
a++;
}
// 小测试
// 在for循环的()中只能写一句话,执行体只能写一句话,打印100个数
var a = 100;
for(; a-- ;) {
document.write(a + '');
}
输出99 ~ 0
②while循环
// 如果for()里面只写中间部分,比如for(; a < 10 ;)和while循环是完全一样的
var a = 0;
for(;a < 10;) {
document.write(a);
a++;
}
等于
var a = 0;
while(a < 10) {
document.write(a);
a++;
}
// 小栗子
// 写一个逢7 或 7的倍数输出打印切小于100
var a = 100;
while(a % 10 === 7 || a % 7 === 0) {
document.write(a);
a ++;
}
③do{} while() 循环
// do{} while() 循环,不管满足不满足条件,都会执行一次
7.现在穿插几个小逻辑题
①求2^n, n从1开始, n用户输入
var n = parseInt(window.prompt('input'));
var mul = 1; // 用来存储2的n次方
for(var i = 0; i < n; i++) {
mul *= 2;
}
console.log(mul)
// 在浏览器中输入10 输出 1024
②求n!, n为用户输入
var n = parseInt(window.prompt("input"));
var mul = 1; // 用来存储n!
// 因为1!是1,所以初始化i为1
for(var i =1; i <= n; i++) {
mul *= i;
}
console.log(mul);
// 输入10 输出 3628800
③输出100以内的质数(质数是只能被1或者自身整除)
for(var i = 2; i < 100; i ++) {
var count = 0
for(var j = 1; j < Math.sqrt(100); j++) {
if(i % j === 0) {
count ++;
}
}
if(count === 2) {
console.log(i)
}
count = 0;
}
8.初始化引用值
①数组
// 数组里面可以存放很多东西,如 1, 'a', undefined, true,甚至可以是数组
// 小栗子 打印数组中的每一项
var arr = [1, 2, 3, undefined, 'abc'];
let len = arr.length;
for(let i = 0; i < len; i++) {
console.log(arr[i])
}
输出 1 2 3 undefined abc
②对象
// 以前编程面向过程,后来出现了面向对象(按照人的思维方式来思考操作),对象也是存储数据的仓库,要比数组更加直观一点,对象中的每个属性值都要有属性名,属性名可以加双引号,可以不加,属性值可以放boolean/undefined/null等,还可以存放数组/对象
//小栗子 定义一个对象
var lei = {
lastName: 'lei',
age: '23',
sex: 'man',
wife: 'baicai'
}
// 如何取对象的值
lei.lastName ==> 输出 lei
// 如何赋值
lei.firstName = 'zhou';
// 如何删除对象的属性
delete lei.age 删除成功返回true
9.编程形式的区别
1.面向过程
c就是面向过程,它会有机械的想法,第一步想要干嘛,第二步想要干嘛
// 比如 人想要飞
机械: 给人焊一个翅膀,从高处跳下就能飞
人: 不可能
2. 面向对象
人的编程思想
// java javascript c++ 面向对象语言,后来javascript 面向对象和面向过程
10.typeof 操作符(返回的都是 'xxx')
①六中数据类型
number string boolean undefined object(null array object ) function
// typeof 小栗子
var num = 123; typeof(num) // 输出 "number"
var num = [] | {} | null; typeof(num) // 输出 "object"
var num = undefined; typeof(num) // 输出 "undefined"
var num = function() {}; typeof(num) // 输出 "function"
②用法
a. typeof(xxx) 使用typeof(数据)
b. typeof xxx 中间加空格
不过最好使用a方案
// 笔试题
typeof( typeof undefined)
// 输出 "string"
11.类型转化之显式类型转换
①Number()
用途: 看起来不像数字的转换得到NaN,Number目的是千方百计地把()中的值转换为数字
// 小栗子
var num = Number('123'); 输出 123
下面是试了一下其他值的转换
true ==> 1
undefined ==> NaN
null ===> 0
'a' ===> NaN
'2' * '1' ===> 2
'2' - '1' ===> 1
'123abc' ===> NaN
②parseInt()
用途1: 把()中的值转换为整型,小数点的部分去掉,parseInt()的目的是把数字和字符串的数字(数字后面加字符串)转换为整型,
parseInt从数字位一直截到非数字位
// 小栗子
var a = 123.99;
parseInt(a);
// 输出 123
a = '123abc';
parseInt(a);
// 输出 123
a = 'abc123';
parseInt(a);
// 输出 NaN
用途2: parseInt(number, radix(基底)), 以radix为基底进制的数字转换为10进制的数字
// 小栗子
var demo= '10';
var num = parseInt(demo, 16);
console.log(typeof(num) + ' : ' + num);
// 输出 number : 16
num后面的16是16进制, 16进制为
1 2 3 4 5 6 7 8 9 a b c d e f ==> 10
③parseFloat()
用途: 从数字位一直截到第一个小数点结束到第二个小数点,遇到非数字位直接返回前面的数字
// 小栗子
var num = "123.444.555"
parseFloat(num);
输出 123.444
num = '123.44abc';
parseFloat(num);
输出 123.44
num = 'abc44.123abc';
parseFloat(num);
输出 NaN
④String()
用途: 跟Number一样,致力于把()中的值转换为字符串
// 小栗子
var num = 123;
String(num);
输出 '123'
num = undefined;
String(num);
输出 'undefined'
num = null;
String(num);
输出 'null'
⑤Boolean
用途: 除了被认为是false的六个值, 0 '' false undefined null NaN, 其他都是true
// 小栗子
Boolean(0 | '' | false | undefined | null | NaN)
// 输出是false
⑥demo.toString([radix])
用途1: radix如果不传值的话, 用途是将demo转换为数字类型
// 小栗子
var num = 123;
num.toString();
输出 '123'
num = undefined | null;
num.toString();
// 会报错 Uncaught TypeError: Cannot read property 'toString' of null
用途2: radix如果传值的话,是将demo转换为目标基底的值
// 小栗子
var demo = 123;
var num = demo.toString(10);
输出为 123
num = demo.toString(8);
// 有一个好玩的
// 先给你一个二进制的数,让你转换为16
var num = 1010101010;
var test = parseInt(num, 2); // 首先转换为10进制
var tostring = test.toString(16);
输出为 "2aa"
12.类型转化之隐式类型转换(它转换了你都不知道咋转换的,不过它转换的时候内部调用的是显式类型转换)
①isNaN()
// 原理 isNaN里面首先会通过Number()转换,之后再与NaN进行比较
// 小栗子
isNaN('abc') 输出为true
isNaN('123') 输出为false
② ++ -- +(正) -(负)
// 提前调用Number(),'abc'虽然转换成了NaN,但是它的typeof是Number
// 小栗子
var a = 'abc';
a++;
a--;
-a;
+a;
typeof(a); 输出为"number"
③+ (加号)
// 原理: 加号两边如果有string,就会调用String(),把不是字符串的转化成字符串然后连接
var a = 'a' + 1; 输出为 'a1'
④- (减号) * / %
// 原理: 调用Number
// 小栗子
var a = '1' * '1';
console.log(typeof(a) + " " + a);
输出 number 1
⑤ && || !
⑥ > < >= <=
// 原理: 两个值比较,一方有数字,另外一方转换为数字
// 小栗子
var a = '3' > 2;
console.log(a);
输出 为 true
⑦ == !=
// 小栗子
var a = 1 == '1' 输出为 true
var a = 1 == true 输出为 true
undefined == null ==> 因为undefined和null既不大于0,也不小于0,所以相等
// 三等比较 从左到右比较
100 > 10 > 0 ? 输出为 true
首先 100 > 10 输出 true
接着 true > 0 ? 输出为 true
// 特殊需要记忆
NaN == NaN 输出 false 非数不等与自己,也不可能等于其他
⑧不发生类型转换 === 和 !==
// 小栗子
var a = 1 === '1' 输出为 false 因为1 是number类型, '1'是string类型
注意: 在没有定义a的前提现,输出a,会报 a is not defined, 当且仅当输出 typeof(a)的时候回输出undefined,不会报错
13.函数
①函数声明
function test() {}
关键字 函数名
// 函数跟数组一样,栈里面是指向堆的地址,堆里面是内容
// 无论是变量名还是函数名都要遵循"小头风的规则", 如下
function theFirstName() {}
console.log(theFirstName)
输出为 function theFirstName() {}
// js不输出函数的地址,它输出地址指向那个房间的内容
②函数表达式
a. 命名函数表达式
// 命名函数表达从语义上来说是再给变量赋值函数的时候,该函数有函数名
// 小栗子
var test = function abc() {
console.log('a')
}
// abc 是函数名,test()会执行,abc()不会执行
// 输出test.name 输出为abc
b.匿名函数表达式(重点,开发中我们都用这个)
// 匿名函数表达式,从语义上来说是给变量赋值函数的时候,该函数没有函数名
// 小栗子
var test = function() {
console.log('a');
}
// test.name 是 test
c.函数组成
// 函数组成形式
必须有 function 函数名 ()括号 {}花括号
d.函数的参数(形参和实参)
// 形参或者实参(可有可无), 真正的编程,有参数才更有意义
// 小栗子
function test(a, b ) { // 这里的a b是形参
console.log(a + b);
}
test(1,2) // 这里的1 2 是实参
输出为3
注意: js参数最强大的有点就是不限制位数,实参和形参位数可以不对等,实参可以比形参多,反之也可以
// 形参比实参多
// 小栗子
function sum(a, b, c) {
console.log(a + b + c)
}
sum(1, 2)
输出是NaN 因为undefined 在Number转换的时候变成了NaN
以上栗子中形参是a b c, 实参是 1 2
//实参比形参多
function sum(a, b) {
console.log(a + b);
}
sum(1, 2, 3);
输出为3
以上栗子中a b是形参, 1 2 3是实参
// 如何读取形参的长度呢
// 只需要 函数名.length即可获取
// 如何读取实参的长度呢
// 只需要 arguments.length
下面有一个小测试: js实现任意数求和,任意数的意思是不规定你传入几个数字
function sum() {
var result = 0;
var len = arguments.length;
for(let i =0; i < len; i++) {
result += arguments[i]
}
console.log(result);
}
sum(1, 5, 6, 7);
输出为 19
// 测试 arguments[0] 和第一个实参是绑定关系吗
function sum(a, b) {
arguments[0] = 1;
console.log(a);
}
sum(2,3);
输出为 1
// 看来是绑定关系
// 有个特殊情况
function sum(a, b) {
arguments[1] = 2;
console.log(b);
}
sum(1);
输出结果是 undefined
// 此时发现,只有形参和实参有对应关系的时候,他们才有映射关系
e.返回值
return 必须写在函数里,它有两个作用,第一个作用是返回一个值,第二个作用是终止函数
终止函数,如果没有写在function,系统会自动在函数的最后加一个return undefined;
f.作用域
var a = 123; // 此时的a是全局变量
fucntion test() {
var b = 345; // 此时的b是局部变量
}
备注: 局部变量可以访问全局变量,全局变量不可以访问局部变量(里面可以访问外面的,外面的不能访问里面的)
14.递归
- 找规律
- 找出口,找已知条件为出口
好处: 代码简洁 但是效率不高
// 小栗子
// 写n!(递归) 已知1! === 1
// 分析 n! 为 n * (n - 1) * (n -2) * 一直乘到 1
var n = window.prompt("input");
function mul(n) {
if(n === 1) {
return 1;
}
return n * mul(n-1);
}
mul(n);
// 小栗子
// 写一个函数 实现斐波那契数列
// 找规律 fb(n) = fb(n - 1) + fb(n - 2)
function fb(n) {
if(n == 1 || n == 2) {
return 1;
}
return fb(n - 1) + fb(n - 2);
}
fb(3);
// 输出: 3
15.JS运行三部曲
- 第一步::语法分析
系统会通篇扫描一遍,看程序有没有低级错误(少些")" "}"),如果发现有低级错误,系统将停止执行
- 第二步:预编译
函数声明整体提升,变量 声明提升
imply global 暗示全局变量:任何变量,如果未经声明就赋值,此变量就归全局对象所有。
// 小栗子
a = 10;
a 因为没有声明就进行赋值,相当于
window.a = 10;
// 又一个小栗子
function test () {
var a = b = 123;
}
test();
console.log(b);
console.log(a);
输出 123 a is not defined
- 第三步:解释执行,一行代码一行代码地执行
16. 预编译(函数的预编译)
函数预编译发生在函数将要执行的前一刻
1. 首先创建AO(Activation Object)
2.找形参和变量声明,将变量和形参名作为AO属性名,值为undefined
3.将实参和形参相统一
4.在函数体里面找函数声明,将函数声明的函数名作为AO的另一个属性名,函数声明的函数体作为该属性名的属性值
// 小栗子
function fn(a) {
console.log(a);
var a = 123;
console.log(a);
function a() {}
console.log(a);
console.log(b);
var b = function() {};
console.log(b);
function d() {}
console.log(d)
}
fn(1);
输出 function(){} 123 123 undefined function() {} function d(){}
17. 预编译(全局的预编译)
1. 生成GO对象(Global Object)
2. 变量声明的变量名作为GO的属性名,值赋予undefined
3.找函数声明,值赋予函数体
// 小栗子
console.log(test);
function test(test) {
console.log(test);
var test = 123;
console.log(test);
function test() {}
}
test(1)
// 输出 function test(test){} fucntion test() {} 123
// 小栗子
var global = 100;
function fn() {
console.log(global);
}
fn();
首先 GO {
global: undefined ---> 100,
fn: fucntion fn() {...}
}
在代码执行到 fn() 前一刻
AO{
啥也没有
}
在console.log(global)时,由于AO里面啥也没有,它会向GO里面找 此时的global为100 输出 100
// 稍微有点难的哦
global = 100;
function fn() {
console.log(global); 此时找到fn函数内有变量声明 global undefined
global = 200;
console.log(global);
var global = 300;
}
fn();
var global;
输出 undefined 200 因为AO中有global
18.作用域精解
-
[[scope]] (对象隐式的属性)
每个JavaScript函数都是一个对象,对象的某些属性我们可以访问,有些不可以,这些属性仅供JavaScript引擎存取,[[scope]]就是其中的一个,[[scope]]指的就是我们所说的作用域,其中存储了运行期上下文的集合 (执行期上下文,即预编译,函数预编译创建AO{} --> 函数执行完之后销毁AO{} ---> 当再次执行前函数预编译 ---> 创建AO{} ---> 用完就销毁了)
-
作用域链
[[scope]] 中所存储的执行期上下文的集合,这种集合呈链式连接,我们把这种叫做作用域链
-
查找变量
作用域链变量的查找是从作用域的顶端依次向下查找,在哪个函数中找一个变量就去哪个函数的作用域链里查找
// 函数小栗子分析作用域链
function a() {
function b() {
var bb = 234;
a = 0;
}
var aa = 123;
b();
console.log(aa);
}
var glob = 100;
a();
当代码被执行的时候,创建GO对象,并将GO放到[[scope]]作用域顶端, 执行全局预编译,将glob变量和函数a提升到GO中,当执行到a()的前一刻,a函数执行函数预编译,创建AO对象,AO放到[[scope]]顶端,GO在AO下面,此时aa变量和b函数提升,放到AO对象中,在执行b()函数的前一刻,b函数执行函数预编译,创建一个新的AO对象,bb变量提升,a变量放到最开始的GO对象中,此时的AO放到最前面,下面依次是a函数的AO和全局的GO对象,如下图
a函数执行的时候:
b函数执行的时候:
总结:
当b函数执行完的时候,b函数回到定义的状态,销毁自己的AO(只能销毁自己的AO),剪短第三张图中0与AO的线,当b函数再次被执行的时候,会重新产生一个AO放到[[scope]]顶部,当b执行完,a函数内部代码被执行完,a函数剪掉第二张图0与AO的线,a会回到定义的状态。
如果a不执行,b就不会被定义。
19.闭包
-
现象
当内容函数被保存到外部时,将会产生闭包,导致原油作用域链不释放,造成内存泄露。
-
闭包的作用 实现公有变量
// 实现一个累加器 使用闭包
function add() {
var count = 0;
function demo() {
count++;
console.log(count);
}
return demo();
}
var counter = add();
counter(); // 输出1
counter(); // 输出2
-
可以做缓存
// 使用闭包做一个缓存
function test() {
var num = 100;
function a() {
num++;
console.log(num);
}
function b() {
num--;
console.log(num);
}
return [a, b];
}
var myArr = test();
myArr[0](); // 输出101
myArr[1](); // 输出100
备注:在一个函数中,有多个子函数的话,这几个子函数公共指向同一个复函数产生的作用域链,即父函数的
test define test [[scope]] 0 GO
test doing test [[scope]] 0 AO 1 GO
20.立即执行函数
-
定义:凡是写的函数只想被执行一次,执行完一次后被销毁的函数叫做初始化功能函数,即立即执行函数
// 立即执行函数不传参格式
(function() {
var a = 123;
var b = 234;
console.log(a, b);
}())
// 输出123 234
// 传参格式
(function(a, b, c) {
console.log(a, b, c);
}(1, 2, 3))
// 輸出1 2 3
-
两种格式:
-
(function() {}()) ()在里面 ---> 建议这种(w3c标准)
-
(function() {})() ()在外面
注意:只有表达式才能被执行
// 小栗子
fucntion test() {} () ---> 不会被执行,因为test是函数声明不是函数表达式
123; 234; 123, 234 ----> 可以叫做表达式
var test = function() {console.log('a')} () ----> 能执行 输出a
被执行函数执行的表达式就成了立即执行函数,并忽略函数名引用
// 小栗子
var test = function() { console.log('a') };
执行 test() test --> 输出为function
当写成 var test = fucntion() {console.log('a')} ()
执行完之后 test ---> undefined
-
立即执行函数的几个题
// 给ui下的四个li绑定一个事件,输出每个li的位置
function test() {
var liCollection = document.getElementsByTagName('li');
var len = liCollection.length;
for(var i = 0; i < len; i ++) {
(function(j) {
liCollection[j].onclick = function() {
console.log(j + 1)
}
}(i))
}
}
// 如果没有立即执行函数,每个li输出的i都是li标签的数量,因为当li被点击的时候,li函数的GO作用域中的i已经变成了len
// 求一个字符串的字节长度(提示:字节串有一个方法,charCodeAt();一个中文占两个字节,一个英文占一个字节) charCodeAt返回指定位置的字符串的Unicode编码,这个返回值是0~65535之间的整数。(当返回值<= 255时为英文,>时就是中文)
function retByteSlean(target) {
if(typeof(target) !== "string") {
return;
}
var len = target.length,
count = len;
for(var i = 0; i < len; i++) {
if(target[i].charCodeAt() > 255) {
count++;
}
}
return count;
}
21.逗号运算符
-
原理:
逗号表达式会先计算表达式前面的值,再计算后面的值,最后打印后面的值。
// 小栗子
var a = (2 + 1, 2 - 1);
输出1
22.对象
- 定义:生活中的任何东西都可以抽象为对对象,对象有属性和方法,我们可以实现对对象的增删改查
// 对对象的增删改查
var obj = {name: 'zl', age: 18};
增 对象 + . + 属性名 + : + 属性值 --> obj.lastName = 'lei'
删 delete + 对象.属性名 ---> delete obj.name
改 obj.原属性名 = 需要改的属性值 ---> obj.age = 17
查 obj.属性名, 原对象没有该属性名,返回undefined ---> obj.name 输出 zl obj.height 输出 undefined
- 对象创建的几种方式
// 第一种 对象字面量/对象直接量
var obj = {...}
// 第二种 (通过构造函数构造对象)
1. 系统自带Object构造函数,通过Object来创建对象,Object系统相当于一个工厂,它可以批量地生产对象,生产出来的对象一毛一样,但是每个对象都相互独立,系统自带其他构造函数,如Array、Number等
当new Object(),系统会给你一个真正的对象,js中的对象相对于Java和C++的对象更加灵活,js中的对象更像人的出生,系统自带Object的使用
var obj = new Object();
obj.name = 'zl';
2.自定义的构造函数
function Person() {}
因为构造函数跟普通函数看起来没什么区别,所以构造函数在命名时要遵循大驼峰规则(单反是单词首字母都需要大写)这样来区别
|
—— 小栗子
function Car(color) {
this.height = 1400;
this.weight = 1000;
this.lang = 4900;
this.color = color;
}
var car1 = new Car('red');
var car2 = new Car('green');
|
_ 解析
Car 相当于一个车间,给同一个车型车刻了一个模子,color是可选的同款车型的不同颜色,height、weight、lang是该款车型不变的配置,Car生产出来的车相互独立,互不影响(互不影响就可以用来做原型链的继承)
- 构造函数内部原理
1. 在函数体最前面会有一个隐式的this对象
function Student() {
// var this = {};
}
var this = {} 相当于在AO中加入this属性名和{}属性值 AO{this: {}}
刚开始的时候这个this指向window,当new 该函数的时候,this指向__proto__
2.执行this.xxx = xxx
3.隐式返回this ==> 这一步是new之后返回的
function Student(name, age) {
this.name = name;
this.age = age;
// new之后 代码执行到这里相当于 AO{this: {name: name, age: age}}
// 最后隐式返回this 如果你强制return {} 会替换return的this, 如果你return的是原始值,
// 不会影响this对象的返回
}
// 如果不执行new,就是普通的 Student(1,2) 这里的this指向window,这里的name和age是给window上赋值,执行window.name window.age 分贝输出1 和 2
23.包装类(原始值有对象方式的创建方式)
在JavaScript中数字有两种个数字,字符串分两种字符串,布尔值分为两种布尔值,原始值数字才是原始值,原始值字符串才是原始值,这里还有非原始值数字和非原始值字符串,new Number(xxx) 默认是Number {0} 、new String(xxx) String {""}、 new Boolean() 默认是Boolean {false}
注意:undefined和null没有包装类
// 包装类有自己的属性和方法
var num = new Number(123);
num.abc = "ac";
console.log(num.abc); // 输出 “ac”
num.fn = function() { console.log(123) };
num.fn() // 输出 123
// 但是原始值本身没有属性和方法,也不能加属性和方法,也不能访问属性和方法
var num = 3;
num.length = 4;// 原始值num是没有length属性的,它会转换为new Number(3).length = 4,执行完这段代码之后Number销毁
console.log(num.length); // 此时又重新转换为new Number(3),此时输出length为undefined。
- 包装类面试题
// 面试题
var str = "abc";
str += 1;
var test = typeof(str);
if(test.length === 6) {
test.sign = "typeof返回的值可能是string"; // 转换为包装类之后销毁
}
console.log(test.sign) // 再次转换为包装类 因为new String("test")没有sign这个属性,输出为undefined 原始值没有方法和属性
// 输出为undefined
24.原型
定义:原型是function对象的一个属性,它定了构造函数制造出来的对象的公共祖先,通过构造函数生产的对象可以继承该原型的属性和方法,原型也是对象。
- 原型的特点:
我们创建出来的对象,都会继承了父级的prototype,他不仅有自己定义的属性,还有“他爹”(原型上)的属性,当我们构造出来的对象有公共的不改变的属性,我们可以把这些不变的属性到原型上面
// 小栗子
function Car(color, ower) {
this.color = color;
this.ower = ower;
this.height = 1400;
this.weight = 4900;
}
var car = new Car("red", "lei.zhou");
// 每次new Car()都会走一遍 this.height this.weight, 而这两个属性是不变的,可以直接放到Car的原型上面
Car.prototype = { height: 1400, weight: 4900}
- 原型的增删改查(构造函数的原型本职上是对象,它的增删改查就是对象的增删改查)
// 小栗子
Person.prototype.lastName = "lei";
function Person (name) {
this.name = name;
}
var person = new Person("张");
// 原型的增
Person.prototype.firstName= "zhou";
// 原型的删
delete Person.prototype.firstName; 返回true
// 原型的改
Person.prototype.lastName = "leizi";
// 原型的查
Person.prototype.firstName; 删除 "zhou"
- 原型的写法
// 原型的两种写法
第一种 XX.prototype.xx = xx
Person.prototype.name = 'buhaoba';
第二种 XX.prototype = {xx: xx}
Person.prototype = { name: "buhaoba"};
- 原型的constructor(构造器)
// 查看对象的构造函数
function Car() {}
var car = new Car();
console.log(car.constructor); // 输出为 function Car(){}
// car.constructor有时候也会找错生产自己的工厂
function Person() {}
car.prototype = {
constructor: Person
}
function Car() {}
var car = new Car();
console.log(car.constructor); // 输出为Person
- 原型的__proto__
Person.prototype.lastName = "abc";
function Person() {}
var person = new Person();
console.log(person);
从上图可以知道输出person.name时会沿着__proto__找到Person.prototype,__proto__跟Person.prototype指向同一个地址,他两都是引用值,在原型中找对应的属性,Person.prototype属性有lastName和constructor,constructor这个指向function Person() {},Person.prototype也有自己的__proto__,这个__proto__指向Object.prototype
- 修改原型对构造出来对象的影响
Person.prototype.name = "sunny";
function Person() {}
var person1 = new Person();
Person.prototype = {name: 'abc'};
console.log(person1.name); // 输出为"sunny"
var person2 = new Person();
console.log(person2.name); // 输出为"abc"
Person.prototype.name = "sunny";
function Person() {}
var person = new Person();
Person.prototype.name = 'abc';
console.log(person.name); // 输出为"abc"
为啥会出现以上这种情况呢,原因竟然是:
第一种:打印person1.name 沿着__proto__找Person.prototype,此时的Person.prototype的指向为stack0001,这里面的name为“sunny”,所以输出“sunny”;打印person2.name 沿着__proto__找Person.prototype,Person.prototype重新赋值一个对象,指向发生了改变,此时的Person.prototype的指向为stack0002,这里面的name为“abc”,所以输出“abc”。
第二种:打印person.name 沿着__proto__找Person.prototype,此时的Person.prototype的指向为stack0001,这里面的name从“sunny”变成了“abc”,指向还是之前的 stack0001,只是内容改变了,所以输出“abc”。
25.原型链
- 如何构成原型链呢?
Grand.prototype.lastName = "lei";
function Grand() {
this.age = 1000;
}
var grand = new Grand();
Father.prototype = grand;
function Father() {
this.height = 123;
}
var father = new Father();
Son.prototype = father;
function Son() {
this.weight = "henqin";
}
var son = new Son();
console.log(son);
- 原型链属性的增删改查
// 原型链的增
构造函数名.prototype.xx = xx;
Father.prototype.happyState = true;
console.log(son.happyState); 输出为true
// 原型链的删
delete 构造函数名.prototype.xx
delete Father.prototype.happyState;
console.log(son.happyState); 输出为undefined
// 原型链的改 (改值的时候不要直接等于一个引用值,这样会让原型链断裂)
构造函数名.prototype.原有属性名= xx;
Father.prototype.age = 15;
console.log(son.age ); 输出为15
// 原型链的查
从儿子级一直到祖先查,如果祖先有和自己一样的属性,输出自己的
- 绝大多数对象最终都会继承自Object.prototype
// 小栗子
function Person() {}
var person = new Person;
person.toString(); 输出 “[object Object]”
为啥会输出呢,是因为Person的__proto__指向Object.prototyepe,Object.prototyepe里有toString这个属性
- Object.create(原型) 构造对象 绝大多数对象最终都会继承自Object.prototype,为什么说绝大多数呢?看以下内容就可明白
var obj = Object.create("原型 可以自己指定");
var prop = {
name: 123
}
var obj1 = Object.create(prop);
console.log(obj1);
如果create里面传值为null时,他就不继承自Object.prototype
var obj = Object.create("原型 可以自己指定");
var prop = null;
var obj1 = Object.create(prop);
console.log(obj1);
- 重写系统定义函数定义的原型上的方法
var num = 123;
num.toString(); 输出为“123”
为啥会输出字符串123呢,因为Number.prototype原型上面有 toString = function() {}
Number.prototype中没有的属性才会找Object.prototype上的属性
// 包装类(Number String Boolean) 和 Array Object都有自己的toString方法
调用的方法不同,输出的结果也不同
Object.prototype.toString.call(123) 输出 "[object Number]"
26.改变this指向 (call 和 apply)
function Person(name, age) {
this.name = name;
this.age = age;
}
var person = new Person("zhoulei", 18);
var obj = {};
Person(1, 2); 相当于 Person.call(null, 1, 2);
call 和 apply进行改变this指向的芳芳详解
// call 改变this指向 第一个参数是this,this需要指向的函数 之后的参数是需要进行赋值的参数,用‘,’分隔传递
function a(name, age) {
console.log(123);
this.name = name;
this.age = age;
}
function b(name, age, sex) {
a.call(this, name, age);
this.sex = sex;
}
var item = b(1,2,3);
// apply改变this指向 第一个参数是this,this需要指向的函数 之后的参数是需要进行赋值的参数,需要传递一个数组
function a(name, age) {
console.log(123);
this.name = name;
this.age = age;
}
function b(name, age, sex) {
a.call(this, name, age);
this.sex = sex;
}
var item = b(1,2,3);
27.继承发展史
- 使用原型链的方式
缺点:继承了太多的没必要的属性。
- 借用构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
function Student(name, age, sex) {
Person.call(this, name, age);
this.age = age;
}
var st1 = new Student(1, 2, 3);
缺点:必须是new出来才有意义,因为不new的话,this指向window;因为是借用,①不能继承借用构造函数的原型 ②每次new构造函数时,都要多走一个函数。
- 共享原型
// 共享原型其实就是两个构造函数的原型相互等于
Father.prototype.name = "zl";
function Father() {}
Son.prototype = Father.prototype;
function Son() {}
//上面代码换一种写法
function inherit(origin, target) {
target.prototype = origin.prototype
}
缺点:其中一方原型属性的改变会影响另一方的原型
- 圣杯模式的原型继承
// 既然共享原型方法,其中一方原型属性的改变会影响另一方的原型这样不好,我们在中间过渡加一个函数即可
function inherit(origin, target) {
function F(){}
F.prototype = origin.prototype;
target.prototype = new F();
target.prototype.constructor = target;
target.prototype.uber = origin.prototype;
}
28.属性方法的调用
- 对象属性调用的两种方式
// 小栗子
var obj = {
name: "baicai";
}
// 两种访问方式
① obj.name ==> 隐式访问 ② obj["name"];
- 属性拼接式访问
var dengGe= {
wife1: {
name: "xiaoliu"
},
wife2: {
name: "xiaozhou"
},
wife3: {
name: "xiaowang"
},
sayWife(num) {
return this['wife' + num]
}
}
console.log(dengGe.sayWife(1)); 输出 xiaoliu
// 事件拼接使用的时候更重要
29.对象的枚举(遍历)
- for in
var obj = {
name: "123",
sex: "male",
height: 180
}
for(var prop in obj) {
console.log(typeof prop);
console.log(obj[prop]);
}
// prop是对象的每个属性名,这个属性名是字符串类型的
- 对象的hasOwnProperty() --> 用来过滤遍历对象的原型上的属性,输出自身内部的属性
// 在obj上加上自己的__proto__属性,直接for in 循环打印
var obj = {
__proto__: {lastName: 'deng'},
name: "123",
sex: "male",
height: 180
}
for(var prop in obj) {
console.log(typeof prop);
console.log(obj[prop]);
}
这里我们可以看出for in 会打印出对象原型上的属性,lastName为deng
这时候我们使用hasOwnProperty来过滤原型上的属性,只输出对象内部的属性,我是这样使用的
// hasOwnProperty使用
var obj = {
__proto__: {lastName: 'deng'},
name: "123",
sex: "male",
height: 180
}
for(var prop in obj) {
console.log(typeof prop);
obj.hasOwnProperty(prop) && (console.log(obj[prop]));
}
- 对象的 in
// in 只能判断prop这个属性是不是属于这个对象,如果是属于返回true,否则返回false
var obj = {
__proto__: {lastName: 'deng'},
name: "123",
sex: "male",
height: 180
}
// 使用
"lastName" in obj 输出 true
总结:无论是原型上的属性还是自己的属性,都会返回true
- instanceof --> A instanceof B 判断A对象的原型链上有没有B的原型
// 小栗子
[] instanceof Array 输出true [].__proto 为Array.prototype Array.prototype.__proto__为Object
{} instanceof Array 输出false {}.__proto 为Object.prototype
有趣的问题来了,如何判断是 "[]" 还是 "{}",下面提供几个方法
// 方法一
[] instanceof Array 输出true
{} instanceof Array 输出false
// 方法二
[].constructor === Array 输出true
var obj = {};
obj.constructor === Object 输出true
// 方法三
Object.prototype.toString.call([]); 输出 "[object Array]"
var obj = {};
Object.prototype.toString.call(obj); 输出 "[object Object]"
30.this指向
- 普通函数预编译this指向window
function test() {c} {
var a = 3;
function b() {}
}
test(1);
// 函数预编译
AO{
arguments: [1],
this: window,
c: 1, // undefined --> 1
b: function b(){}
}
- 构造函数预编译指向构造函数原型
function Test(name) {
this.name = name;
}
这里的this相当于 var this = Object.create(Test.prototype); 或者等于下面的
{
__proto__: Test.prototype
}
- 全局作用域里面的this指向window
- call,apply会改变this指向,this指向第一个参数
- 对象.函数,this指向该对象(该对象中的函数不能使用箭头函数,箭头函数中的this指向父级)
var obj = { call: () => {console.log(this)}}
obj.call();
// 输出window对象
var obj = { call:function() {console.log(this)}}
obj.call();
// 输出obj
31.arguments(只有callee和length两个属性)
- arguments.callee,它的值为函数引用,用处:立即执行函数执行完之后找不到自己的引用,可以使用该属性找到自己引用
var num = (function (n) {
if(n === 1) {
return 1;
}
return n * arguments.callee(n-1)
}(10));
console.log(num);
// 输出 3628800 即10!
- func.caller(caller是函数的属性,该函数在哪被调用,func.caller就指向它的引用)
function test() {
demo();
}
function demo() {
console.log(demo.caller);
}
demo();
// 输出 function test() { demo(); }
- arguments.length,输出实参长度,用处:不定参传参,实现实参数据的操作
32.对象的克隆(浅层克隆和深层克隆)
- 浅层克隆
var obj = {
name: "abc",
age: 123,
have: [1, 2, 3]
}
var obj1 = {};
// 定义clone函数
function clone(origin, target) {
var target = target || {};
for(var prop in origin) {
target[prop] = origin[prop];
}
return target;
}
clone(obj, obj1);
注意:使用for in遍历对象赋值,只会赋值表层对象属性,如果属性值是引用值,克隆出来的对象跟之前的对象引用属性值指向同一个地址,只要不是重新开辟地址,而是简单的新增、修改,两个对象相互影响。
- 深层克隆,克隆出来的对象跟之前对象无关,属性值不关联
// 遍历对象、数组也是特殊的对象
// 1. 判断是不是原始值
// 2. 判断是数组还是对象
// 3.建立相应的对象或是数组
// 4.递归
function deepClone(origin, target = {}) {
console.log(target);
var toStr = Object.prototype.toString,
arrStr = "[object Array]";
for (const prop in origin) {
if (origin.hasOwnProperty(prop)) {
if(origin[prop] !== null && typeof(origin[prop]) == "object") {
target[prop] =toStr.call(origin[prop]) == arrStr ? [] : {};
deepClone(origin[prop], target[prop]);
} else {
target[prop] = origin[prop];
}
}
}
return target;
}
let a = {name: 123, age: 345, age1: [1,2,3]}, b;
console.log(deepClone(a, b));
// 输出为 {name: 123, age: 345, age1: [1,2,3]}
32.数组
- 定义:
数组的方法都来自于Array.prototype,给Array()传一个参数,它会把一个参数当做长度而不是只,这个参数要求是整数
// 小栗子
var arr = new Array(10);
console.log(arr);
// 输出的是10个值为undefined的数组
- 数组的读和写
// 数组不可以溢出读,溢出部分是undefined
var arr = [1, 2, 3];
console.log(arr[3]); // 输出undefined
// 数组可以溢出写
var arr = [1, 2, 3];
arr[3] = 4;
console.log(arr); // 输出 [1, 2, 3, 4]
33.数组中常用的方法之改变元素组的方法
- push pop shift unshift reserver splice sort
// push pop 新增 / 删除数组末尾元素
var arr = [1, 2];
arr.push(3); // 输出3 是数组的长度
console.log(arr); // 输出[1, 2, 3]
arr.pop(); //输出3 是删除的元素
console.log(arr); // 输出[1, 2]
// shift unshift 删除 / 新增数组头部元素
arr.unshift(3); // 输出3 是数组的长度
console.log(arr); // 输出[3, 1, 2]
arr.shift(); // 输出3 是删除的元素
console.log(arr); // 输出[1, 2]
// reverse 数组反转
arr.reverse(); // 输出[2, 1]
console.log(arr); // 输出[2, 1]
// sort 函数返回a-b,增序
arr.sort( (a, b) => a - b); // 输出[1, 2]
console.log(arr); // 输出[1, 2]
// splice(index, count, prop1, prop2...) 第一个参数是从数组下标第几项开始,第二个参数是需要截取的长度,之后的参数是该位置新增的元素
arr.splice(0, 2, 9, 8); // 输出[1, 2]
console.log(arr); // 输出 [9, 8]
34.数组小例题
- 给一个有序的数组乱序
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.sort( _ => Math.random() - 0.5);
console.log(arr); // 输出[7, 1, 9, 6, 4, 8, 5, 2, 3]
- 三个对象为一个数组,在数组中按年龄排序
var lei = {
name: "lei",
age: 19
}
var jun = {
name: "jun ",
age: 18
}
var bai = {
name: "bai",
age: 17
}
var arr = [lei, jun, bai];
arr.sort( (a, b) => a.age - b.age);
console.log(arr); // 输出 [{name: "bai", age: 17}, {name: "jun ", age: 18}, {name: "lei", age: 19}]
- 给一个装有不等长字符串的数组拍升序
var arr = ['89898889', '888', '5555555', '88', '8', '2222222222'];
arr.sort( (a, b) => a.length - b.length);
console.log(arr); // 输出["8", "88", "888", "5555555", "89898889", "2222222222"]
- 如果字符串中有中文,用字节数比较,按字节数递增方式排序
var arr = ["是真的", "真的真的", "wo", "aii", "you"];
function retBytes(str) {
var num = len = str.length;
for(var i = 0; i < len; i++) {
if(str.charCodeAt(i) > 255) {
num++;
}
}
return num;
}
arr.sort( (a,b) => retBytes(a) - retBytes(b));
// 输出["wo", "aii", "you", "是真的", "真的真的"]
35.数组方法之不改变原数组
- concat toString slice join split
// concat 用来连接两个数组
var arr1 = [1, 2, 3];
var arr2 = [4, 5, 6];
arr1.concat(arr2); // 输出[1, 2, 3, 4, 5, 6]
console.log(arr1); // 输出[1, 2, 3]
console.log(arr2); // 输出[4, 5, 6]
// toString 将数组转换为字符串
arr1.toString(); // 输出"1,2,3"
// slice(first, end) first是截取数组的第一个下标,end是截取到数组的第几位,包括end
arr1.slice(1, 2); // 输出[2]
// split() 将字符串转换为数组
var str = "name";
str.split(""); // 输出["n", "a", "m", "e"]
str.split("n"); // 输出["", "ame"]
// join() 将数组转化为字符串
arr1.join(""); // 输出"123"
arr1.join(""); // 输出"1-2-3"
36.类数组
- 定义:类数组长得像数组,是对象,可以手动增加数组的方法,是对象,可以当数组来用。
// 属性要为索引属性,必须有length,最好加上push
var obj = {
"0": "a",
"1": "b",
"2": "c",
"length": 3,
"push": Array.prototype.push
}
输出obj
- 小例题
var obj = {
"1": "b",
"2": "c",
"length": 2,
"push": Array.prototype.push
}
obj.push("c");
obj.push("d");
console.log(obj);
为啥会出现以下这种情况呢:这就要聊到Array。prototype.push方法原理了
Array.prototype.push = function(target) {
this[this.length] = target;
return ++this.length;
}
37.测试题
- 封装一个type方法,用于区分各个数据的类型
var template = {
"[object Number]": "number",
"[object String]": "string",
"[object Boolean]": "boolean",
"[object Undefined]": "undefined",
"[object Null]": "null",
"[object Array]": "array",
"[object Object]": "Object",
"[object Function]": "function"
}
function retDataType(data) {
let str = Object.prototype.toString;
return template[str.call(data)];
}
retDataType(123);
测试retDataType返回值
- 数组去重
var arr = [1, 1, 2, 3, 4, 5, 6, 5, 7, 7, 7];
Array.prototype.unique = function() {
var temp = {}, _arr = [], len = this.length;
for(var i = 0; i < len; i++) {
if(!temp[this[i]]) {
temp[this[i]] = 5;
} else {
_arr.push(this[i]);
}
}
return _arr;
}
arr.unique(); // 输出[1, 5, 7, 7]
38.try{} catch(error) {} finally捕获异常
有的时候一个代码我确实是没有把握的,而且我们坑定代码在这个条件下报错,在另外一个条件下不报错,但必须这么写,这个时候要保证在任何情况下就能用try catch
语法:
在try中发生错误,不会执行错误后的try里面的代码,会执行catch中的代码,之后再执行程序外的其他代码
try里面没有发生错误,则正常执行,不执行catch中的代码
try{
console.log("a");
console.log(b);
console.log("c");
} catch(err) {
console.log(err);
console.log(err.name);
console.log("d");
} finally {
console.log("e");
}
console.log("f");
输出:
- catch中的六种err.name
// EvalError:
eval() 的使用和定义不一致
// RangeError
数组越界
// ReferenceError: 非法或不能识别的引用数组 --> 没定义就使用 ※
console.log(a); / b(); / var str = name; --> a b name 未定义使用会报错
// SyntaxError: 发生语法解析错误 ※
低级错误 代码中出现中文符号 :
// TypeError 操作数类型错误
// URIError: URI函数使用不当
SyntaxError 和 ReferenceError最重要
39.es5.0 严格模式
浏览器执行以es3.0(标准)和es5.0(通用)的新增方法,使用的是es3.0(如果冲突的话),今天讲的是es3.0和es5.0冲突的部分,es3.0和es5.0产生冲突的部分用es5.5,否则就用3.0
- 定义:
不再兼容es3.0的一些不规则语法,使用全新的es5.0规范
- 启用严格模式
// 在页面顶端写 "use strict",是全局的严格模式,写在函数的最顶端,是局部严格模式,
//这种局部的严格模式不会产生对其他代码块产生影响
function demo() {
console.log(arguments.callee);
}
function test(){
"use strict";
console.log(arguments.callee);
}
demo();
test();
- 为什么设计师要用字符串来启用严格模式,而不使用函数呢?
因为123 "abc" 在浏览器中是不会报错的,向后兼容
而函数执行的时候会影响后面的代码
"use strict"; 就是一行字符串,不会对不兼容严格模式的浏览器产生影响
- 语法:
不支持arguments.callee, with, func.caller,变量赋值前必须声明,局部this必须被赋值,
func.call(null/undefined);赋值什么就是什么
变量赋值前必须声明
局部this必须被赋值,否则this为undefined,全局的this还是指向window
func.call(null/undefined);赋值什么就是什么
40.DOM
DOM定义了表示和修改文档所需的方法,DOM对象即为宿主对象,由浏览器厂商定义,用来操作html和xml功能一类对象的集合,也有人称DOM是html和xml的标准编程集合。
xml ---> xhml ---> html(4.0, 5.0)
xml现在没咋用,可自定义标签,现在用html
xml以前用于数据传输,现在用json
- DOM基本操作
我们在写html网页的时候最外面一层是html,其实本质上html有一个父级,#document,document代表整个文档
下面正式介绍关于标签元素节点的操作:
- 查找元素节点的方法
// 使用id查询行内属性有id的标签元素
document.getElementById("xx");
// 使用标签名查找标签元素,查找出来的是该标签的一个数组,操作一个的时候需要[i] 最常用哦
document.getElementsByTagName("div");
document.getElementsByTagName("div")[0]; // 表示第一个是div标签的元素
// 使用class名来查找行内元素有class属性的标签元素,查找出来的是该标签的一个数组,操作一个的时候需要[i] 比较常用哦
document.getElementsByClassName("red");
document.getElementsByClassName("red")[0]; // 表示第一个class含有red的标签元素
// 使用name查找标签行内有name属性的元素,name表示的是提交数据的数据名,(只有部分标签含
// 有的name属性使用该方法有效,表单、表单元素、img、iframe) 不常用
document.getElementsByName();
// querySelector() 选中一个元素,里面是能代表一个标签元素的各种属性 ie7及以下没有该方法
var strong = document.querySelector("div > span.demo");
选中的是div下面有class属性的span元素
<div>
<span>
<strong class="demo"> 123 </strong>
</span>
</div>
// querySelectorAll()选中的是一组该属性的元素, ie7及以下没有该方法
注意:querySelector() 和querySelectorAll()选出来的元素不是实时的,选择出来的是副本,是静态的(这个就很像我们看的电视直播,是现场直播的副本)
- 遍历元素节点树(第一种:所有浏览器都兼容的)
// 父节点 查找元素的父节点,需要注意的是祖先节点是#document,#document就没有父元素了
元素.parentNode
// 子节点们 childNodes 选的是节点树
<div>
123
<!-- This a comment -->
<strong></strong>
<span></span>
</div>
document.getElementsByTagName("div")[0].childNodes.length // 输出[text, comment, text, strong, text, span, text]
// 第一个子节点
元素.firstChild
// 最后一个子节点
元素.lastChild
// 后一个兄弟节点
元素.nextSibling
//前一个兄弟节点
元素.previousSibling
- 遍历元素节点树(第二种:IE9以下不兼容【除了以下介绍的children】)
// 返回当前元素的父元素节点 parentElement
parentElement 跟parentNode的区别是,#document不是元素节点,所以html没有parentElement,而html有
parentNode
// 返回当前元素子节点 children
<div>
123
<!-- This a comment -->
<strong></strong>
<span></span>
</div>
document.getElementsByTagName("div")[0].children.length // 输出[strong, span] 2
// 返回第一个元素子节点
元素.firstElementChild
// 返回最后一个元素子节点
元素.lastElementChild
// 返回后一个元素兄弟节点
元素.nextElementSibling
// 返回前一个元素兄弟节点
元素.previousElementSibling
- 节点的四个属性
// 第一个属性 node.nodeName 只读
document.getElementsByTagName("div")[0].nodeName 输出 "DIV"
// 第二个属性 node.nodeValue 可读写
document.getElementsByTagName("div")[0].nodeValue 输出null
只有文本节点和注释节点有value
// 第三个属性 node.nodeType 每个节点都有该属性 只读
document.getElementsByTagName("div")[0].nodeType 输出1
Text(3) 元素节点(1) 注释节点(8) 属性节点(2)[存在 没啥用]
// 第四个属性 该节点的属性集合 node.attributes
document.getElementsByTagName("div")[0].attributes
输出 {0: class, 1: name, 2: age, class: class, name: name, age: age, length: 3}
// 第五个属性 node.hasChildNodes()
document.getElementsByTagName("div")[0].hasChildNodes() 输出true
返回的有两个值 true和false
41.DOM结构树
42.函数节点结构树例题
- 遍历元素节点树(在原型链上编程)
<div>
<span>1</span>
<strong>2</strong>
<em>
<strong>3</strong>
</em>
</div>
- 封装一个函数,返回元素e的第n层祖先元素节点
function retParentElm(elm, n) {
while(elm && n) {
elem = elem.parentElement;
n--;
}
return elem;
}
Object.prototype.retParentElm = retParentElm;
- 封装函数,返回元素e的n个元素节点,n为正,返回后面的兄弟元素节点,n为负,返回前面的,n为0,返回自己
function retSibling(e, n) {
while(e && n) {
if(n > 0) {
e = e.nextElementSibling;
n --;
} else {
e = e.previousElementSibling;
n ++;
}
}
return e;
}
- 编辑函数,封装myChildren功能,解决以前部分浏览器的兼容性问题
Element.prototype.myChildren = function() {
let child = this.childNodes;
let len = child.length;
let arr = [];
for (let i = 0; i < len; i++) {
if (child[i].nodeType === 1) {
arr.push(child[i]);
}
}
return arr;
};
- 封装hasChildren(),不可用children
Element.prototype.myChildren = function() {
let child = this.childNodes;
let len = child.length;
let arr = [];
for (let i = 0; i < len; i++) {
if (child[i].nodeType === 1) {
return true;
}
}
return false;
};
43.DOM基本操作
- 元素节点的增加
// 创建一个元素节点
var div = document.createElement("div");
document.body.appendChild(div);
// 创建一个文本节点
var text = document.createTextNode("文本的内容");
document.body.appendChild(text);
// 创建一个注释节点
var comment = document.createComment();
document.body.appendChild(comment);
// 创建一个文档碎片节点
var fragment = document.createDocumentFragMent("");
- 元素节点的插入-appendChild
// appendChild() 相当于“剪切”的push
var div1 = document.createElement("div");
var span = document.createElement("span");
var comment = document.createComment("hhh");
var text = document.createTextNode("你是谁");
div1.setAttribute("class", "red");
span.appendChild(text);
div1.appendChild(comment);
div1.appendChild(text);
div1.appendChild(span);
document.body.appendChild(div1);
上面代码中明明给span添加了元素节点,页面中并没有, appendChild如果对一个元素进行操作,以最后一次操作为准
- 元素节点的插入-insertBefore(A,B)---> insert A before B
// 最开始的dom树是这样的
<div id="app" class="class">
<ul></ul>
</div>
// 我们想要在div子元素的ul前面新增一个带有文本为哈哈哈哈的span标签
var app = document.getElementById("app");
var ul = document.getElementsByTagName("ul")[0];
var span = document.createElement("span");
var text = document.createTextNode("哈哈哈哈哈");
span.appendChild(text);
app.insertBefore(span, ul);
执行结果:
- 元素节点的删除-node.remove(child) 和 chid.remove()
var app = document.getElementById("app");
var ul = document.getElementsByTagName("ul")[0];
var span = document.createElement("span");
var text = document.createTextNode("哈哈哈哈哈");
span.appendChild(text);
app.insertBefore(span, ul);
// 删除ul的两种方式
ul.remove(); --> 自杀
app.removeChild(ul); --> app父元素谋杀
- 元素节点的替换 parent.replaceChild(new, origin);
// span元素替换id为app的div的ul元素
var app = document.getElementById("app");
var ul = document.getElementsByTagName("ul")[0];
var span = document.createElement("span");
var text = document.createTextNode("哈哈哈哈哈");
span.appendChild(text);
app.replaceChild(span, ul);
DOM树由上面变成下面的:
- 元素节点获取内容 --> innerHTML 和 innerText
// html结构
<div id="app" class="class">
我是innerText
<!-- 我是注释节点 -->
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
</div>
// script代码
var app = document.getElementById("app");
console.log(app.innerHTML);
console.log(app.innerText);
输出:
- 元素innerHTML 和 innerText赋值
var app = document.getElementById("app");
app.innerHTML = "<div>7</div>";
上面代码的innerHTML替换成innerText
- 获取元素的某个行内属性 node.getAttribute(xx)
// html
<div id="app" class="class"></div>
// js
var app = document.getElementById("app");
console.log(app.getAttribute("id"));
- 获取元素的行内属性 node.attributes
var app = document.getElementById("app");
console.log(app.getAttribute("id"));
console.log(app.attributes);
console.log(app.attributes.id);
console.log(app.attributes.class);
console.log(app.attributes.name);
- 设置元素的某个行内属性 node.setAttribute(key, value)
// html
<div id="app" class="class"></div>
// js
var app = document.getElementById("app");
app.setAttribute("name", "zl");
44.Date对象
// new Date() 创建一个事件对象,这个时间对象他有几个方法
var date = new Date();
date.getFullYear(); // 获取年份
date.getMonth(); // 获取月份 date.getMonth() + 1才是当前月份
date.getDate(); // 获取天
date.getHours(); // 获取小时
date.getMinutes(); // 获取分钟
date.getSeconds(); // 获取秒数
date.getDay(); // 返回一周的某一天 0~6 0表示周日 6表示星期六
45.JS定时器
-
setInterval(每隔time都会执行定时器)
var interval = setInterval(() => {
// 没个time 执行该语句
}, time);
// 使用 clearInterval(interval) 来清除定时器
-
setTimeout(只会执行一次),应用在非vip显示部分视频
var timeout = setTimeout(() => {
// time之后 执行的语句
}, time);
clearTimeout(timeout); // 清除定时器
46.脚本化css
- 行内style属性(可读写)
element.style.prop
eg:
document.getElementById("custom-bg").style.backgroundColor = "red"
// 输出"red"
// 需要注意
a. 该方法可读写行内样式,没有兼容问题,碰到float这样的保留字属性,需要在前面加css
例如 float 应该写成 cssFloat
b. 复合属性必须拆解,组合单词编程小驼峰式写法
例如:background-color 应该写成 backgroundColor
c. 写入的值必须是字符串形式
例如:div.style.width = "200px";
- 读取css属性(只读)
window.getComputedStyle(elm, null)[prop] // elm表示元素,第二个参数如果是伪元素那么写伪元
素名称,否则输出null即可,prop表示css
window.getComputedStyle(elm, "after")[prop]
window.getComputedStyle(document.getElementById("custom-bg"), null)["width"]
// 输出"160px"
// 当我们尝试去设置css时
window.getComputedStyle(document.getElementById("custom-bg"), null)["width"] = "1800px"
// 会报错
Uncaught DOMException: Failed to set the 'width' property on 'CSSStyleDeclaration':
These styles are computed, and therefore the 'width' property is read-only.
注意:DOm操作是js连接JS和HTML操作的桥梁,JS中每次设置elm.style.prop都会损耗效率,所以应该改变elm的className,这样效率会提高
47.事件
- 定义:
你做了什么,程序给你一个反馈,事件是交互体验的核心功能
- 如何绑定事件
// 第一种给元素绑定事件方式
elm.onclick = function(event) {}
兼容性很好,但是一个元素的同一个类型的事件上只能绑定一个处理函数,如果同时赋值一个类型函数,后面的函数为最后赋值函数
事件为本元素的事件
// 第二种给元素绑定事件的方式
node.addEventListener(type, fn, false) --> ie9以下不兼容 第三个参数是是否事件委托
el:
div.addEventListener("click", function() {console.log(123);}, false)
如果你绑定了多个事件,会依次执行
// addEventListener ie9以下不兼容 使用node.attachEvent('on' + type, fn, false)
eg:
div.attachEvent("onclick", function() {console.log(123)}, false)
IE独有 一个事件可以绑定多个处理程序
- 给ul下的li绑定事件,并输出他们的内容,他们的内容从0开始组建递增
// html
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
</ul>
// js
var liCollections = document.getElementsByTagName("li"),
len = liCollections.length;
for (let i = 0; i < len; i++) {
(function(i) {
liCollections[i].addEventListener("click", _ => {
console.log(i)
}, false)
}(i))
}
- 事件处理程序的运行环境中this的指向
a. elm.onXX = function(){}
this 指向elm本身
b. elm.addEventListener(type, fn, false)
this 指向elm本身
c. elm.attachEvent("on" + type, fn, false)
this 指向window
- 封装一个addEvent(elm, type, handle) 方法,给元素封装一个type类型的事件
function addEvent(elm, type, handle) {
// ie9以上
if(elm.addEventListener) {
elm.addEvent(type, handle);
} else if(elm.attachEvent) {
// ie9及以下
elm.attachEvent(`on${type}`, handle);
} else {
// 全不兼容 使用 onXX
elm["on" + type] = handle;
}
}
// 给页面中的第一个span注册一个点击事件
function test() {
alert("我注册了点击事件");
}
var ul = document.getElementsByTagName("span")[0];
addEvent(ul, "click", test);
- 解绑事件处理程序
a. elm.onxxx = false/null;
b. elm.removeListener(type, fn, false) fn必须是定义在外面的,否则没有事件引用
c. elm.detachEvent("on" + type, fn);
48.事件处理模型(事件冒泡和捕获)※
- 事件冒泡
dom元素在结构上(非视觉上)存在嵌套关系的元素,会存在事件冒泡的功能,即同一事件,自子元素向父元素
事件依次执行(自里向外,自底向上)
- 事件捕获 addEventListener(type, handle, true)
结构上存在嵌套关系的元素,会存在事件捕获的功能事件,自父元素向子元素(自顶向下)
IE注册事件的函数attachEvent没有事件捕获功能
事件冒泡和事件捕获的触发顺序,先捕获再冒泡,一个对象绑定的一个事件类型上面绑定的一个处理函数只能尊新一个事件模型(要么冒泡,要么捕获)
- 不冒泡的事件
focus blur change submit reset select
- 取消冒泡
a. event.stopPropagation() ie9以上
b. event.cancleBubble = true; ie独有
c. 封装一个取消冒泡的函数
function stopBubble(event) {
var event = event || window.event;
if(event.stopPropagation) {
event.stopPropagation();
} else {
event.cancleBubble = true;
}
}
eg:
function wrapAlertBuHuo() {
alert("wrapAlertBuHuo");
stopBubble();
}
- 阻止默认事件,表单提交,a标签跳转,右键菜单
a. return false;
以对象属性的方式注册的事件才有效
eg:
let a = document.getElementsByTagName("a")[0]
a.onclick = function(e) {
console.log(e);
return false;
};
b. event.preventDefault() w3c标注,ie9以上兼容
c. event.returnValue = false;
封装一个阻止默认事件的函数
function cancalHand(event) {
var event = event || window.event;
if(event.preventDefault) {
event.preventDefault()
} else {
e.returnValue = false;
}
}
- a标签取消跳转/至顶事件
第一:
a.onclick = function() {
return false;
}
第二:
<a href="Javascript: void(0/false)">noStep</a>
第二种取消a标签跳转点击之后会出现以下标注
49.事件对象
- elm.onclick = function(e){}
// e事件对象会被浏览器打包传到函数参数中区,非ie浏览器会这样做;ie浏览器的事件对象存放在window.event
// 封装兼容各种浏览器的事件对象
dom.onclick = function(e) {
var e = e || window.event;
}
- 事件源对象
event.target // firefox只有这个
event.srcElement // ie只有这个
google Chrome // 两个都有
- 事件源对象用处:(事件委托)-- addEventListener(type, fn, true) 第三个参数为true开启事件委托
// 题:ul > li * 100 输出li中的内容
var ul = document.getElementsByTagName("ul")[0];
ul.onclick = function(e) {
var event = e || window.event,
target = event.target || event.srcElement;
console.log(target.innerText);
}
50.事件分类
- 鼠标事件
onmouseDown // 鼠标点击下去
onmouseUp // 鼠标点击之后放开
onclick // mouseUp执行完之后执行click事件 click = mouseDown + mouseUp
onmouseMove // 鼠标移动事件
onmouseEnter // 鼠标移入元素时事件
onmouseLeave // 鼠标移出元素事件
onmouseOver // 鼠标在元素上移动事件
onmouseOut // 鼠标在元素上移出事件
、、这些事件都是绑定在document上面的
- 鼠标右击出现菜单:唯一用处是取消右键菜单选项
document.oncontextmenu = function() {
return false;
}
- 如何区分鼠标的左右键:只有onmousedown、onmouseup事件的事件源对象有这个button属性,button为0是左边,1是鼠标滑轮,2是鼠标右键
document.onmouseup = function(e) {
var event = e || window.event;
if(event.button === 2) {
console.log("鼠标右键');
} else if(event.button === 0) {
console.log("鼠标左键");
} else {
console.log("滑轮");
}
}
注意:DOm3规定click只监听右键,只能通过onmousedown、onmouseup来判断鼠标键
- 如何让a标签在想点击的时候跳转页面,拖拽的时候不跳转页面:利用时间差
var firstTime = 0,
lastTime = 0,
key = flase;
document.onmousedown = function() {
firstTime = new Date().getTime();
}
document.onmouseup = function() {
lastTime = new Date().getTime();
if(lastTime - firstTime < 300) {
key = true;
}
}
document.onclick = function() {
if(key) {
console.log("click");
key = false;
}
}
51.键盘事件
- keydown keyup keypress
onkeypress = onkeydown + onkeyup ? flase
document.onkeypress = function () {
console.log("press");
}
document.onkeydown = function () {
console.log("down");
}
document.onkeyup = function () {
console.log("up");
}
注意:输出down、press、up,press跟down和up无关,press跟down类似,按键一直按住时,会一直输出down、press,做游戏时使用onkeydown
keydown 与 keypress的区别
keydown能检测到所有的按键类事件(fn是服务键),keypress只能检测到字符类按键(Ascall有的)
如果你想区分字母的大小写用keypress,如果你用操作类的话使用keydown
keydown与keypress的事件源对象里面有charCode记录每个按键的值(共有108个键值)
document.onkeypress = function(e) {
console.log(e.charCode);
}
- 文本类操作事件 input focus blur change
- 封装一个placeholder
<input type="text" value="请输入用户名" style="color: #999;" onfocus="if(this.value ===
'请输入用户名'){this.value = ''}" onblur="if(this.value == ''){this.style.color = '#999';
this.value ='请输入用户名'}">
- 窗口操作类(window上有的事件)scroll 和 load
// onscroll
window.onscroll = function() {
console.log(window.pageXOffset, window.pageYOffSet);
}
// onload 用处是在页面加载完成之后加载一个广告弹窗
window.onload = function() {
var div = document.createElement("div");
div.style.width = "100px";
div.style.height = "100px";
div.style.backgroundColor ="red";
document.documentElement.appendChild(div);
}
注意:我们平时都不会用window.onload执行事件,因为它阻塞代码,它会等到html解析完成和文档下载完成之后执行
52.浏览器识别HTML、CSS、JS顺序
①生成domTree,使用的是深度优先原则(一条枝干往下看,看完看其他的)
domTree
head body
title meta.. div span
strong em
生成domTree的过程叫做dom节点的解析(我认识你就行,比如解析过程中遇到img标签,不会等img下载完再
挂载到domTree上,而是看到这里有个叫img的元素,就把"img"这个标签挂上去)
domTree的形成代表所有dom节点解析完毕,并不是dom节点的下载(加载)完
domTree形成之后等cssTree的形成
②生成cssTree
根据我们写的css代码,生成一个类似domTree的树,跟domTree节点都是对应的(使用的也是深度优先原
则),等cssTree形成之后会与domTree树结合形成renderTree(渲染树)
③等renderTree形成之后,js引擎会根据renderTree来绘制页面
注意:renderTree是渲染页面的最后模板,如果你在JS中删除或增加一个元素,会重排(reflow)domTree和renderTree,降低了效率
- 什么情况下造成重排、什么情况下造成重绘呢?
a. 造成重排 reflow
dom 节点的删除或者新增
dom 节点的宽高、位置变化
display:none ---> 改为block
offsetWidth、offsetHeight 发生改变
b. 造成重绘 repain
改变css颜色之后,会改变cssTree中的一部分,随之renderTree也会改变对应的一部分,repain对效率的影
响比较小,可以使用。
53.异步加载js
- js加载的缺点
加载工具方法没必要阻塞文档,js加载因为本身是同步的会影响页面加载效率,一旦网速不好,那么整个网站等待js加载就不会进行后续的页面渲染等工作
有些工具方法需要按需加载,用到再加载,不用就不需要加载了
- 异步加载js的三种方案
① defer异步加载(只有ie可用)
defer异步加载需要等到DOM文档全部解析完才能被执行。
<script src="xx.js" defer type="text/javascript"></script>
<script defer>
//...
</script>
②async异步加载
async只能加载外部脚本,不能把js写到script中
<script async src="./xx.js"></script>
- 第三种方案,创建script,插入到dom中,加载完毕之后写callback即可
// 创建script
<script>
var script = document.createElement("script");
script.src = "ts.js";
document.body.appendChild(script);
test();
</script>
// ts.js中的内容是
function test() {
console.log("我是ts.js文件");
}
输出为Uncaught ReferenceError: test is not defined
原因是: 在创建好script并且下载ts.js的过程中,得发请求和返回资源内容,test()已经执行完毕
所以现在有个问题,我引入一个工具类,我啥时候才能用呢,总不能无法检测吧,这样经常会出现上面的情况,script有一个方法叫做onload,onload是等你下载完文档再执行test方法。
// 创建script
<script>
var script = document.createElement("script");
script.src = "ts.js";
script.onload = function() {
test()
}
document.body.appendChild(script);
</script>
// ts.js中的内容是
function test() {
console.log("我是ts.js文件");
}
ie没有onload方法,相对应地有状态码,script.readyState,状态码从loading--> loaded/complete
script.onreadystatechange = function() {
if(script.readyState == "complete" || script.readyState == "loaded"){
test()
}
}
- 封装一个兼容ie或者其他浏览器的异步加载js的函数
function loadScript(url, callback) {
var script = document.createElement("script");
script.type = "text/javascript";
if(script.readyState) {
script.onreadystatechange = function() {
if(script.readyState == "loaded" || script.readyState == "complete") {
callback();
}
}
} else {
script.onload = fucntion() {
callback();
}
}
script.src = url;
document.body.appendChild(script);
}
54.JS加载时间线
JS时间线是根据JS出生的那一刻开始,记录浏览器按照一定顺序做的事
1.
创建一个document对象,开始解析页面,解析html元素和他们的文本内容后添加ELement对象和Text节点到文档
中,这个阶段document.readyState = "loading"
2.
遇到link外部css,创建线程进行异步加载css,并继续解析文档
3.
遇到script外部js,而且没有设置defer或者async,浏览器加载被阻塞,等待js在家完成并解析脚本,然后继续
解析文档
4.遇到script外部js,如果设置了defer或者async,浏览器创建线程异步加载,并继续解析文档,对于async属
性的脚本,脚本加载完之后立即执行(异步禁止使用document.write()),defer属性的外部脚本得等解析完才
执行
5.
遇到img标签时,先正常解析dom结构,然后浏览器创建线程异步加载src,并继续解析文档
6.
当文档解析完成时,document.readState = "interactive",domTree建立完成
7.
文档解析完成之后,所有设置defer的脚本会按顺序执行(注意与async不同,同样禁止document.write())
8.
document对象触发DomConnentLoaded事件,这也标志着程序执行从同步脚本执行阶段,转化为事件驱动阶段
(浏览器可以识别事件了)
9.
当所有async的脚本加载完并执行完后,img等加载完成后,document.readyState = "complete",window对
象触发load事件
10.
从此,以一部相应方式处理用户输入,网络事件等
简化
1.
创建document对象,JS可以执行了
2.
文档解析完了
3.
文档加载完并执行完了