JavaScript 函数进阶
这篇文章主要讲了ES5新增的方法,函数this指向,严格模式,闭包和浅拷贝和深拷贝的知识
如有错误,欢迎前来指正!
ES5 新增方法
- 下面记了数组方法, 字符串方法, 对象方法
数组方法
- forEach 方法:(遍历方法)
// 括号里面是回调函数
array.forEach(function(currentValue, index, arr))
-
currentValue:数组当前项的值
-
index:数组当前项的索引
-
arr:数组对象本身
-
代码示例:
var arr = [1,2,3];
arr.forEach(function(value, index, array) {
console.log(value);
})
- fiter 方法:(筛选数组)
- 返回的是一个新数组
array.filter(function(currentValue, index, arr))
- 回调函数里面的参数含义和上面的是一样的
- 和上面的函数的区别就是他返回的是一个新数组
arr.filter(function(value, index, array) {
return value >= 20;
})
- some 方法:(数组中元素是否满足指定条件)
- 返回值是一个布尔值
array.some(function(currentValue, index, arr))
- 和上面的方法的区别是这个方法返回的是一个布尔值
- 这个方法是惰性的,找到第一个满足条件就不再往下查找 (需要
return true
才能终止遍历)
字符串方法
- trim 方法:删除字符串两端的空白字符
- trim 方法不影响字符串本身,会返回一个新字符串
var str = ' 菜鸟小铭 ';
var str1 = str.trim();
console.log(str1);
// 输出 菜鸟小铭
对象方法
Object.keys
方法:用于获取对象所有属性名 (和 for in 很像)- 返回一个数组
var obj = {
id: 1,
name: "noobMing"
}
var arr = Object.keys(obj);
Object.defineProperty()
方法:添加新属性或者修改原有属性
Object.defineProperty(obj, prop, descriptor)
-
obj:必须 目标对象
-
prop:必须 添加或者修改的属性的名字
-
descriptor:必须 添加属性的说明 (以对象格式书写)
-
descriptor 的说明:
- value 设置属性的值 (默认为 undefined)
- writable 值是否可以重写 (true/false 默认值为false)
- enumerable 目标属性是否可以被枚举 (true/false 默认值为false)
- configurable 目标属性是否可以被删除或是否可以再次修改特性 (true/false 默认值为false)
-
代码示例:
var obj = {
name: 'noobMing',
age: 18
}
Object.defineProperty(obj,'age', {
value: '20',
// 不允许修改
writable: false,
// 不允许遍历出来
enumerable: false,
// 不允许这个属性被删除,也不允许再次修改第三个参数的特性
configurable: false
})
函数进阶
函数的定义
- 可以使用 function 直接定义函数, 也可以使用函数表达式定义匿名函数, 使用
new Function('参数1', '参数2', '方法体')
来定义函数
// 1. 直接命名函数
function fn() {}
// 2. 匿名函数
var fun = function() {}
// 3. 使用构造函数来定义函数
var f = new Function('name','console.log("hello"+name)');
- 所有函数都是 Function 的实例对象
立即执行函数
- 定义一个匿名函数,并且自动调用的就叫立即执行函数
- 立即执行函数书写方式:
(function(){console.log("我是立即执行函数")})()
- 在JS引擎中规定,如果 function 出现在行首,一律解析成语句。所以我们只需要让 function 不在行首就可以了 (例如在function前面加上-*/之类的符号)
- 立即执行函数会形成一个单独的作用域,我们可以封装一些临时变量或者局部变量,避免污染全局变量
- 参考资料:什么是立即执行函数,它有什么作用?
函数中 this 指向问题
- 函数中 this 指向:
函数类型 | this的指向 |
---|---|
普通函数(定义在全局的函数) | window |
定义在对象中的方法 | 指向该对象 |
构造函数 | 指向创建的实例对象 (包括 prototype 原型对象指向的也是实例对象) |
绑定事件的函数 | 指向绑定事件对象 (例如 button 等) |
定时器函数 | window |
立即执行函数 | window |
- this 在函数中永远指向最后调用它的对象,所以不能只看这个函数的定义,还需要看这个函数的执行时指向的是哪里
- 例如:
var o = {
a: 10;
fn: function() {
console.log(this);
}
}
// 指向 o
o.fn();
// 这里指向的是 window
var j = o.fn;
j();
- 第一个是 o 对象直接调用这个函数,所以指向的是 o
- 第二个的完整写法是
window.j()
,this 指向最后调用它的对象也就是 window
改变函数中的 this 指向
- 可以通过继承时候的用的 call 函数改变
函数名.call(this要指向的地方, 参数);
- call 可以调用函数,也可以改变函数中 this 的指向
apply 方法改变 this 指向
- apply 也可以调用和改变函数中 this 指向
fn.apply(thisArg,[argArray]);
- thisArg:在函数运行时的 this 值
- argArray:传递的值,但必须包含在数组里
- 函数接收的部分可以不是一个数组,但参数个数要对的上
- 例如利用 apply 来计算数组中最大的元素
var arr = [1, 2, 3];
// 这里最好把指向改为函数的调用者 也就是 Math
// var max = Math.max.apply(null, arr);
var max = Math.max.apply(Math, arr);
console.log(max);
// 输出 3
- apply 应用:可以让数组作为参数去执行一些函数 (例如 max,min)
bind 方法改变函数中 this 指向
- 这个在实际运用中用的比较多
- bind 也可以改变 this 指向,但是不会调用这个函数
fun.bind(thisArg, arg1, arg2);
-
thisArg:在函数运行时的 this 值
-
arg:传递的值
-
返回改变 this 之后的原函数的拷贝
-
代码示例:
// 新建一个对象 o
var o = {
name: "菜鸟小铭"
};
// 新建一个函数 fn
function fn(a,b) {
console.log(this);
console.log(a+b);
};
var f = fn.bind(o, 1, 2);
// 因为 bind 不立即执行,所以需要自己调用
// 即使在此处修改参数也不会改变相加的值
f(3,4);
// 输出对象 o 和 3
- 三个函数的相同点和不同点:
严格模式
- 严格模式清除了一些不合理的地方 (毕竟是一个星期就创造出的语言),并且新增了一些保留字
- 有兼容性问题:IE10 以上 (旧版本浏览器会忽略)
开启严格模式
- 严格模式可以应用到整个脚本或者个别函数中
- 为脚本开启严格模式:
// 下面的代码就会按照严格模式运行
'use strict';
- 为函数开启严格模式:
// 为这个函数开启严格模式
function fn() {
'use strict';
}
严格模式的变化
- 严格模式下变量必须先用 var 声明在使用 (非严格模式下变量没有声明就赋值,默认是全局变量)
'use strict';
// 报错,变量未定义
num = 10;
// 正确写法
var num = 10;
- 不能在严格模式下删除已经定义好的变量
var num = 10;
delete num;
// 报错,不能删除已经好的变量
- 严格模式下全局函数里的 this 指向 undefined
function() {
// undefined
console.log(this);
}
-
构造函数不加 new 调用,this 就会报错 (因为全局函数指向的是 undefined,不能给 undefined 加属性)
-
严格模式下函数不能有重名的参数 (好像没有人会这么用吧)
-
不允许在 if for 等非函数代码块里面声明函数
高阶函数
- 接受函数作为参数 (回调函数) 或者 将函数作为返回值输出 都叫高阶函数
- 在JavaScript中,函数都是以对象的形式储存的,所以可以让函数作为参数来使用
闭包
- 闭包是一个作用域可以访问另外一个函数的局部变量
- 最简单的闭包:
function fn() {
var num = 10;
function fun() {
console.log(num);
}
fun();
}
// fun 访问 fn 作用域里的局部变量,就产生了闭包 (Closure)
闭包的作用
- 闭包延伸了局部变量的范围
- 利用 return 来扩大变量作用范围
function fn() {
var num = 10;
return function() {
console.log(num);
}
}
var f = fn();
f();
// 输出 10 (在全局作用域下可以访问到 fn 内部的变量)
闭包的应用
- 给一堆 li 绑定点击事件,点击哪个 li 获取哪个 li 的索引号
- 原来的做法:先给 li 设置自定义属性里面写上标号,然后再点击的时候获取标号
- 现在的做法:立即执行函数生成了四个作用域,每一个作用域里的 onclick 事件都对应这不同的标号 (i 值不同)
var lis = document.querySelectorAll('li');
for (var i = 0; i < lis.length; i++) {
(function (i) {
lis[i].onclick = function () { console.log(i);}
})(i);
}
递归函数
- 递归:自己调用自己
- 先写一个简单的阶乘
function factorial(num) {
if (num <= 1) return 1;
else {
return factorial(--num) * num;
}
}
console.log(factorial(10));
// 输出 362880
- 一定要小心栈溢出 (stack overflow)
浅拷贝和深拷贝
-
浅拷贝只是拷贝一层,更深层次 对象级别 的只拷贝引用
-
深拷贝拷贝多层,每一级别的数据都会拷贝
-
两者的区别:
- 浅拷贝的对象级别成员拷贝时只传递一个地址,导致两个拷贝前后一个改变另一个也跟着改变
- 深拷贝是两个对象成员指向了两个不同的地址,所以即使改变也不会影响其他地方
浅拷贝
var source = {
name:"菜鸟小铭",
msg: {
data: "用代码构建世界!"
}
};
var copy = {};
// 浅拷贝
for(var k in obj) {
// k 是属性名,obj[k] 是属性名
copy[k] = source[k];
}
console.log(copy);
// 输出的内容和 source 是一摸一样的
// 浅拷贝的对象内容指向的是一个地址
// 但是我们现在改变 copy 中的 data,source 也跟着一起改变
copy.msg.data = "Never Settle";
// 这时发现两个内容一起改变
- ES6 新增的浅拷贝语法糖:
语法糖:语法糖就是一种便捷写法,使代码更简洁流畅,代码更语义自然
Object.assign(target,source);
- target:要拷贝给谁
- source:要拷贝哪个对象
深拷贝
- 深拷贝使用递归函数来把所有内容都给放进去
function deepcopy(newobj, oldobj){
for(var k in oldob3) {
// 判断我们的属性值属于那种数据类型
// 1.获取属性值 oldobj[k]
var item= oldobj[k];
// 2.判断这个值是否是数组
if(item instanceof Array) {
newobj[k] = [];
deepCopy(newobj[k],item);
} else if (item instanceof object) {
// 3.判断这个值是否是对象
newobj[k]={};
deepCopy(newobj[k],item);
} else {
//4.属于简单数据类型
newobj[k]= item;
}
}
}