JavaScript(ES5)
数据类型
类型分类
数据类型共八种,分为 number,string,boolean,null,undefined,object,Symbol(ES6),BigInt(ES6)
基本数据类型
JavaScript中的变量可能包含两种不同数据类型的值:基本数据类型和引用数据类型。
JavaScript中一共有5种基本数据类型:String、Number、 Boolean、Undefined、Null。
基本数据类型的值是无法修改的,是不可变的。
基本数据类型的比较是值的比较,也就是只要两个变量的值相等,我们就认为这两个变量相等。
引用数据类型
引用类型的值是保存在内存中的对象。
当一个变量是一个对象时,实际上变量中保存的并不是对象本身,而是对象的引用。
当从一个变量向另一个变量复制引用类型的值时,会将对象的引用复制到变量中,并不是创建一个新的对象。
这时,两个变量指向的是同一个对象。因此,改变其中一个变量会影响另一个。
堆栈及数据类型内存分配梳理
栈和堆梳理
JavaScript在运行时数据是保存到栈内存和堆内存当中的。
简单来说栈内存用来保存变量和基本类型,堆内存是用来保存对象。
我们在声明一个变量时,实际上就是在栈内存中创建了一个空间用来保存变量。
如果是基本类型则在栈内存中直接保存,如果是引用类型则会在堆内存中保存,变量中保存的实际上对象在堆内存中的地址
数据类型内存分配梳理:
1、原始类型变量的内存在栈中分配,数据存储在栈内存中
2、引用类型变量的内存在栈中分配,实际的内存在堆中分配,变量的内存存储的是堆内存中的地址
3、拷贝时,原始类型是拷贝数据,引用类型是拷贝在堆内存中的地址
4、引用类型,每有一个变量引用了它的内存地址,计数器+1,反之,计数器为0时,该引用类型的地址被释放
Number
Number 类型用来表示整数和浮点数,最常用的功能就是用来表示10进制的整数和浮点数。
Number表示的数字大小是有限的,如果超过了这个范围,则会返回 ±Infinity。
使用typeof检查一个Number类型的数据时(包括NaN 和 Infinity),会返回"number"。
进制表示
二进制:0b 开头表示二进制,但是,并不是所有的浏览器都支持
八进制:0 开头表示八进制
十六进制:0x 开头表示十六进制
常用的Number常量:
最大值 Number.MAX_VALUE,
最小值 Number.MIN_VALUE,
无穷大 Number.POSITIVE_INFINITY
常用的API:
函数 isFinite() 判断一个数是否是无穷大,如果是正常数返回true,无穷大返回false
函数 isNaN() 会自动调用Number进行类型转换,如果无法转换(即NaN)返回true,否则返回false
成员函数 toFixed() 保留多少位小数,返回的数据是string类型的
0.1 + 0.2 !== 0.3 问题探究
JS中数字是以IEEE754双精度64位浮点数来存储的。IEEE 754 标准的 64 位双精度浮点数的小数部分最多支持53(52位,加上整数部分隐藏位)位二进制位,所以两者相加后,因浮点数小数位的限制而截断的二进制数字,再转换为十进制,就成了 0.30000000000000004,所以在进行算术计算时会产生误差。
//方法一:
let num = parseFloat((0.1 + 0.2).toFixed(1));
console.log(num); // 0.3
//方法二:
console.log(((0.1 * 10) + (0.2 * 10)) / 10);
//方法三:
function test(a,b){
return Math.abs(a - b) < Number.EPSILON ? true : false;
}
console.log(test((0.1 + 0.2),0.3));
String
使用typeof运算符检查类型时,会返回"string"。
常用的成员函数
string.indexOf() 查找某个字符,返回相应的位置下标
string.lastIndexOf() 查找某个字符,返回相应的位置下标(从后往前开始查找)
string.concat() 拼接字符串,返回拼接后的结果
string.charAt() 返回指定位置的字符
string.substring(start,end) 返回指定位置的字符串
string.substr(start,length) 返回指定位置的字符串
string.split() 基于某种分隔符把字符串切割成数组返回
String.fromCharCode(多个参数) 把该ASCII码值的字符组成字符串返回
string.charCodeAt() 获得指定字符的ASCII码
string.replace() 替换相应的字符串
string.slice(start,end) 截取相应的字符串返回,不影响原字符串
string.toLowerCase() / string.toUpperCase() 转换大小写,返回新的字符串
string.trim() / string.trimLeft() / string.trimRight() 去掉两边的空格,不影响原字符串,返回新的字符串
string.length 字符串长度,中文和英文都是一个字节,可以用于string遍历
Boolean
布尔型也被称为逻辑值类型或者真假值类型。
布尔型只能够取真(true)和假(false)两种数值。除此以外, 其它的值都不被支持。
Undefined
Undefined 类型只有一个值,即特殊的 undefined。
在使用 var 声明变量但未对其加以初始化时,这个变量的值就是 undefined。
注意:使用typeof对没有初始化和没有声明的变量,会返回“undefined”。
Null
Null 类型是第二个只有一个值的数据类型,这个特殊的值是 null。
undefined值实际上是由null值衍生出来的,所以如果比较undefined和null是否相等,会返回true。
注意:从语义上看null表示的是一个空的对象,所以使用typeof检查null会返回一个Object。
强制类型转换
强制类型转换指将一个数据类型强制转换为其它的数据类型。一般是指,将其它的数据类型转换为String、Number、Boolean。
转换为Number类型
有三个函数可以把非数值转换为数值:Number()、parseInt() 和parseFloat()。Number()可以用来转换任意类型的数据,而后两者只能用于转换字符串。parseInt()只会将字符串转换为整数,而parseFloat()可以将字符串转换为浮点数。
方式一:使用Number()函数
- 字符串 --> 数字
- 如果是纯数字的字符串,则直接将其转换为数字
- 如果字符串中有非数字的内容,则转换为NaN
- 如果字符串是一个空串或者是一个全是空格的字符串,则转换为0
- 布尔 --> 数字
- true 转成 1
- false 转成 0
- null --> 数字
- null 转成 0
- undefined --> 数字
- undefined 转成 NaN
方式二:使用 ParseInt ( 数字,按进制解析 ) 函数
这种方式专门用来对付字符串,parseInt() 把一个字符串转换为一个整数,把一个字符串按照整数的方式解析,如果只有开头一部分可以,解析一部分,如果解析不了,NaN(不会进行隐式类型转换,开头部分非整数,直接NaN)
方式三:使用 ParseFloat () 函数
这种方式专门用来对付字符串,parseFloat() 把一个字符串转换为一个浮点数
总结
1、区别:Number本质上是能否转换成数字,parseInt是开头部分看上去更像数字
2、Number可以,parseInt不可以:false 、null、空字符串
3、Number不可以,parseInt可以:数字开头的字符串
4、都不可以:undefined、字母开头的字符串
5、如果对非String使用parseInt()或parseFloat(),它会先将其转换为String然后在操作
转换为String类型
将其它数值转换为字符串有三种方式:toString()、String()、 拼串。
-
toString(进制)
toString()方法,该方法不会影响到原变量,它会将转换的结果返回
注意:null和undefined这两个值没有toString()方法,如果调用它们的方法,会报错。 -
String()
使用String()函数做强制类型转换时,对于Number和Boolean类型的数据实际上就是调用的toString()方法,但是对于null和undefined,就不会调用toString()方法,它会将 null 直接转换为 “null”,将 undefined 直接转换为 “undefined”。 -
拼串
转换为Boolean类型
将其它的数据类型转换为Boolean,只能使用Boolean()函数
使用Boolean()函数
- 数字 —> 布尔
- 除了0和NaN,其余的都是true
- 字符串 —> 布尔
- 除了空串,其余的都是true
- null和undefined都会转换为false
- 对象也会转换为true
运算符
算数运算符 + - * / % ++ –
关系运算符 > >= < <=
赋值运算符 = += -= *= /= %=
逻辑运算符 && || !
- &&在布尔运算的应用:
x&&y,如果x是false,返回x。如果x是true,返回y 等价于false的有0、undefined、null、NaN、‘ ‘ - ||在布尔运算的应用 :
x||y,如果x是true,返回x。如果x是false,返回y
比较运算符
- 使用 == 来做相等运算
- 当使用==来比较两个值时,如果值的类型不同,则会自动进行类型转换,将其转换为相同的类型,然后在比较.
- 使用 != 来做不相等运算
- 不相等用来判断两个值是否不相等,如果不相等返回true,否则返回false,不相等也会对变量进行自动的类型转换,如果转换后相等它也会返回false
- 使用 === 来做全等运算
- 用来判断两个值是否全等,它和相等类似,不同的是它不会做自动的类型转换,如果两个值的类型不同,直接返回false
- 使用 !== 来做不全等运算
- 用来判断两个值是否不全等,它和不等类似,不同的是它不会做自动的类型转换,如果两个值的类型不同,直接返回true
参与比较时的转换规则:
boolean作为运算数,会把true转换成 1 ,把false转换成 0
对象是对象/字符串,另一个运算数是数字,试图把对象/字符串转换成数字,失败的话转换成NaN
对象作为运算符,另一个是字符串,试图把对象转化成字符串
undefined和null互相相等,也互相等于自己,也等价于false(0),但是undefined和null不等于0
console.log(false == 0); // true
console.log(true == 1); // true
console.log(undefined == null); // true
console.log(undefined == undefined); // true
console.log(null == null); // true
console.log('5' == 5); // true
console.log([] == ''); // true
console.log({} == '[object Object]'); // true
console.log(NaN == NaN); //false
console.log(NaN == 'NaN'); //false
console.log(undefined == 0); //false
console.log(null == 0); //false
条件运算符 ?:
逗号运算符 ,
运算符优先级
- ()
- ! ++ –
- * / %
- + -
- < > >= <=
- == != === !==
- &&
- ||
- ? :
- = += -= *= /= %=
- ,
包装类
- Number,String,Boolean 三者使用 new 关键字,返回的是 Object 类型
- 为什么一个原始类型可以调用Object的成员函数?
JS默认创建临时变量把原始类型包装转换成object类型,使用该临时变量调用成员函数,调用完成,临时变量销毁
JavaScript 对象
Array对象
数组初始化
var arr = [1,2,3,4];
//只有一个参数 : 初始化长度为 x 的数组
//有多个参数 : 往数组中添加数据元素
var arr2 = new Array();
清空数组的四种方法
1、delete arr[i] //在原数组上操作删除数组中的元素,使其变成稀疏的,不改变数组长度
2、splice() //截取数组所有元素
3、pop() / shift() //每去掉一个元素会改变当前数组的长度,所以遍历删除的条件不能用length常规判断
4、 arr.length = 0 //动态改变length长度直接清空
5、arr = [] //存在隐患,不建议
遍历数组
1、arr.forEach(function(value,index,arr){},this)
//单纯用于数组遍历,不会改变原数组也没有返回值,无法使用break,continue跳出循环
2、arr.map(function(value,index,arr){},this)
//只支持遍历数组,对原始数组元素逐个进行操作,调用完成后返回一个新数组,不影响原数组
//支持链式调用
3、arr.filter(function(value,index,arr){},this)
//用于过滤数组,不会改变原数组,把所有满足条件的元素返回成一个新数组,没有满足的条件返回空数组
4、arr.some(function(value,index,arr){},this)
//数组只要有一个元素满足条件就返回true,反之false
//不会改变原数组,返回布尔值
5、arr.every(function(value,index,arr){},this)
//数组所有元素满足条件就返回true,反之false
//不会改变原数组,返回布尔值
6、arr.find(function(value,index,arr){},this)
//返回数组中满足条件的第一个元素的值
//不会改变原数组,返回布尔值
7、arr.findIndex(function(value,index,arr){},this)
//返回数组中满足条件的第一个元素的下标
//不会改变原数组,返回布尔值
8、arr.keys() / arr.values() / arr.entries()
9、for in
//适用于数组/对象循环遍历
//不仅会遍历当前对象的所有可枚举属性,还会遍历其原型链上的属性
10、for of
//只会遍历当前对象的属性,不会遍历到原型链上的属性
//适用于 数组/类数组/字符串/map/set等拥有迭代器对象的集合
//不支持遍历普通对象,因为其没有迭代对象,如果想要遍历obj对象的属性,可以使用for in
//可以使用break,continue,return中断循环遍历
数组常用成员函数
concat(xx)合并数组,返回新的数组
join (分隔符) 数组串行化,生成字符串
sort(回调) 默认把数字按照字符串ASCII码进行排序,在原数组上操作
reverse()翻转数组,在原数组上操作
slice()在数组上选择一部分元素进行拷贝,生成新的数组,原数组不受影响。一个参数:正数从0开始,倒数从-1开始。多个参数:slice(start,end)
splice()从数组上截取一部分元素,影响原数组。
splice(start) 从start到结尾全部截取,一样可以倒数截取
splice(start,length)从start开始截取length这么长的长度
splice(start,length,item,item...)从1start开始截取length长度的元素,在补充数据进去
map(回调)遍历并且操作数组元素
push()尾插
pop()尾出
shift()头出
unshift()头插
数组去重的两种方法
1、创建新数组,插入比对
var a1 = [1,3,1,2,5,5,6,4];
var a2 = [];
for(var i=0; i<a1.length; i++){
for(var j=0; j<a2.length; j++){
if(a2[j] == a1[i]){
break;
}
}
if(j>=a2.length){
a2.push(a1[i]);
}
}
console.log(a2.join('-'));
2、排序,数组每个元素与新数组末尾元素比较
var a3 = [];
a1.sort((a,b)=>{
return a-b;
});
console.log(a1.join('-'));
for(var i=0; i<a1.length; i++){
if(a1[i] !== a3[a3.length-1]){
a3.push(a1[i]);
}
}
console.log(a3.join('-'));
3、使用 Set 数据结构
Object对象
浏览器有个对象window。所有在脚本中(非函数范围内)用var定义的变量都挂载在window对象上。如果一个变量没有定义直接使用,在该作用域上找不到该变量,浏览器会默认把该变量挂载在window对象上
对象初始化
var obj = {};
var obj2 = new Object();
Object.create() // ES6介绍
删除属性
var obj = {
a: 1,
b: 2
}
delete obj.a;
console.log(obj); // {b;2}
查找属性
function F(){
this.a = 1;
this.b = 2;
}
F.prototype.name = '张三';
var obj = new F();
//使用in检查对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true
console.log('a' in obj);
console.log('name' in obj);
//可以使用对象的hasOwnProperty()来检查对象自身中是否含有该属性,使用该方法只有当对象自身中含有属性时,才会返回true
console.log(obj.hasOwnProperty('a'));
console.log(obj.hasOwnProperty('name'));
//判断对象属性(不继承)是否可枚举
console.log(obj.propertyIsEnumerable('name'));
Object.defineProperty(object , propName , descriptor)
- object 对象 => 给谁加
- propName 属性名 => 要加的属性的名字 【类型:String】
- descriptor 属性描述 => 加的这个属性有什么样的特性【类型:Object】
- value: 设置属性的值
- writable: 值是否可以重写。true | false
- enumerable: 目标属性是否可以被枚举。true | false
- configurable: 目标属性是否可以被删除或是否可以再次修改特性 true | false
- set: 目标属性设置值的方法
- get:目标属性获取值的方法
注意
使用Object.defineProperty() 定义对象属性时,如已设置 set 或 get, 就不能设置 writable 和 value 中的任何一个了,不然会报如下错误。因为不再需要value和writable的属性了,所有关于value的操作都由get和set代理了
当重新设定对象属性时,自动调用set函数
当输出对象属性的值时,自动调用get函数
遍历对象
var obj = {
name : '小红',
age : 15,
gender : 'male'
}
// for in
//不仅会遍历当前对象的所有可枚举属性,还会遍历其原型链上的属性
for(var i in obj){
console.log('key:',i,'value:',obj[i]);
}
// Object.keys()
for(var i in Object.keys(obj)){
console.log(i);
}
//Object.values()
for(var i in Object.values(obj)){
console.log(i);
}
//Object.entries()
for(var [key,value] in Object.entries(obj)){
console.log(key,value);
}
Function函数
函数声明
函数表达式
立即执行函数
递归
原始类型和引用类型和函数名作为参数
调用方式
普通调用,对象成员函数,构造函数,间接调用:使用call或者apply绑定this
形参与实参
- 形参的个数:函数名.length
- 实参的个数:arguments.length
- arguments.callee()就是函数本身
- 形参和实参个数互相对应的前提下,系统会自动把形参和实参绑定,否则不绑定
Date对象
new Date( ).getTime( ) 获得从1970年1月1日0点到现在的毫秒数
var d = +new Date() 等价于 getTime()毫秒数
设定年月(0~11代表 1~12月)日 :
new Date().setFullYear(x,x,x),具体时间是当前时间
new Date(x,x,x),具体时间是00:00:00,需要自己设定
设定年 new Date().setFullYear( year )
获得年 new Date().getFullYear()
获得月 new Date().getMonth()+1
获得日 new Date().getDate()
获得星期几 new Date().getDay()
获得小时 new Date().getHours()
获得分钟 new Date().getMinutes()
获得秒 new Date().getSeconds()
时间格式转换
GMT 格林尼治时间 new Date().toGMTstring()
UTC 协调世界时间 new Date().toUTCstring()
克隆时间 var d2 = new Date( d )
日期格式化
new Date().toDateString()
new Date().toLocalDateString()
时间格式化
new Date().toTimeString()
new Date().toLocalTimeString()
Math对象
//常量:
Math.PI
Math.E
Math.LN2
Math.LN10
Math.LOG2E
Math.LOG10E
根号二:Math.SQRT2
根号二分之一:Math.SQQRT1_2
//方法
三角函数(弧度 PI/xx ): Math.sin() Math.cos() Math.tan()
反三角函数(弧度值): Math.asin() Math.acos() Math.atan()
指数(以E为底) Math.exp()
对数(以E为底) Math.log()
任意数的指数 Math.pow(xx,xx)
开根号 Math.sqrt()
四舍五入 Math.round()
绝对值 Math.abs()
向上取整 Math.ceil()
向下取整 Math.floor()
找到最接近的单精度数 Math.fround()
最大值 Math.max(x,x,x,x,x,x,x)
最小值 Math.min(x,x,x,x,x,x,x)
随机值 0~1 之间平均分布的小数 Math.random()
RegExp对象
正则概述
正则表达式用于定义一些字符串的规则,计算机可以根据正则表达式,来检查一个字符串是否符合规则,获取将字符串中符合规则的内容提取出来。
RegExp初始化
//使用构造函数
var RegExp = new RegExp('正则表达式','匹配模式');
//字面量
var RegExp = /正则表达式/匹配模式
匹配模式:
- i:忽略大小写
- g:全局匹配模式
- ig:忽略大小写且全局匹配模式
- m:执行多行匹配
正则量词
1、简单类,单词本身
2、范围类 [ ]
3、负向类,排除 [ ^ ]
4、通配符 预定义类
- \d [0-9] 数字
- \D [ ^ 0 - 9 ] 非数字
- \w [a-zA-Z_0-9] 数字字母下划线
- \W [ ^a-zA-Z0-9_ ] 其他
- \s [ \ t \ n \ r \ f \ v ] 所有空格或者空白
- \S [ ^ \ t \ n \ r \ f \ v ] 非空格或者非空白
- \b 表示边界(两个字符之间的位置) 一边是\w,一边是\W
- \B 不是边界,两边都是 \w 或者 \w
- ^ 开始
- $ 结尾
- . [ ^ \n \r ] 除回车外所有字符
5、量词 任何一个单位允许重复的次数
- {n} 出现n次
- {m,n} 最少出现m次,最多出现n次
- {m, } 最少出现m次,最多不限制
- ? = {0,1}
- + = {1, }
- * = {0, }
6、
贪婪量词 ? + * 独立使用,返回最大字符串
惰性量词 +? *? 满足即停止,见好就收(在后面加 ?)
var str6 = 'aabbbasdbbbsedbbbbdsbbb';
console.log(str6.match(/.*bbb/g)); //搜索到最后的bbb,
console.log(str6.match(/.*?bbb/g)); //前面遇到aabbb满足即截断
7、分组
一个正则表达式,不仅可以对整个匹配进行操作,还可以对其中的()中的字串进行匹配
(pattern) ,捕获pattern匹配的结果 ,自动设定好分组
如果不想捕获分组,只需要在分组内加上(?:xxx)就可以了
表示捕获的子串 : \1~99 正则表达式中反向引用 , RegExp.$1~99 在外部中反向引用
(只要正则匹配了就会有,可以使用 test、exec 或者 str的replace方法获取捕获子串引用)
var str7 = 'world world hello excel world excel excel';
console.log(str7.match(/([a-zA-z]+)\s+\1/g));
(?pattern) 捕获pattern匹配的结果,自动设定分组,组名为name
\k <name> 反向引用
var str7 = 'world world hello excel world excel excel';
console.log(str7.match(/(?<word>[a-zA-Z]+)\s+\k<word>/g));
- 参数g 全局的,给出所有匹配的字符串
- 没有参数g 在第一匹配后停止,给出更多信息
- 0 : 匹配的串, 在js中引用 RegExp.lastMatch
- 1 : 捕获的串, 在js中引用 RegExp.$1
- groups : 如果使用了名字形式的分组,那么所有有名字的分组会放到groups里面,
- index : 匹配的串的起始位置,
- input : 原来的串
分组可以解决很多问题
$1~$99 分组 ,
//匹配符合条件的字串,倒转过来
var str3 = 'aaaa-bbbbb ccccc-dddddd';
console.log(str3.replace(/([a-z]+)-([a-z]+)/g,'$2-$1'));
$& 匹配的字串 , $`匹配子串的左侧 , $'匹配子串的右侧(在replace中函数回调也可以应用)
var str = 'I have a dream';
/dr(ea)m/.test(str);
console.log(RegExp.$1); // ea
console.log(RegExp['$&']); // dream
console.log(RegExp['$`']); // I have a
console.log(RegExp['$\'']); // __
8、断言
零宽-后行 断言 (?=x) 断言不算与字符串匹配
//匹配以分号结尾的两个字母
var str10 = 'reaaa;rcaaa=bbb=;';
console.log(str10.match(/[a-zA-Z]{2}(?=;)/g)); // ['aa']
零宽-前行 断言 (?<=x) 断言不算与字符串匹配
//匹配以re开头的的四个字符
var str10 = 'reaaa;rcaaa=bbb=;';
console.log(str10.match(/(?<=re).{4}/g)); // ['aaa'];
零宽-负向后行 断言 (?!x) 断言不算与字符串匹配
//匹配不以分号结尾三个字母
var str10 = 'reaaa;rcaaa=bbb=;';
console.log(str10.match(/[a-zA-Z]{3}(?!;)/g)); //['rea','rca','bbb']
零宽-负向前行 断言 (?<!x) 断言不算与字符串匹配
//匹配不以re开头的的三个字母a
var str10 = 'reaaa;rcaaa=bbb=;';
console.log(str10.match(/(?<!re)a{3}/g)); // ['aaa']
9、或 |
//匹配var 或者 function(){}
var str11 = 'var abcd';
var str12 = 'function f(){} var asd';
console.log(str11.match(/\s*var\s+[a-zA-Z_]\w*;?/));
console.log(str12.match(/\s*function\s+[a-zA-z_]\w*\s*\(\)\{\}/));
console.log(str12.match(/\s*var\s+[a-zA-Z_]\w*;?|\s*function\s+[a-zA-z_]\w*\s*\(\)\{\}/g));
- 参数g 全局的,给出所有匹配的字符串
- 没有参数g 在第一匹配后停止,给出更多信息
- 0 : 匹配的串, 在js中引用 RegExp.lastMatch
- 1 : 捕获的串, 在js中引用 RegExp.$1
- groups : 如果使用了名字形式的分组,那么所有有名字的分组会放到groups里面,
- index : 匹配的串的起始位置,
- input : 原来的串
正则方法
String
1、string.search(RegExp) 返回匹配的字串的开始位置,没有返回 -1
var str = 'I Love you';
console.log(str.search(/Love/)); // 2
2、string.match(RegExp) 返回匹配的字符串,正则表达式有无匹配模式影响返回的结果
var str = 'abcdefg';
console.log(str.match(/def/));
console.log(str.match(/def/g));
3、string.replace(正则 / 字符串,字符串 / 函数) 返回replace后的字符串
var str = 'I have a dream';
console.log(str.replace(/dream/g,'DREAM'));
正则表达式的不同,传递给回调的参数也不用
console.log(str3.replace(/([a-z]+)-([a-z]+)/g,function(){
//看看会传哪些参数进来
console.log(arguments);
}));
4.split(字符串/正则表达式,数组长度) 把字符串按照separator切成多个次,返回数组
var str4 = 'i hava s d drs asdasd fsdd';
// 按照空格切割
console.log(str4.split(/\s+/g));
console.log(str4.split(' '));
// 只想返回长度为4的数组
console.log(str4.split(/\s+/g,4));
RegExp
1、regExp.test(str) 判断字符串是否与正则表达式匹配,返回 true / false
var str6 = 'asdfgh';
console.log(/\w+/g.test(str6));
2.exec(string) 用于检索字符串中的正则表达式的匹配。返回值是一个数组,但是此数组的内容和正则对象是否是全局匹配有着很大关系
- 无论有没有匹配模式g,都会返回具信息
- 如果有g,还会额外返回lastIndex属性
var str5 = 'i word excel word';
var regexp = /word/g;
console.log(regexp.exec(str5));
console.log(regexp.lastIndex); // 6
//再调用一次exec,lastIndex会变成第二个符合匹配条件的字符结束位置的下一位
console.log(regexp.exec(str5));
console.log(regexp.lastIndex); // 17
1.没有g修饰符:
在非全局匹配模式下,此函数的作用和match()函数是一样的,只能够在字符串中匹配一次,如果没有找到匹配的字符串,那么返回null,否则将返回一个数组,数组的第0个元素存储的是匹配字符串,第1个元素存放的是第一个引用型分组(子表达式)匹配的字符串,第2个元素存放的是第二个引用型分组(子表达式)匹配的字符串,依次类推。同时此数组还包括两个对象属性,index属性声明的是匹配字符串的起始字符在要匹配的完整字符串中的位置,input属性声明的是对要匹配的完整字符串的引用。
特别说明:
在非全局匹配模式下,IE浏览器还会具有lastIndex属性,不过这时是只读的。
2.具有g修饰符:
在全局匹配模式下,此函数返回值同样是一个数组,并且也只能够在字符串中匹配一次。不过此时,此函数一般会和lastIndex属性匹配使用,此函数会在lastIndex属性指定的字符处开始检索字符串,当exec()找到与表达式相匹配的字符串时,在匹配后,它将lastIndex 属性设置为匹配字符串的最后一个字符的下一个位置。可以通过反复调用exec()函数遍历字符串中的所有匹配,当exec()函数再也找不到匹配的文本时,它将返回null,并把lastIndex 属性重置为0。
数组的内容结构和没有g修饰符时完全相同。
特别说明:
如果在一个字符串中完成了一次模式匹配之后要开始检索新的字符串,就必须手动地把lastIndex属性重置为0。
类型检测
-
typeOf
string,number,boolean,undefined准确判断,null、对象 -> object,function -> function -
instanceOf
判断其原型链上是否能找到该类型的原型 -
Object.prototype.toString.call()
toString是Object的原型方法,而Array,Function等引用类型作为Object的实例很多都重写了toString方法,根据原型链的知识逐层寻找,所以使用obj.toString()不能得到正确的数据类型,要调用Object原型上的toString方法-
toString方法
toString()函数用于将当前对象以字符串的形式返回。该方法属于Object对象,由于所有的对象都"继承"了Object的对象实例,因此几乎所有的实例对象都可以使用该方法,所有主流浏览器均支持该函数。 -
JavaScript的许多内置对象都重写了该函数,以实现更适合自身的功能需要。
-
类型 | 行为描述 |
---|---|
String | 返回 String 对象的值。 |
Number | 返回 Number 的字符串表示。 |
Boolean | 如果布尔值是true,则返回"true"。否则返回"false"。 |
Object | (默认) 返回"[object ObjectName]",其中 ObjectName 是对象类型的名称。 |
Array | 将 Array 的每个元素转换为字符串,并将它们依次连接起来,两个元素之间用英文逗号作为分隔符进行拼接。 |
Date | 返回日期的文本表示。 |
Error | 返回一个包含相关错误信息的字符串。 |
Function | 返回如下格式的字符串,其中 functionname 是一个函数的名称 此函数的 toString 方法被调用: “function functionname() { [native code] }” |
如何检测变量类型:
1、首先使用 typeof 判断 object 和 原始类型
2、不可以使用 instanceof ,因为 instanceof用于指明原型链,而且如果__proto__被修改,instanceof失效
3、使用Object.prototype.toString.call()
JavaScript DOM
DOM概述
当网页被加载时,浏览器会创建页面的文档对象模型(Document Object Model)。HTML DOM 模型被结构化为对象树
通过这个对象模型,JavaScript 获得创建动态 HTML 的所有力量:
- JavaScript 能改变页面中的所有 HTML 元素
- JavaScript 能改变页面中的所有 HTML 属性
- JavaScript 能改变页面中的所有 CSS 样式
- JavaScript 能删除已有的 HTML 元素和属性
- JavaScript 能添加新的 HTML 元素和属性
- JavaScript 能对页面中所有已有的 HTML 事件作出反应
- JavaScript 能在页面中创建新的 HTML 事件
- 换言之:HTML DOM 是关于如何获取、更改、添加或删除 HTML 元素的标准。
DOM节点
节点Node,是构成我们网页的最基本的组成部分,网页中的每一个部分都可以称为是一个节点。
比如:html标签、属性、文本、注释、整个文档等都是一个节点。
虽然都是节点,但是实际上它们的具体类型是不同的。
比如:标签我们称为元素节点、属性称为属性节点、文本称为 文本节点、文档称为文档节点,注释称为注释节点等。
节点的类型不同,属性和方法也都不尽相同。
节点:Node——构成HTML文档最基本的单元。
nodeType | nodeName | nodeValue | |
---|---|---|---|
1 | 元素 | 元素名 | Null |
2 | 属性 | 属性名 | 属性值 |
3 | 文本 | #text | 节点内容 |
8 | 注释 | #comment | 注释文本 |
9 | Document | #document | Null |
10 | Document Type | Doctype name | Null |
如果是一个节点,那么一定有nodeType 、nodeName 、nodeValue
如果是一个元素节点,那么一定有 元素.attributes[ index ] ( 是一个类数组,该元素属性组成的类数组 ) 拿到该属性对应的下标值,通过访问nodeType,nodeName,nodeValue就能得到该属性节点的具体详细信息
DOM节点操作
1、查找HTML元素
方法 | 描述 | 返回 |
---|---|---|
document.getElementById( ) | 通过元素 id 来查找元素。 | 返回元素 |
document.getElementsByTagName( ) | 通过标签名来查找元素。 | 返回类数组,不能用 for in 和 foreach 遍历 |
document.getElementsByClassName( ) | 通过类名来查找元素。 | 返回类数组,不能用 for in 和 foreach 遍历 |
document.getElementsByName( ) | 通过name属性来查找元素。 | 返回类数组,不能用 for in 和 foreach 遍历 |
document.querySelector( ) | 通过CSS选择器选择一个元素 | 返回元素 |
document.querySelectorAll( ) | 通过CSS选择器选择多个元素。 | 返回类数组,不能用 for in 遍历 |
2、访问该元素下的所有子节点
- childNodes(类数组 类型:NodeList) 该元素下的所有子节点
- children(类数组 类型:HTMLCollection) 该元素下的所有元素节点
3、访问节点的成员
- parentNode / parentElement
查看该节点的父节点 / 父元素节点(可以认为都是父节点) - previousSibling / previousElementSibling
在chrome和FF上:前一个结点 / 前一个元素结点
在IE上: 前一个元素结点 / 不支持 - nextSibling / nextElementSibling
在chrome和FF上:后一个结点 / 后一个元素结点
在IE上: 后一个元素结点 / 不支持 - firstChild / firstElementChild (在含有子节点的父节点上访问)
在chrome/FF上:第一个结点 / 第一个元素结点
在IE上: 第一个元素结点 / 不支持 - lastChild / lasteElementChild (在含有子节点的父节点上访问)
在chrome/FF上:最后一个结点 / 最后一个元素结点
在IE上: 最后一个元素结点 / 不支持
4、创建节点的三种方法
- document.write()
注意:如果页面加载完成后(onload事件)使用,会把页面上以前所有的元素全部清除,只用于页面加载时,页面加载完毕后不再使用 - innerHTML
注意如果页面加载完成后(onload事件)使用,不会把页面上以前所有的元素全部清除
直接赋值会覆盖该元素原有内容,可以用 += - document.createElement()
//创建节点
var node = document.createElement('input');
//设置属性
//方法一:
node.setAttribute('type','button');
//方法二:
var value = document.createAttribute('value');
value.value = '按钮';
node.setAttributeNode(value);
//获取属性
console.log(node.getAttribute('type'));
//在div1的最后挂在该节点作为div1的子节点
div1.appendChild(node);
5、节点的常用成员方法
- 增加
document.createElement() 创建元素节点
document.createTextNode() 创建文本节点
document.createComment() 创建注释节点
document.createDocumentFragment() 创建注释节点
parent.appendChild() 把某节点挂在到父节点上
parent.insertBefore(新节点,挂载在哪一个节点之前) - 删除
child.remove() 自杀
parent.removeChild() 父亲清理门户 - 替换
parent.replaceChild(新节点,已经存在的节点)
5-1、document.createDocumentFragment() 的具体解释
在更新少量节点的时候可以直接向document.body节点中添加,但是当要向document中添加大量数据是,如果直接添加这些新节点,这个过程非常缓慢,因为每添加一个节点都会调用父节点的appendChild()方法,为了解决这个问题,可以创建一个文档碎片,把所有的新节点附加其上,然后把文档碎片一次性添加到document中。
假如想创建十个段落,使用常规的方式可能会写出这样的代码:
for(var i = 0 ; i < 10; i ++) {
var p = document.createElement("p");
var oTxt = document.createTextNode("段落" + i);
p.appendChild(oTxt);
document.body.appendChild(p);
}
当然,这段代码运行是没有问题,但是他调用了十次document.body.appendChild(),每次都要产生一次页面渲染。这时碎片就十分有用了:
var oFragment = document.createDocumentFragment();
for(var i = 0 ; i < 10; i ++) {
var p = document.createElement("p");
var oTxt = document.createTextNode("段落" + i);
p.appendChild(oTxt);
oFragment.appendChild(p);<br>}
1
document.body.appendChild(oFragment);
在这段代码中,每个新的
元素都被添加到文档碎片中,然后这个碎片被作为参数传递给appendChild()。这里对appendChild()的调用实际上并不是把文档碎片本省追加到body元素中,而是仅仅追加碎片中的子节点,然后可以看到明显的性能提升,document.body.appenChild()一次替代十次,这意味着只需要进行一个内容渲染刷新。
用户数据
- 在标签内增加用户数据 data-xxx=“xxx”
- 不操作元素value的情况下,可以使用用户数据控制逻辑,在JS中编写元素.dataset[ ‘xxx’ ] = ’ xxx ’
innerHTML / innerText / textContent区别
- innerHTML 设置的是HTML原始字符串,如果是标签,会进行转换 chrome/FF/Ie8都支持
- innerText 设置单纯是文本内容 chrome/FF/Ie8都支持
- textContent chrome/FF 支持
offset* client* scroll* 区别
-
offset*
offsetWidth / offsetHeight 元素的宽高(content + padding + border)
offsetLeft / offsetTop 该元素到定位祖先的距离 -
client*
clientWidth / clientHeight 元素的宽高(content)
clientLeft / clientTop 元素border的大小 -
scroll*
scrollWidth / scrollHeight 元素内容的宽高
scrollLeft / scrollTop 可视窗口左上角 相对 element内容的宽度/高度的距离 -
获取浏览器滚动条高度兼容
pageOffset: window.pageXOffset / window.pageYOffset
html element : document.documentElement.scrollLeft / document.documentElement.scrollTop
body element : document.body.scrollLeft / document.body.scrollTop -
注意:
有 <! DOCYPE html> 的时候,body element 是没有数值的 ,html element 和 pageoffset是有数值的
没有 <! DOCYPE html> 的时候,body element 和 pageoffset是有数值的 ,html element 是没有数值的
事件
定义:如果某件事情发生在某个元素上以后,允许JS引擎自动调用的回调函数(callback)
绑定事件的三种方法
onClick
不支持捕获,支持冒泡
优势:兼容性好,相当于直接写DOM上的Attribute
劣势:如果调用两次,后面的回调会覆盖前面的回调
事件解绑: onclick = null
addEventListener(‘ type ’,function,true 捕获/ false冒泡 )
优势:调用多次,都有效
劣势:IE8及以下不支持
事件解绑:removeEventListener(’ type ‘ ,function)删除时必须有具体函数名,JS引擎没有途径查询
attachEvent(‘ onType ’,function)
不支持捕获,支持冒泡
劣势:chrome/FF不支持,只有这种方式在调用中时this指向的是window,其他方式this都是指向被触发的元素
事件解绑:detachEvent(’ onType ‘ ,function)
事件的冒泡和捕获
事件的冒泡(Bubble):所谓的冒泡指的就是事件的向上传导,当后代元素上的事件被触发时,其祖先元素的相同事件也会被触发,在开发中大部分情况冒泡都是有用的,如果不希望发生事件冒泡可以通过事件对象来取消冒泡。
捕获:事件与顺序和冒泡相反
同时有捕获和冒泡,执行顺序:
1、祖先节点,从高到低捕获
2、节点本身,按照绑定顺序,触发冒泡+捕获
3、祖先节点,从低到高冒泡
阻止默认行为
//事件委托 = 冒泡 + 起源
//事件起源
let e = e.target || e.scrElement
//冒泡取消
let e.stopPropagation() || e.cancelBubble = true
//阻止默认行为/事件
let e.preventDefault() || e.returnValue = false
//阻止a标签的跳转事件: return false
事件委托
我们希望只绑定一次事件,即可应用到多个的元素上,即使元素是后添加的,我们可以尝试将其绑定给元素的共同的祖先元素,也就是事件的委派。事件的委派,是指将事件统一绑定给元素的共同的祖先元素,这样当后代元素上的事件触发时,会一直冒泡到祖先元素,从而通过祖先元素的响应函数来处理事件。事件委派是利用了事件冒泡,通过委派可以减少事件绑定的次数,提高程序的性能。
利用冒泡+事件的起源,在父节点绑定事件,利用事件的起源进行操作,避免在DOM上过度绑定事件
应用
1、单选多选demo
//给多个元素绑定事件,用立即执行函数保存变量i,返回函数给事件,等于返回的函数有GO和立即执行函数两层作用域链,压栈出栈应用解决全部重复遍历问题
//闭包:立即执行函数
// 压栈,出栈
<input type="button" value="">
<input type="button" value="">
<input type="button" value="">
<input type="button" value="">
<input type="button" value="">
<input type="button" value="">
<input type="radio" value="">男
<input type="radio" value="">女
<input type="radio" value="">中
<input type="checkbox" value="">羽毛球篮球
<input type="checkbox" value="">游泳
<input type="checkbox" value="">打篮球
<input type="checkbox" value="">乒乓球
//一个按钮被点击了,改变value,其他还原
var btns = document.querySelectorAll('input');
var target = [];
for(var i=0; i<btns.length; i++){
//初始化所有元素的value值
btns[i].value = '按钮' + i;
//循环给每个元素声明函数,立即执行函数拿到当时的i,返回函数给click
//此时返回的函数拿到GO和立即执行函数的两层作用域链
btns[i].addEventListener('click',(function(x){
//添加属性,保存当时的下标
btns[i].myIndex = x;
return (function(){
while(target.length > 0){
var tmp = target.pop();
tmp.value = '按钮' + tmp.myIndex;
}
btns[x].value = '我被点击了';
//入栈,记录当前点击的目标
target.push(btns[x]);
});
})(i))
}
//单选判断
var radios = document.querySelectorAll('input[type="radio"]');
var temp = [];
for(var j=0; j<radios.length; j++){
//点击后,先变化checked状态,在触发事件
radios[j].addEventListener('click',function(){
//判断当前点击的是否在栈中,如果在,点击不做任何逻辑操作
for(var k=0; k<temp.length; k++){
if(temp[k] == this){
break;
}
}
//如果不在栈中,出栈,当前元素入栈
if(k >= temp.length){
while(temp.length > 0){
var tmp = temp.pop();
tmp.checked = false;
}
this.checked = true;
temp.push(this);
}
})
}
//多选判断
var checkbox = document.querySelectorAll('input[type="checkbox"]');
var checkTemp = [];
for(var l = 0; l<checkbox.length; l++){
checkbox[l].addEventListener('click',function(){
//原本没有选中过,checked状态先改变,再触发事件
if(this.checked){
while(checkTemp.length > 1){
var tmp = checkTemp.shift();
tmp.checked = false;
}
checkTemp.push(this);
}else {
//取消选中的
for(var q=0; q<checkTemp.length; q++){
if(checkTemp[q] == this){
checkTemp.splice(q,1);
break;
}
}
}
})
}
2、运用闭包,高亮显示
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
//运用立即执行函数,闭包保存当时每个元素的文本值,事件回调就会利用闭包拿到自己元素的文本
let list = document.querySelectorAll('div');
list.forEach((element => {
let tmp = element.innerHTML;
element.addEventListener('mouseover',function(){
this.innerHTML = parseInt(tmp) + 1;
})
element.addEventListener('mouseout',function(){
this.innerHTML = tmp;
})
}));
3、JS动态添加删除 class 样式
function addClass(element,sty){
let myClass = element.className.split(' ');
for(var i=0; i<myClass.length; i++){
//判断是否存在相同样式名
if(myClass[i] == sty){
break;
}
}
if(i >= myClass.length){
//不存在,判断样式是否为空,考虑空格问题
element.className = '' ? element.className = sty : element.className += ' ' + sty;
}
}
function closeClass(element,sty){
let myClass = element.className.split(' ');
for(var i=0; i<myClass.length; i++){
//判断是否存在相同样式名
if(myClass[i] == sty){
myClass.splice(i,1);
element.className = myClass.join(' ');
}
}
}
常用事件
-
鼠标相应事件:
onmouseover鼠标悬浮在元素上 onmouseenter 不支持冒泡
onmouseout 鼠标移走 onmouseleave 不支持冒泡
onmousemove 鼠标移动
onmousedown 鼠标按下
onmouseup 鼠标某个键松开
onwheel 鼠标滚轮被滚动触发
oncontextmenu 鼠标右键菜单点击
onclick 鼠标点击
ondblclick 鼠标双击某个对象 -
键盘相应事件:
onkeydown 键盘某个键按下
onkeypress 键盘某个键按住
onkeyup 键盘某个键松开()输入中文输入法的时候无法被触发,解决方案:oninput:FF/chrome支持,监听value的变化,onpropertychange ) -
表单事件
onfocus 聚焦
onblur 失去焦点
onchange 元素内容改变
oninput 元素获取用户输入时触发
onsubmit 提交按钮被点击
onreset 重置按钮被点击
onselect 文本被选定 -
框架/对象事件
onresize 浏览器窗口大小被改变
onabort 图像加载被中断
onabort 图像加载被中断
onerror 加载文档或者图像(没找到)发生错误 window.onerror(msg,url,line,col,error)
onscroll 浏览器页面滚动
onpageshow 用户访问页面时触发
onunload 用户退出页面 -
拖动事件
ondrag 元素正在被拖动时触发
ondragend 用户完成元素拖动时触发 -
多媒体事件
onplay 在视频/音频开始播放时触发
onended 在视频/音频播放结束时触发
onpause 在视频/音频暂停时触发 -
过渡结束事件 : ontransitionend
JavaScript BOM
BOM概述
浏览器对象模型(BOM)使 JavaScript 有能力与浏览器"对话"。
浏览器对象模型(Browser Object Model (BOM))尚无正式标准。
由于现代浏览器已经(几乎)实现了 JavaScript 交互性方面的相同方法和属性,因此常被认为是BOM的方法和属性。
浏览器对象模型(BOM)可以使我们通过JS来操作浏览器,在BOM中为我们提供了一组对象,用来完成对浏览器的操作,常见的BOM对象如下:
- Window:代表的是整个浏览器的窗口,同时window也是网页中的全局对象
- Navigator:代表的当前浏览器的信息,通过该对象可以来识别不同的浏览器
- Location:代表当前浏览器的地址栏信息,通过Location可以获取地址栏信息,或者操作浏览器跳转页面
- History:代表浏览器的历史记录,可以通过该对象来操作浏览器的历史记录,由于隐私原因,该对象不能获取到具体的历史记录,只能操作浏览器向前或向后翻页,而且该操作只在当次访问时有效
- Screen:代表用户的屏幕的信息,通过该对象可以获取到用户的显示器的相关的信息
这些BOM对象在浏览器中都是作为window对象的属性保存的,可以通过window对象来使用,也可以直接使用。
Window对象
JavaScript 有三种类型的弹出框:警告框、确认框和提示框。
弹出框
1、警告框
如果要确保信息传递给用户,通常会使用警告框。当警告框弹出时,用户将需要单击“确定”来继续。
//window.alert() 方法可以不带 window 前缀来写
window.alert('我是一个警告框');
2、确认框
如果您希望用户验证或接受某个东西,则通常使用“确认”框。
当确认框弹出时,用户将不得不单击“确定”或“取消”来继续进行。
如果用户单击“确定”,该框返回 true。如果用户单击“取消”,该框返回 false。
//window.confirm() 方法可以不带 window 前缀来编写
var r = confirm('请按按钮');
r == true ? console.log('已确认') : console.log('已取消');
3、提示框
如果您希望用户在进入页面前输入值,通常会使用提示框。
当提示框弹出时,用户将不得不输入值后单击“确定”或点击“取消”来继续进行。
如果用户单击“确定”,该框返回输入值。如果用户单击“取消”,该框返回 NULL。
//window.prompt() 方法可以不带 window 前缀来编写
var person = prompt('请输入您的姓名');
if (person != null) {
console.log(person);
}
定时器(window前缀可以省略不写)
JavaScript 可以在时间间隔内执行,这就是所谓的定时事件( Timing Events)。
window 对象允许以指定的时间间隔执行代码,这些时间间隔称为定时事件。
通过 JavaScript 使用的有两个关键的方法:
setTimeout(function, milliseconds) 在等待指定的毫秒数后执行函数。
setInterval(function, milliseconds) 等同于 setTimeout(),但持续重复执行该函数。
setTimeout() 和 setInterval() 都属于 window 对象的方法
常用窗口属性
两个属性可用用于确定浏览器窗口的尺寸。
这两个属性都以像素返回尺寸:
- window.innerHeight - 浏览器窗口的内高度(以像素计)
- window.innerWidth - 浏览器窗口的内宽度(以像素计)
浏览器窗口(浏览器视口)不包括工具栏和滚动条。
对于 Internet Explorer 8, 7, 6, 5:
-
document.documentElement.clientHeight
-
document.documentElement.clientWidth
或 -
document.body.clientHeight
-
document.body.clientWidth
一个实用的 JavaScript 解决方案(包括所有浏览器):该例显示浏览器窗口的高度和宽度(不包括工具栏和滚动条)
var w = window.innerWidth|| document.documentElement.clientWidth|| document.body.clientWidth;
var h = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
其它窗口方法
- window.open(URL,name,specs,replace) :打开新的窗口
Navigator对象
Navigator代表的当前浏览器的信息,通过该对象可以来识别不同的浏览器,由于历史原因,Navigator对象中的大部分属性都已经不能帮助我们识别浏览器了,一般我们只会使用userAgent来判断浏览器的信息,userAgent是一个字符串,这个字符串中包含有用来描述浏览器信息的内容,不同的浏览器会有不同的userAgent,如下代码:
var ua = navigator.userAgent;
console.log(ua);
Location对象
Location对象中封装了浏览器的地址栏的信息,如果直接打印location,则可以获取到地址栏的信息(当前页面的完整路径)
常用属性
console.log(location); //输出location对象
console.log(location.href); //输出当前地址的全路径地址
console.log(location.origin); //输出当前地址的来源
console.log(location.protocol); //输出当前地址的协议
console.log(location.hostname); //输出当前地址的主机名
console.log(location.host); //输出当前地址的主机
console.log(location.port); //输出当前地址的端口号
console.log(location.pathname); //输出当前地址的路径部分
console.log(location.search); //输出当前地址的?后边的参数部分
常用方法
assign():用来跳转到其它的页面,作用和直接修改location一样
location.assign("https://www.baidu.com");
reload():用于重新加载当前页面,作用和刷新按钮一样,如果在方法中传递一个true,作为参数,则会强制清空缓存刷新页面
location.reload(true);
replace():可以使用一个新的页面替换当前页面,调用完毕也会跳转页面,它不会生成历史记录,不能使用回退按钮回退
location.replace("https://www.baidu.com");
History对象
History对象可以用来操作浏览器向前或向后翻页
常用属性
console.log(history); //输出history对象
console.log(history.length); //可以获取到当成访问的链接数量
常用方法
back():可以回退到上一个页面,作用和浏览器的回退按钮一样
history.back();
forward():可以跳转到下一个页面,作用和浏览器的前进按钮一样
history.forward();
go():可以用来跳转到指定的页面,它需要一个整数作为参数
- 1:表示向前跳转一个页面,相当于forward()
- 2:表示向前跳转两个页面
- 1:表示向后跳转一个页面,相当于back()
- 2:表示向后跳转两个页面
history.go(-2);
Screen对象
Screen 对象包含有关客户端显示屏幕的信息
注意:没有应用于 screen 对象的公开标准,不过所有浏览器都支持该对象。
Screen对象描述
每个 Window 对象的 screen 属性都引用一个 Screen 对象。Screen 对象中存放着有关显示浏览器屏幕的信息。JavaScript 程序将利用这些信息来优化它们的输出,以达到用户的显示要求。例如,一个程序可以根据显示器的尺寸选择使用大图像还是使用小图像,它还可以根据显示器的颜色深度选择使用 16 位色还是使用 8 位色的图形。另外,JavaScript 程序还能根据有关屏幕尺寸的信息将新的浏览器窗口定位在屏幕中间。
Screen对象属性
属性 | 描述 |
---|---|
availHeight | 返回显示屏幕的高度 (除 Windows 任务栏之外)。 |
availWidth | 返回显示屏幕的宽度 (除 Windows 任务栏之外)。 |
bufferDepth | 设置或返回调色板的比特深度。 |
colorDepth | 返回目标设备或缓冲器上的调色板的比特深度。 |
deviceXDPI | 返回显示屏幕的每英寸水平点数。 |
deviceYDPI | 返回显示屏幕的每英寸垂直点数。 |
fontSmoothingEnabled | 返回用户是否在显示控制面板中启用了字体平滑。 |
height | 返回显示屏幕的高度。 |
logicalXDPI | 返回显示屏幕每英寸的水平方向的常规点数。 |
logicalYDPI | 返回显示屏幕每英寸的垂直方向的常规点数。 |
pixelDepth | 返回显示屏幕的颜色分辨率(比特每像素)。 |
updateInterval | 设置或返回屏幕的刷新率。 |
width | 返回显示器屏幕的宽度。 |
JavaScript 高级
预编译
脚本
- 创建全局对象GO(window)上下文
- 加载脚本文件
- 预编译:
- 找出所有变量声明,按照变量名加入全局对象window中,如果已经存在,忽略
- 找出所有的函数声明,按照函数名加入全局对象window中,如果已经存在同名变量或者函数,替换
- 非声明不予理睬
- 解释执行
调用
- 创建对象AO(Active Object)上下文
- 预编译:
- 初始化作用域链
- 初始化arguments,初始化形参,将实参的值赋值给实参
- 找出所有变量声明加入AO,如果已经存在,忽略
- 找出所有函数声明加入AO,如果存在同名的变量或者函数,替换
- 初始化this
- 解释执行
作用域
- 外部对内部可见
- 内部对外部不可见
- 内部优先
- ES5中只有函数级别的作用域,没有块级作用域,只会进入或者退出函数的时候,作用域会发生变化
- 除了在函数内部定义的变量是函数局部的,其他地方定义的基本都可以算是全局的
作用域链
**执行环境**
全局执行环境(GO)从见到JS代码开始创建,到网页关闭时销毁
函数执行环境(AO)从函数 ” 调用 “ 开始创建,到函数调用结束时销毁(既AO的引用数是0)
1、调用函数时创建函数作用域,函数执行完毕以后,函数作用域销毁,每调用一次函数就会创建一个新的函数作用域,它们之间是互相独立的
2、作用域是私有属性,只能由JS引擎访问
3、作用域链,是AO和GO构成的链
4、执行环境,就是根据作用域链依次层层向外查找变量和函数,找到既停,找不到报错
5、每个函数都有作用域链
-
如何生成作用域链
每个函数在定义(函数声明/函数表达式)时会拷贝其父亲函数的作用域链
在函数被调用时,生成AO并压入作用域链的栈顶
每个函数的作用域链都是不一样的,引用类型的地址是共用的 -
作用域链的应用
提高效率,尽量使用自己局部变量,少使用上层变量
重名容易出错,尽量减少不用层次函数使用相同的变量名,避免函数名与变量名一样
闭包
闭包引入
需求信息: 点击某个按钮,提示"点击的是第n个按钮"
第一种方法:将btn所对应的下标保存在btn上
var btns = document.querySelectorAll('button');
for(var i = 0; i<btns.length; i++){
btns[i].index = i;
btns[i].addEventListener('click',function(){
this.innerHTML = `第${this.index + 1}个按钮`
})
}
第二种方法:利用闭包延长局部变量的生命周期
var btns = document.querySelectorAll('button');
for(var i = 0; i<btns.length; i++){
(function(j){
btns[j].addEventListener('click',function(){
this.innerHTML = `第${j + 1}个按钮`;
})
})(i);
}
闭包的概念
- 如何产生闭包?
- 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时,就产生了闭包
- 函数的AO通过作用域链连接相互起来,使得函数体内的变量都可以保存在函数的AO中,称为闭包
- 什么才是闭包?
- 理解一:闭包是嵌套的内部函数(绝大部分人认为)
- 理解二:包含被引用变量(函数)的对象(极少部分人认为)
- 闭包的作用?
- 它的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中
- 危险:
- 造成原有的AO不释放,产生内存泄漏
闭包的生命周期
生命周期:
1、产生:在嵌套内部函数定义执行完时就产生了(不是在调用)
2、死亡:在嵌套的内部函数成为垃圾对象时就死亡了
演示:
function fn1() {
//此时闭包就已经产生了(函数提升, 内部函数对象已经创建了)
var a = 2;
function fn2() {
a++;
console.log(a);
}
return fn2;
}
var f = fn1();
f(); // 3
f(); // 4
f = null; //闭包死亡(包含闭包的函数对象成为垃圾对象)
闭包的应用
定义JS模块
- 具有特定功能的js文件
- 将所有的数据和功能都封装在一个函数内部(私有的)
- 只向外暴露一个包含n个方法的对象或函数
- 模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能
//实现公有变量(一个变量一个函数),对外返回一个函数
function createCount(){
let count = 0;
return (function addCount(){
count++;
console.log(count);
return count;
})
}
let count = createCount();
count();
//缓存存储结构(多个变量多个函数),对外返回数组对象
function createCount(){
let count = 0;
function add(){
count++;
console.log(count);
return count;
}
function reset(){
count = 0;
console.log(count);
return count;
}
return [add,reset];
}
let count = createCount();
count[0](); // 1
count[0](); // 2
count[1](); // 0
//模块化开发,防止污染全局变量(面向对象方式),对外返回对象
function createCount(){
let count = 0;
function add(){
count++;
console.log(count);
return count;
};
function reset(){
count = 0;
console.log(count);
return count;
};
return {add,reset};
}
let count = createCount();
count.add(); // 1
count.add(); // 2
count.reset(); // 0
工厂函数和构造函数
构造对象的方式:工厂模式(创建对象/this处理/返回对象:自己搞定)与构造函数(创建对象/this处理/返回对象:隐式处理)
用工厂函数创建对象
如果要生产1000个对象,就要书写1000个对象,那么,能不能有一种方法能够批量生产对象呢?
有的,使用工厂函数
// 使用工厂模式创建对象
function createPerson() {
// 创建新的对象
var obj = new Object();
// 设置对象属性
obj.name = "孙悟空";
obj.age = 18;
// 设置对象方法
obj.sayName = function () {
console.log(this.name);
};
//返回新的对象
return obj;
}
var person1 = createPerson();
console.log(person1);
这样我们就实现了批量创建对象的功能
用构造函数创建对象
在实际生活中,人应该是一个确定的类别,属于人类,对象是一个笼统的称呼,万物皆对象,它并不能确切的指明当前对象是人类,那我们要是既想实现创建对象的功能,同时又能明确所创建出来的对象是人类,那么似乎问题就得到了解决,这就用到了构造函数,每一个构造函数你都可以理解为一个类别,用构造函数所创建的对象我们也成为类的实例,那我们来看看是如何做的:
// 使用构造函数来创建对象
function Person(name, age) {
// 设置对象的属性
this.name = name;
this.age = age;
// 设置对象的方法
this.sayName = function () {
console.log(this.name);
};
}
var person1 = new Person("孙悟空", 18);
console.log(person1);
构造函数:构造函数就是一个普通的函数,创建方式和普通函数没有区别,不同的是构造函数习惯上首字母大写,构造函数和普通函数的还有一个区别就是调用方式的不同,普通函数是直接调用,而构造函数需要使用new关键字来调用。
那构造函数是怎么执行创建对象的过程呢?我再来解释一下:
1、调用构造函数,它会立刻隐式创建一个新的对象
2、将新建的对象设置为函数中this,在构造函数中可以使用this来引用新建的对象
3、逐行执行函数中的代码
4、将新建的对象作为返回值返回
this指针
- 在非函数的脚本中,this被初始化成window(默认绑定)
- 在普通函数调用中,this被初始化成window(默认绑定)
- 对象的函数,通过对象调用,this被初始化成对象(隐式绑定)
- call/apply中,this被初始化成第一个参数(显示绑定)
- 用new调用构造函数,this被指向刚刚创建的对象(new绑定)
- 如果把函数作为回调赋值给settimeout,那么this被初始化成window(隐式丢失)
- bind(this)可以帮助将this初始化成当前对象
- 原则:this永远和调用函数时的状态有关,和定义无关
- this绑定:call(this,item,item) apply(this,[item,item]) bind(this)(item,item)
原型
- 我们学习了使用构造函数的方式进行创建对象,但是,它还是存在一个问题,那就是,你会发现,每一个对象的属性不一样这是一定的,但是它的方法似乎好像是一样的,如果我创建1000个对象,那岂不是内存中就有1000个相同的方法,那要是有10000个,那对内存的浪费可不是一点半点的,我们有没有什么好的办法解决,没错,我们可以把函数抽取出来,作为全局函数,在构造函数中直接引用就可以了
- 但是,在全局作用域中定义函数却不是一个好的办法,为什么呢?因为,如果要是涉及到多人协作开发一个项目,别人也有可能叫sayName这个方法,这样在工程合并的时候就会导致一系列的问题,污染全局作用域,那该怎么办呢?有没有一种方法,我只在Person这个类的全局对象中添加一个函数,然后在类中引用?答案肯定是有的,这就需要原型对象了,我们先看看怎么做的,然后在详细讲解原型对象。
// 使用构造函数来创建对象
function Person(name, age) {
// 设置对象的属性
this.name = name;
this.age = age;
}
// 在Person类的原型对象中添加方法
Person.prototype.sayName = function() {
console.log(this.name);
};
var person1 = new Person("孙悟空", 18);
person1.sayName();
那原型(prototype)到底是什么呢?
我们所创建的每一个函数,解析器都会向函数中添加一个属性prototype,这个属性对应着一个对象,这个对象就是我们所谓的原型对象,即显式原型,原型对象就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象,我们可以将对象中共有的内容,统一设置到原型对象中。
如果函数作为普通函数调用prototype没有任何作用,当函数以构造函数的形式调用时,它所创建的对象中都会有一个隐含的属性,指向该构造函数的原型对象,我们可以通过__proto__(隐式原型)来访问该属性。当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如果找到则直接使用。
以后我们创建构造函数时,可以将这些对象共有的属性和方法,统一添加到构造函数的原型对象中,这样不用分别为每一个对象添加,也不会影响到全局作用域,就可以使每个对象都具有这些属性和方法了。
概念
1、每个函数都有一个prototype原型,每个对象都有__proto__属性指向自己构造函数的原型。 每个函数都有prototype属性,prototype包含挂载在原型的属性和方法、constructor指向自己构造函数本体、__proto__指向下一层原型,其实函数的原型也是Object的实例
2、原型上的方法可以访问原型的属性和实例上的属性
3、原型上的属性和方法是共享的,给实例添加属性或者方法不会影响原型,读取属性或者方法从实例沿原型链由近至远寻找
原型链
注意:Object对象是所有对象的祖宗,Object的原型对象指向为null,也就是没有原型对象
访问一个对象的属性时,先在自身属性中查找,找到返回, 如果没有,再沿着__proto__这条链向上查找,找到返回,如果最终没找到,返回undefined,这就是原型链,又称隐式原型链,它的作用就是查找对象的属性(方法)。
继承方法
前边我们一直在说继承,那什么是继承?它有什么作用?如何实现继承?将会是本章节探讨的问题。
面向对象的语言有一个标志,那就是它们都有类的概念,而通过类可以创建任意多个具有相同属性和方法的对象。但是在JavaScript中没有类的概念,前边我们说所的类只是我们自己这么叫,大家要清楚。因此它的对象也与基于类的对象有所不同。实际上,JavaScript语言是通过一种叫做原型(prototype)的方式来实现面向对象编程的。
那实现继承有一个最大的好处就是子对象可以使用父对象的属性和方法,从而简化了一些代码。
JavaScript有六种非常经典的对象继承方式,
原型链继承
核心思想: 子类型的原型为父类型的一个实例对象
基本做法:
1、定义父类型构造函数
2、给父类型的原型添加方法
3、定义子类型的构造函数
4、创建父类型的对象赋值给子类型的原型
5、将子类型原型的构造属性设置为子类型
6、给子类型原型添加方法
7、创建子类型的对象: 可以调用父类型的方法
案例演示:
// 定义父类型构造函数
function Persion(name){
this.name = name;
this.say = function(){
console.log('hahaha');
}
}
// 给父类型的原型添加方法
Persion.prototype.age = 15;
// 定义子类型的构造函数
function Per(name){
this.name = name;
}
// 创建父类型的对象赋值给子类型的原型
Per.prototype = new Persion();
// 将子类型原型的构造属性设置为子类型
Per.prototype.constructor = Per;
// 创建子类型的对象: 可以调用父类型的属性或者方法
let per = new Per('小红');
console.log(per.age);
优点描述:
实例可以访问子类属性、父类属性、父类原型属性
缺点描述:
原型对象的引用类型是共享的
不能向父类构造函数传参
借用构造函数继承
核心思想: 使用.call()和.apply()将父类构造函数引入子类函数,使用父类的构造函数来增强子类实例,等同于复制父类的实例给子类
基本做法:
1、定义父类型构造函数
2、定义子类型的构造函数
3、给子类型的原型添加方法
4、创建子类型的对象然后调用
案例演示:
定义父类型构造函数
function Persion(name){
this.name = name;
this.say = function(){
console.log('hahaha');
}
}
// 定义子类型的构造函数
function Per2(name){
// 在子类型中调用call方法继承自Persion
Persion.call(this,name);
}
// 给子类型的原型添加属性
Per2.prototype.age = 15;
let per2 = new Per2('小蕊');
console.log(per2);
优点描述:
只继承父类属性,不继承原型属性
解决原型链继承子实例不能向父类构造函数传参的缺点
可以继承多个构造函数的属性
子实例可以向父类构造函数传参
缺点描述:
只能继承父类的实例属性和方法,不能继承原型属性和方法
无法实现构造函数的复用,每个子类都有父类实例函数的副本,影响性能,代码会臃肿
组合继承(解决了原型链继承和借用构造函数继承的缺点)
核心思想: 原型链+借用构造函数的组合继承
基本做法:
1、利用原型链实现对父类原型对象的继承
2、利用借用父类构建函数初始化相同属性
案例演示
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.setName = function (name) {
this.name = name;
};
function Student(name, age, price) {
Person.call(this, name, age); // 为了得到父类型的实例属性和方法
this.price = price; // 添加子类型私有的属性
}
Student.prototype = new Person(); // 为了得到父类型的原型属性和方法
Student.prototype.constructor = Student; // 修正constructor属性指向
Student.prototype.setPrice = function (price) { // 添加子类型私有的方法
this.price = price;
};
var s = new Student("孙悟空", 24, 15000);
console.log(s.name, s.age, s.price);
s.setName("猪八戒");
s.setPrice(16000);
console.log(s.name, s.age, s.price);
缺点描述:
父类中的实例属性和方法既存在于子类的实例中,又存在于子类的原型中,不过仅是内存占用,因此,在使用子类创建实例对象时,其原型中会存在两份相同的属性和方法 。
原型式继承
核心思想: 基于当前构造函数创建对象的情况下,给当前构造函数继承父类的原型套上一层壳子,起到保护父类原型的作用,使其修改子类实例不会影响到父类原型
基本做法
用一个空构造函数做容器,把父类实例当作原型,返回该构造函数的实例
案例演示
function Persion(name, age) {
this.name = name;
this.age = age;
}
Persion.prototype.setName = function (name) {
this.name = name;
};
function content(obj){
//建立新的构造函数,把该构造函数的原型指向传入的实例,然后返回该构造函数的实例出去
//(把原型链延长了一段,起到保护父类原型的作用)
function F(){};
F.prototype = obj;
return new F();
}
let per3 = new Persion(); //实例
let per4 = content(per3);
console.log(per4);
寄生式继承
核心思想: 在原型式继承的继承上,将继承进行封装
案例演示
function Persion(name, age) {
this.name = name;
this.age = age;
}
Persion.prototype.setName = function (name) {
this.name = name;
};
function content(obj){
//建立新的构造函数,把该构造函数的原型指向传入的实例,然后返回该构造函数的实例出去
//(把原型链延长了一段,起到保护父类原型的作用)
function F(){};
F.prototype = obj;
return new F();
}
function subobject(obj){
return content(obj);
}
let per3 = new Persion(); //实例
let per4 = subobject(per3);
寄生式组合继承
核心思想: 组合继承的方式下,由于当前构造函数的原型是父类的实例,当前构造函数是借用构造函数继承属性方法,所以创建出来的实例上本身的属性方法和原型上的属性和方法是重合的,造成一定内存上的浪费。
基本方法:
寄生式继承(寄生就是把父类原型套到壳子里) + 父类实例属性剥离(借用构造函数) + 组合继承(继承父类原型)
案例演示
function Persion(name, age) {
this.name = name;
this.age = age;
}
Persion.prototype.setName = function (name) {
this.name = name;
};
function content(obj){
//建立新的构造函数,把该构造函数的原型指向传入的实例,然后返回该构造函数的实例出去
//(把原型链延长了一段,起到保护父类原型的作用)
function F(){};
F.prototype = obj;
return new F();
}
function Per4(name){
Persion.call(this,name);
}
let con = content(Persion.prototype);
Per4.prototype = con;
con.constructor = Per4;
let per3 = new Per4('小红'); //实例
console.log(per3);
ready和load区别
src :css外部文件 img 他们的加载是异步的,不对阻塞DOM的解析过程
- html下载完,DOM解析完
- 所有css外部文件,img下载完
通常是1先完,2后完 : window.onload 在 2 完成后触发 DOMContentLoaded 在 1 完成后触发
页面加载完成事件
-
window.onload
可以直接用window.onload = function(){} 或者 addEventListener -
window.DOMContentLoaded
只能用addEventListener
iframe框架
-
优点
- iframe能够原封不动的把嵌入的网页展示出来
- 如果有多个网页引用iframe,那么只需要修改iframe内容,可以实现调用的每一个页面的更改,快捷
- 网页为了统一风格,头部和版本都是一样的,就可以写成一个页面,用iframe嵌套,增加代码复用性
- 遇到加载缓慢的第三方内容如图标或者广告,可以用iframe解决
-
缺点
- 会产生很多页面,不易于管理
- iframe框架比较多的话,会出现上下左右滚动条,分散访问者注意力,体验感差
- 代码复杂,不利于SEO优化
- 很多设备无法完全显示框架,设备兼容差
- iframe页面会增加服务器的http请求,对于大型网站不可取
垃圾回收机制-标记清除法
- 每隔一段时间,会把所有标记设成false,从window出发,可触达就设置成true,不可触达的就回收掉