JavaScript基础
数据类型有 String
、 Number
、 Boolean
、 Null
、 Undefined
、 Object
十六进制:0x
开头
八进制: 0
开头
二进制:0b
开头 Chrome与firefox支持
Infinity
: 无穷
typeof
:是一个运算符,用于获取数据类型,其结果类型是一个字符串类型
instanceof
: 检查一个对象是否是一个类的实例
isNaN():用来判断是否是非数字类型
NaN: undefined and Number,undefined和数字相加结果是 NaN
数据类型强制转换
转换为String
- 调用
toString()
方法,null
和undefined
类型没有toString()方法 - 使用
String()
函数,null
和undefined
类型可以被转换成字符串。
对于Number和Boolean的转换,底层
调用的仍是toString()
方法,对于null直接转换成“null”;undefined也是如此。
转换为Number
一、使用Number()
函数
- 字符串转数字
- 如是
纯数字
,直接转换为数字 - 如含有
非数字
的内容,则转换为NaN
- 如是
空串
或全是空格
字符串,则转换为0
- 如是
- Boolean转数字
true 转为 1
,false 转为 0
-
null
转数字 值为0
-
undefined
转数字 值为NaN
二、字符串转为Number类型
-
parseInt()
可以将字符串中有效的【整数】
取出来转换为Number,小数部分不会提取。 -
parseFloat()
可以将字符串中有效的【小数】
取出来转换为Number - 对于非String类型数据,先将其转换为字符串,再使用该方式进行转换
<script>
var a = '100px';
a = parseInt(a,10);// 100 第二个参数10表示10进制
</script>
转换成Boolean类型
- 使用
Boolean()
函数
Boolean('') :false
Boolean(0) :false
Boolean(null) :false
Boolean(NaN) :false
Boolean(undefined) :false
运算符
- 对非Number类型的值进行运算,会将这些值转换为Number再做运算
- 任何值和NaN做运算都是NaN
- 任何值和字符串做加法运算,都会先转换为字符串,然后再拼接字符串
-
!
非,对非布尔值进行取反,会将其转换为布尔值,再取反 -
!!
对一个任意数据类型两次取反,将其转换为布尔值,结果就是真实正确的布尔值 -
&&
:false && alert('是否显示‘)
该语句不会弹出对话框。
两边的值都是true,则返回后面结果
两边的值有false,则返回靠前的false
var res = 5 && 6; // 6
res = 0 && 2;// 0
res = 2 && 0;// 0
res = NaN && 0; // NaN
res = 0 && NaN;// 0
比较运算
- 任何值和NaN作比较都是false
- 符合两侧的值都是字符串类型,不会转换为数字进行比较,会比较Unicode编码
- 比较字符编码,一位一位进行比较,两位一样,比较下一位
’abc‘ < 'bcd'
结果是 true
// 将10转换为了数字类型
console.log('10000' > + '10') // true
// js中使用Unicode编码 前缀必须是\u , 跟上四位编码
console.log("\u0054")
// html中使用Unicode编码 前缀必须是&# , 跟上四位编码的十进制,最后分号结尾
<h1>
☠
</h1>
-
==
比较两个值,类型不同会自动进行类型转换,再做比较
var a = 10;
a == 4 // false
"1" == 1 // true
true == "1" // true
null == 0 // false
undefined == null // true undefined衍生自null
NaN == NaN // false NaN不和任何值相等,包括他本身。 isNaN() 可以来判断是否是NaN
-
===
全等,判断两个 值是否全等,不会自动做类型转换,类型不同直接 返回false -
!==
不全等,判断是否不全等,不会自动做类型转换,类型不同直接返回true
对象
- 创建:
- 构造函数方式创建:
var obj = new Object()
- 字面量方式创建:
var obj = {}
- 工厂方法创建:批量创建对象
new Object()
工厂方法创建对象有一定局限性,类型都是Object,无法区分对象的具体类型 - 构造函数方式创建
- 构造函数方式创建:
// 工厂方法创建
function createObj(name){
var obj = new Object();
obj.name = name
return obj;
}
var a = createObj('A');
// 构造函数创建
function Person(name){
this.name = name;
}
var per = new Person('A');
- 添加属性:
obj.name = "张三"
- 读取属性:
const name = obj.name
|const name = obj['name']
- 修改属性:
obj.name = "李四"
|obj['name']= "李四"
- 删除属性:
delete obj.name
- 通过
[]
访问属性,[]
中可以传递变量。如var a = 'name'; obj[a] = '你好'
-
in
运算符可以检查一个对象中是否含有指定的属性。如’name‘ in obj
,返回true - 对象的
属性值
可以是任意类型,也可以是函数
,函数也可以称为对象的属性,这个函数就是对象的方法 - 枚举对象中的属性
for ... in
,原型中的属性也会遍历出来 -
hasOwnProperty()
检查对象自身是否有该属性,原型中的不会查找出来 - 查找一个对象的属性,如果没有回返回undefined
函数
- 创建
var fun = new Function('console.log('函数传递字符串类型的js代码')')
- 执行函数中的代码
fun()
函数也是一个对象,typeof
检查类型是function
- 声明函数
function 函数名 ([形参...]){
语句
}
函数内部可以声明函数,函数没有返回值,获取返回值是undefined
- 立即执行函数
// 匿名函数
(function(){
console.log('1');
})()
// 传参
(function(x, y){
console.log(x);
console.log(y);
})(100, 200)
立即执行函数只会执行一次
- 函数提升
函数在声明之前可以被调用,函数表达式不存在提升的现象。函数提升出现在相同作用域中。- 函数提升是把所有函数声明提升到当前作用域的最前面。
- 只提升函数的声明,不提升赋值
fn(); // 函数声明会提升到最前面,这里可以调用函数
function fn(){
console.log(1);
}
fun(); // var 声明的变量会提升,但不赋值,所以此时无法调用函数
var fun = function(){
console.log(2);
}
作用域
作用域规定了变量能够被访问的范围。作用域分为全局作用域
和局部作用域
,局部作用域分为函数作用域
和块作用域
- 全局作用域
- 直接编写在script标签中的js代码,都在全局作用域
- 全局作用域在页面打开时创建,关闭页面时销毁
- 在全局作用域中有一个全局对象
window
,可以直接使用 - 在全局作用域中声明的
变量
都会作为window对象的属性
保存 - 在全局作用域中声明的
函数
都会作为window对象的方法
保存 - 变量的声明提前:【只有】使用
var
声明的变量,会在所有代码执行之前被声明
,但不会赋值
。 - 使用函数声明形式创建的函数会在所有的代码执行之前就被创建。
- 使用函数表达式创建的函数不会在所有的代码执行之前创建。
- 全局作用域中创建的变量都是全局变量
- 将函数定义在全局作用域中,会污染全局作用域的命名空间,而且很不安全,原型可以解决这个问题
// 函数声明形式创建函数
// 会在所有代码执行之前创建函数,可以在这里之前调用fun函数
function fun(){}
// 函数表达式创建函数
// fun2变量会提前声明,但未赋值,执行到这里才会赋值。所有不能在之前调用fun2函数
var fun2 = function(){}
- 函数作用域
- 调用函数时创建函数作用域,函数执行完毕,函数作用域销毁
- 每调用一次函数就会创建一个新的函数作用域,他们之间是相互独立的。
- 函数作用域中可以访问到全局变量
- 函数作用域中声明的变量,在全局作用域中无法访问到
- 在函数作用域中操作变量,会先在自身作用域中寻找,再向上一级作用域中寻找
- 在
函数作用域
中也有声明提前
的特性。使用var
声明的变量,会在函数中所有的代码执行之前被声明。未赋值,undefined。 -
函数声明
也会在函数中所有代码执行之前声明 - 定义形参相当于在函数作用域中声明了变量
- 块作用域
被{}
包裹的区域就是代码块。代码块被声明的变量外部将有可能无法被访问。如for循环
。-
const
、let
声明的变量会产生块作用域,var
声明的变量不会产生块作用域。 - 不同代码块相互之间无法访问
- 推荐使用
let
或const
-
for(let i = 0; i< 10; i++){
// 块用域,外部无法访问变量 i
}
for(var i = 0; i< 10; i++){
// 块用域,外部可以访问变量 i
}
- 作用域链
作用域链式在底层变量的查找机制- 优先查找当前作用域
- 依次逐级查找父级作用域,直到全局作用域
this
解析器在调用函数每次都会向函数内部传递一个隐含的参数this
。this指向的是一个对象,这个对象称之为函数执行的上下文对象。根据函数的调用方式
不同,this会指向不同的对象。
- 以函数形式调用(
fun()
),this指向window - 以方法形式调用(
obj.fun()
),this指向方法调用的那个对象 - 以构造函数形式调用时,this就是新创建的那个对象
构造函数
构造函数调用需要使用new
,函数名
首字母大写
,这是与普通函数的区别。
使用同一个构造函数创建的对象,称之为一类对象,也将一个构造函数称之为类
- 构造函数执行流程:
- new时立刻创建一个
新的空对象
-
this
指向新的空对象
- 执行函数中的代码,修改this,
添加新属性
- 将新建的对象作为
返回值
- new时立刻创建一个
- 说明
实例化没有参数时可以省略小括号
构造函数无需return
静态方法中的this指向构造函数,静态方法和静态属性是添加在构造函数身上的,通过构造函数访问。
function MyClass(){
}
function MyClass(name, age){
this.name = name; // this指向构造函数内创建的对象
this.age = age;
// 不推荐
this.say = function(){} // 存在浪费内存问题,可以使用原型,实例对象都可以调用
}
// 静态属性和静态方法
MyClass.name = '';
MyClass.say = function say(){}
// 访问静态属性和方法
const name = `${MyClass.name}你好`
MyClass.say();
原型 prototype
- 创建的每一个函数,解析器都会向函数中添加一个属性
prototype
。这个属性对应着一个对象,这个对象就是原型对象
。 - 如果函数作为普通函数调用,prototype没有任何作用
- 当函数以构造函数调用时,它所创建的对象都会有一个隐含的属性指向该构造函数的原型对象,可以通过proto来访问该属性
- 原型对象相当于一个公共的区域,所有的实例都可以访问到这个原型对象,可以将对象中共有的属性和方法统一设置到原型对象中
- 当访问对象的一个属性或方法时,会先从对象自身中寻找,再去对象原型中寻找,直到原型的原型对象去找
- 原型对象也是对象,它也有原型;Object对象的原型没有原型。
function MyClass(){
}
// 向MyClass的原型中添加属性name
MyClass.prototype.name = 'A';
// 向MyClass的原型中添加方法setName
MyClass.prototype.setName = function setName(name){
console.log(name);
};
var mc = new MyClass();
console.log( mc.__proto__ == MyClass.prototype ); // true
image.png
- 声明一个构造函数
function User{
this.name = 'zs',
this.age = 19,
}
- 创建一个Use实例对象
const user = new User()
显式原型属性:User.prototype
只有函数才有显式原型属性
隐式原型属性:user.__proto__
实例对象拥有隐式原型属性
User.prototype
和user.__proto__
都指向原型对象
,通过显式原型属性User.prototype
操作原型对象,可以追加属性;通过实例对象隐式原型链
查找,可以访问到追加的属性。
// 给原型对象追加sex属性
User.prototype.sex = '男';
// 实例对象访问追加的sex属性
const sex = user.sex;
实例的隐式原型属性永远指向自己缔造者的原型对象
- 原型对象prototype的属性constructor
function MyClass(){
}
const res = MyClass.prototype.constructor === MyClass; // true
// 覆盖原有的原型对象,会失去constructor
MyClass.prototype = {
constructor: MyClass, // constructor指回构造函数,否则prototype就失去了constructor
function fn(){},
function fun(){},
function func(){},
}
对象原型
对象原型是实例对象
身上的属性,这是一个只读属性。
对象原型中也存在一个constructor,指向创建实例对象的构造函数
对象原型指向该构造函数的原型对象
function MyClass(){
}
const cls = new MyClass();
const res = MyClass.prototype === cls.__proto__; // true
原型链
原型链其实就是一个查找规则。使用一些属性和方法时,去查找这些属性和方法的规则。
原型链.jpg原型继承
通过原型对象来实现继承
// 父级对象
function Parent() {
this.a = 1;
this.b = 2;
}
// 子级对象
function Child(){
}
// 通过Child的原型对象来继承Parent对象
Child.prototype = new Parent();
// 指回原来的构造函数
Child.prototype.constructor = Child;
数组
数组是一个对象,typeof的结果是object。
读取不存在的索引,不会报错,而是返回undefined
- 创建数组对象
构造函数形式创建:var arr = new Array(10,20,11)
字面量形式创建:var arr = [];
- 数组属性
length
用于[设置]
或[获取]
数组长度
连续
的数组获取到的长度是数组的长度,也是元素的个数
非连续
的数组获取的长度是数组的长度,不是元素的个数
可以通过修改 length属性来删除一些元素,数组元素可以是任意类型的数据,包括函数、对象、数组。
-
数组方法
-
push()
向数组末尾添加一个或多个元素,返回数组新的长度 -
pop()
删除数组最后一个元素 -
unshift()
向数组开头添加一个或多个数组,返回新长度 -
shift()
删除数组第一个元素,并返回被删除的元素 -
forEach()
循环遍历,支持IE8以上,参数是函数 -
slice(start, end)
提取指定范围的元素 传递正负值参数截取顺序不一样 -
splice(start, delCount, newElement...)
删除、添加新元素,会影响原数组 -
contact()
连接两个或多个数组、元素,返回新数组 不会影响原数组 -
join()
将数组转换为一个字符串 不会影响原数组 -
reverse()
反转数组 会影响原数组 -
sort()
默认按照Unicode编码排序 会影响原数组 -
valueOf()
返回数组的原始值 -
map()
迭代数组,返回新数组,forEach不会返回新数组 -
filter()
过滤数组,返回新数组 -
reduce()
累计器 常用于求和 -
find()
查找符合条件的第一个元素 -
findIndex()
查找符合条件的第一个元素索引值 -
every()
检查所有元素是否符合指定条件 返回 true | false -
some()
检查数组是否有元素符合指定条件 有则返回 true -
fill()
将一个固定值替换数组的元素 ......
-
-
Array静态方法
-
from()
可迭代或类数组对象创建一个新的浅拷贝的数组实例 -
isArray()
确定传递的值是否是一个数组 -
of()
通过可变数量的参数创建一个新的 Array 实例,而不考虑参数的数量或类型
-
回调函数需要定义2个形参,第一个形参肯定在第二个形参的前边
如果返回一个大于0的值,元素会交换位置
如果返回一个小于0的值。则元素位置不变
如果返回一个0,则认为两个元素相等,不交换位置
升序:a - b
降序: b - a
// 排序
aar.sort((a, b)=>{
return a - b; // 升序
})
函数对象的方法
需要通过函数对象来调用,调用call()
和apply()
都会调用函数执行。
在调用这两个方法可以将一个对象指定为第一个参数,函数中的this
就是传递的这个参数
-
call(this指向的对象, 参数)
用于调用函数,可以改变this的指向 -
apply(this指向的对象, 数组参数)
与call一样,区别在于传参必须是一个数组 -
bind(this指向的对象, 参数)
语法与call相似,可以改变this指向,区别在于不会调用函数
<script>
function fun(){
alert(this); // this就是obj,如果没有传递obj ,this则是window
}
var obj = {};
// 使用 call 调用 fun 函数
fun.call(obj);
// 使用 apply 调用 fun 函数
fun.apply(obj);
// call 和 apply的方式调用 fun函数 并传参
function fun(a, b){
console.log(a, b) // 2, 3
}
var obj = {};
fun.call(obj, 2, 3);
fun.apply(obj, [2, 3]); // 以数组形式传参
// apply使用场景 求数组最大值
const max = Math.max(1, 2, 3);
const max = Math.max(...arr); // 展开运算符
const max = Math.max.apply(null, [1,2,3]); // this 指向 null
const max = Math.max.apply(Math, [1,2,3]); // this 指向 Math
// 3 bind() 不会调用函数,但可以改变this指向,返回值是一个新的函数
const f = fun.bind(obj); // 会拷贝一份fun 函数,但是this指向了obj
f(); // 调用新函数
// bind() 使用场景
btn.addEventListener('click',function(){
this.disabled = true; // this指向 btn
setTimeout(function(){
this.disabled = false;
}.bind(this), 2000); // this指向 btn
});
// 箭头函数
btn.addEventListener('click',function(){
this.disabled = true; // this指向 btn
setTimeout(()=>{
this.disabled = false; // this指向 btn
}, 2000);
});
</script>
- this指向问题
以函数形式调用,this是window
以方法调用时,this是调用方法的对象
以构造函数的形式调用时,this是新创建的那个对象
使用 call 和 apply 调用是,this是指定的那个对象
Date对象
<script>
// 当前时间
var d = new Date();
//创建指定时间
var d2 = new Data("12/21/2023 14:00:00");
// 获取一个月中的第几天 1 - 31
var date = d2.getDate();
// 获取一周中的第几天 0 - 6
var day = d2.getDay();
// 获取月份 0 - 11
var month = d2.getMonth();
// 获取年份
var year = d2.getFullYear();
// 获取小时 0 - 23
var hour = d2.getHours();
// 获取分钟 0 - 59
var minute = d2.getMinutes();
// 获取秒 0 - 59
var second = d2.getSeconds();
// 获取毫秒数 0 - 999
var mill = d2.getMilliseconds();
// 获取当前时间对象的时间戳
var time = d2.getTime();
// 获取当前时间戳
time = Date.now();
</script>
包装类
JS提供了三个包装类可以将基本数据类型转换为对象
String()
Number()
-
Boolean()
当对一些基本数据类型的值去调用属性和方法时,浏览器会临时使用包装类将其转换为对象,然后再调用对象的属性和方法
var num = 123;
// 先将值使用包装类转换为对象,再调用对象的toString()方法
num = num.toString();
var type = typeof num; // String
-
String()
在底层字符串是以字符数组的形式保存的
var str = "name";
// 字符串长度
str.length
// 根据索引获取指定字符
str.charAt(0)
// 根据索引获取指定字符的Unicode编码
str.charCodeAt(0)
// 根据字符编码获取字符
String.fromCharCode(72) // H
// 连接多个字符串
str.concat("张","三")
// 检索字符串是否含有指定内容,返回第一次出现的索引位置。第二个参数是指定查找的开始位置
str.indexOf('e',1); // 3
// 从末尾开始查找 返回的index是从前开始数,第二个参数指定从前开始查找,找到指定位置结束
str.lastIndexOf(’e‘,3)
// 截取指定范围的字符串 参数可以为负数
str.slice(0,2)
// 截取字符串 参数不可为负数
str.substring(0, 2)
// 截取字符串 1参:开始位置 2参:长度
str.substr(0, 2)
// 拆分字符串
str.split()
// 转小写 不影响源数据
str.toLowerCase()
// 转大写 不影响源数据
str.toUpperCase()
正则表达式
-
作用
- 检查字符串是否符合规则
- 提取符合规则的字符串
-
正则对象的方法
-
test()
查找指定值,返回布尔值 -
exec()
查找指定值,返回找到的值,并确定其位置 -
compile()
编译正则表达式
-
-
支持正则的String对象的方法
-
search()
查找与正则匹配的值只会查找第一个,即便设置全局匹配也无效
-
match()
查找一个或多个与正则的匹配 返回数组 -
replace()
替换与正则匹配的子串 -
split()
字符串分割为数组不指定全局匹配也会全局进行拆分
-
-
创建
// 函数形式创建
/*
*语法:
* RegExp(’正则表达式‘, '匹配模式')
* 匹配模式:
* i: 忽略大小写
* g: 全局匹配
* 可以为一个正则设置多种匹配模式 顺序无关 /a/ig 或 /a/gi
*/
var reg = new RegExp('a','i');
/*
* 字面量创建
* 语法:
* /正则表达式/匹配模式
*/
reg = /a/i; // 匹配a 忽略大小写
// 或:|
reg = /a|b|c/; // 匹配a或b或c
/*
* 或:[]
* [a-z] 任意小写字母
* [A-Z] 任意大写字母
* [A-z] 任意字母
* [0-9] 任意数字
*/
reg = /[a-z]/
// 检查是否含有 abc 或 adc 或 aec
reg = /a[bde]c/
/*
* [^ ] 排除
*/
reg = /[^ab]/; // 除了a, b都满足
/*
* 量词:通过量词设置一个内容出现的次数
* 只对前面的一个内容起作用
* 次数:{n}, {m, n},{m,}
* 至少1个: + 相当于{1,}
* 0个或多个: * 相当于{0,}
* 0个或1个: ? 相当于{0,1}
*/
reg = /a{3}/; // 匹配连续出现3次的a
reg = /(ab){3}/; // 匹配连续出现3次的ab
reg = /ab{1,3}c/; // 出现1 到 3次 的b
reg = /ab{3,}c/; // 3次以上
reg = /ab+c/; // 至少一个b
reg = /ab*c/; // 0个或多个b
/*
* 检查一个字符是否以 xx 开头
* ^ 表示开头
* $ 表示结尾
*/
reg = /^a/; // 匹配开头的a
reg = /a$/; // 匹配结尾的a
// 如果同时使用 ^和$,则要求字符串必须完全符合正则
reg = /^a$/; // 匹配开头的a,同时也是结尾的a
reg = /^a|a$/; // 以a开头或者以a结尾
//手机号正则
reg = /^1[3-9][0-9]{9}$/;
/*
* . 表示任意字符
* \ 转义字符
* \\ 表示 \
* 注意:使用构造函数创建正则时,他的参数是一个字符串,而\是字符串中的转义字符,
* 如果使用\则需要使用\\来代替
*/
reg = /./; // 匹配任意字符
reg = /\./; // 匹配 .
reg = new RegExp("\\."); // 需要写2个\
/*
* \w 任意字母、数字、_ [A-z0-9_]
* \W 除了字母、数字、_ [^A-z0-9_]
* \d 任意的数字 [0-9]
* \D 除了数字 [^0-9]
* \s 空格
* \S 除了空格
* \b 单词边界
* \B 除了单词边界
*/
// 检查是否有child单词
reg = /\bchild\b/;
str = str.replace(/^\s*/,""); // 去除开头的空格
str = str.replace(/\s*$/,""); // 去除末尾的空格
str = str.replace(/^\s*|\s*$/g,""); // 去除开头或结尾的空格
/*
* 电子邮件
* hello . world @ abc . com.cn
* 任意字母数字下划线 . 任意字母数字下划线 @ 任意字母数字 . 任意字母
* \w{3,} (\.\w+)* @ [A-z0-9]+ (\.[A-z]{2,5}){1,2}
*/
reg = /^\w{3,}(\.\w+)*@[A-z0-9]+(\.[A-z]{2,5}){1,2}$/;
垃圾回收机制
全局变量在页面关闭时回收,局部变量使用完毕自动回收
-
内存生命周期
- 内存分配
- 内存使用
- 内存回收
-
内存泄漏
程序中分配的内存因某种原因程序未释放
或无法释放
叫做内存泄漏 -
垃圾回收算法
堆栈空间分配区别- 堆:一般由
程序员分配释放
,如不释放,则由垃圾回收机制回收
,复杂数据放到堆中。 - 栈:由
操作系统自动分配释放
函数的参数值、局部变量等,基本数据类型放到栈中。
浏览器垃圾回收机制算法
- 引用计数法
IE采用的算法,定义‘内存不再使用’。就是看一个对象是否有指向它的引用,没有就回收。
算法:
(1)跟踪被引用的次数
(2)被引用一次就记录1次,多次引用就累加
(3)减少一个引用,就减一次
(4)引用次数为0,则释放内存
缺点:
嵌套引用,两个对象相互引用,无法进行回收,导致内存泄漏。 - 标记清除法
现代浏览器都是用该算法。
算法:
(1)将不再使用的对象标记为无法达到的对象
(2)从根部出发定时扫描内存中的对象,从根部可达就是还需要使用的对象,不可达对象标记为不再使用的对象,稍后进行回收。
- 堆:一般由
闭包
闭包 = 内层函数 + 外层函数的变量
// 1. 基本形式
// 外层函数
function fun(){
let a = 1; // 外层函数的变量
// 内层函数
function fun2(){
console.log(a); // 内层函数使用了外部函数的变量
}
fun2(); // 必须要调用,外部无法访问 fun2 函数
}
fun(); // 必须要调用
// 2. 常见形式
function fun(){
let a = 1;
// 内层函数
function fun2(){
console.log(a);
}
return fun2; // 返回一个函数
}
const fn = fun(); // 接收这个函数
fn(); // 调用这个函数
作用:支持外部访问函数内部的变量
应用:实现数据的私有
闭包中外层函数的变量不会被回收,应该被回收但是没有被回收,闭包存在内存泄漏的风险。
函数参数
函数参数分为动态参数和剩余参数
动态参数
arguments
arguments参数仅仅存在于函数中。
调用函数时,浏览器都会传递两个隐含的参数,一个是this
,一个是arguments
arguments是一个类数组对象
,但并不是数组,用来封装实参。
Array.isArray(arguments) : false
arguments instanceof Array : false
在调用函数传递的实参
都会在arguments中保存
arguments的length就是实参的长度,通过下标索引来获取实参
不定义形参,也可以通过arguments来获取实参
属性callee
对应一个函数对象,就是当前正在执行的函数的对象
<script>
function fun(a, b){
console.log(arguments instanceof Array);
console.log(Array.isArray(arguments));
console.log(arguments[0]);
console.log(arguments.length);
console.log(arguments.callee);
}
</script>
剩余参数
将不定数的参数表示为一个数组
语法符号:...
放于最末函数形参之前,用于获取多余的实参,获取到的实参是个真数组
function fun(a, b, ...num){
console.log(num); // 真数组
// 展开运算符
// 求最值
const max = Math.max(...num);
const min = Math.min(...num);
// 合并数组
const arr1 = [1,2];
const arr2 = [3,4];
const arr = [...arr1, ...arr2];
}
Object常用静态方法
Object.keys()
:获取对象所有属性名
Object.values()
:获取对象所有属性值
Object.assign()
:对象拷贝,应用场景-给对象添加属性
const obj = {name: 'A', age: 10}
// 获取属性名
const arr = Object.keys(obj)
// 获取属性值
const values = Object.values(obj)
// 拷贝对象
const obj2 = {};
Object.assign(obj2, obj);
// 给obj对象添加gender属性
Object.assign(obj, {gender: 1});
深浅拷贝
- 浅拷贝
浅拷贝拷贝的是地址,只拷贝最外面一层,嵌套层级不会拷贝- 拷贝对象
Object.assign() 或展开运算符 - 拷贝数组
Array.prototype.concat或展开运算符
- 拷贝对象
const obj = {
name:' ',
bean: {
age: 10,
}
}
const o = {};
Object.assign(o, obj);// 将bean对象的地址拷贝给o对象,两个对象的bean都是同一个对象
o.bean.age = 100; // 修改拷贝后的对象属性值
o.name = 'red';
console.log(obj.bean.age); // 100
console.log(obj.name); // ''
- 深拷贝
- 通过函数递归实现
- lodash/cloneDeep
- JSON.stringify()
// 1 递归深拷贝
function deepCopy(newObj, oldObj){
for(let k in oldObj){
if(oldObj[k] instanceof Array){
newObj[k] = [];
deepCopy(newObj[k], oldObj[k]);
}else if(oldObj[k] instanceof Object){
newObj[k] = {};
deepCopy(newObj[k], oldObj[k]);
}else{
newObj[k] = oldObj[k];
}
}
}
// 2 lodash / cloneDeep
const newObj = _.cloneDeep(oldObj)
// 3 JSON. stringify()
const newObj = JSON.parse(JSON.stringify(oldObj));
异常处理
-
抛异常
throw
-
捕获异常
try...catch
// 抛出异常
function fn(a){
if(!a){
throw new Error('参数有误');
}
return a.toString();
}
// 捕获异常
try{
...
}catch(e){
throw new Error('捕获到异常');
}finally{
// finally 这里一定会执行
}
防抖与节流
防抖:单位时间内触发重新计时,单位时间到了才会执行
节流:单位时间内只执行一次
-
lodash/debounce(func, [wait=0], [option=])
防抖 -
lodash/throttle(func, [wait=0], [option=])
节流
延迟一段时间再调用函数,底层是利用定时器来实现
// setTimeout定时器实现防抖
function debounce(fn, t){
let timer = null;
// 需要返回一个函数,作为事件的处理函数
return function(){
if(timer) clearTimeout(timer);
timer = setTimeout(function(){
fn();
}, t)
}
}
// setTimeout实现节流
function throttle(fn, t){
let timer = null;
return function(){
if(!timer){
timer = setTimeout(function(){
fn();
timer = null; // 在定时器回调函数中是无法清除定时
}, t);
}
}
}