函数进阶
函数的定义方式
- 函数声明方式function关键字(命名函数)
// 1 自定义函数
function fn() {
};
- 函数表达式(匿名函数)
var fun = function() {
};
- 利用new function(‘参数1’,‘参数2’,‘参数3’)
// 3 利用new function('参数1','参数2','参数3')必须带引号
var f = new Function('a', 'b', 'console.log(a+b)');
f(1, 2); //3
注意:
- Function里面的参数必须是字符串格式
- 第三种方式执行效率低,也不方便书写,使用较少
- 所有函数都是Function的实例(对象)
- 函数也属于对象
原型三角关系
函数的调用方式
- 普通函数
function fn() {
console.log('人生巅峰');
}
fn();
fn.call();
- 对象的方法
var o = {
sayhi: function() {
console.log('人生的的巅峰');
}
}
o.sayhi();
- 构造函数
function Fn() {};
new Fn();
- 绑定事件函数
btn.onclick = function() {};
- 定时器函数
setTimeout(function() {}, 1000);
- 立即执行函数
(function() {
console.log(312);
})();
函数内this的指向
改变函数内部的this指向
常用的有bind()、call()、apply()三种方法
1.call()方法
call()方法调用一个对象,简单理解为调用函数的方式,但是它可以改变函数的this指向,call可以调用函数 可以改变函数内的this指向 call的主要作用可以实现继承
function Son(uname, age, sex) {
// 把Father这个构造函数调用过来了 并且将父亲中构造函数的this指向Son
Father.call(this, uname, age, sex);
}
2 apply()方法
简单理解为调用函数的方式,但是可以改变函数的this指向
- 也是调用函数 第二个可以改变函数内部this的指向
- 但是它的第二个参数必须是数组(伪数组也可以
- apply的主要应用 比如说我们可以用apply借助于数学内置对象求最大值和最小值
var a = [1, 23, 1324, 3454];
//此处求最大值不用改变this的指向
// var max = Math.max.apply(null, a);
// 尽量不要上面那样写 最好指向Math 把this的指向指向Math
var max = Math.max.apply(Math, a);
var min = Math.min.apply(Math, a);
console.log(max);
console.log(min);
3 bind()方法
bind()方法,但是能改变函数内部this指向
- bind()不会调用原来的函数,但是可以给它传送给一个变量从而进行调用
- 可以改变原来函数内部this的指向 返回的是原函数改变之后this之后产生的新函数
- 如果有的函数不需要立即调用但是又想改变这个函数内部的this指向此时用bind方法最合适
我们有一些按钮,当我们点击了之后,就禁用这个按钮,3秒钟之后开启这个按钮
var btns = document.querySelectorAll('button');
for (var i = 0; i < btns.length; i++) {
btns[i].onclick = function() {
this.disabled = true; //指向的是btn这个按钮
setTimeout(function() {
this.disabled = false;//不能写成btn[i],for循环会执行下去 ,但是这里有延时所以i的下标会改变
}.bind(this), 2000)
//这个this指向的是btn[i]
}
}
延时函数钟this指向的是window,所以要改变this的指向但是又要延时调用所以需要用到bind让延时函数中this指向按钮
call、apply、bind的总结
相同点:
都可以改变函数内部this指向
不同点:
- call和apply会调用函数,并且改变函数内部this指向
- call和apply的传递参数不一样,call传递参数为普通形式,apply传递的必须是数组形式
- bing不会调用函数,可以改变函数内部this指向
应用场景:
1.call经常做继承
2.apply经常跟数组有关系,比如借助于数学对象实现数组最大值最小值
3.bind不调用函数,想改变this指向,比如定时器内部的this指向
严格模式
ES5新增的严格模式,之前写的都是正常模式
严格模式中的变化
1.变量规定
变量名必须先声明再使用
不能随意删除定义好的变量
2.this的指向问题
3.函数变化
1.严格模式下函数里面的参数不允许有重名
2.不允许再非函数的代码块内声明函数 比如再if条件里面声明是不可以的
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
'use strict';
// 1变量名必须先声明再使用
// num = 10;
// console.log(num);
var num = 10;
console.log(num);
// delete num;
//2 不能随意删除定义好的变量
function fn() {
console.log(this);
}
fn();
//3 严格模式下全局函数的this指向的是window,此时this指向的是uundefineed,如果赋值就报错
function Star() {
this.sex = '男';
}
// Star();
//4 严格模式下,如果构造函数不加new调用,this不再指向window
var ldh = new Star();
//5 定时器里面的this指向window
console.log(ldh.sex);
setTimeout(function() {
console.log(this);
}, 2000)
//6 严格模式下函数里面的参数不允许有重名
function fn(a, a) {
console.log(a + a);
}
fn(1, 2);
// 7 不允许再非函数的代码块内声明函数 比如再if条件里面声明是不可以的
var flag = true;
if (flag) {
function fn() {
}
}
</script>
</body>
</html>
高阶函数
高阶函数是对其他函数进行操作的函数,它接收函数作为参数或者将函数作为返回值输出
此时fn为一个高阶函数,函数是一种数据类型,同样可以作为参数吗,传递给另外一参数使用,最典型的就是作为回调函数
闭包
1.变量作用域
2.闭包
有权访问另一个函数作用域中变量的函数
简单理解就是,一个作用域可以访问另一个函数内部的变量
闭包的作用:延伸了变量的作用范围
3.应用
实现点击小li输出索引号:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul class="nav">
<li>榴莲</li>
<li>臭豆腐</li>
<li>罐头</li>
<li>猪蹄</li>
</ul>
<script>
//闭包应用-点击li输出索引号
//1 我们可以利用动态添加属性
// var lis = document.querySelector('.nav').querySelectorAll('li');
// for (var i = 0; i < lis.length; i++) {
// lis[i].index = i;
// lis[i].onclick = function() {
// console.log(this.index);
// }
// }
//2 利用闭包的方式得到当前小li索引号
for (var i = 0; i < lis.length; i++) {
//通过立即执行函数利用for循环创建四个立即执行函数
// 立即执行函数称为小闭包,因为立即执行函数里面的任何一个函数都可以使用它的i这个变量
(function(i) {
// console.log(index);
lis[i].onclick = function() {
console.log(i);
}
})(i);
// 把i作为实参传递给index
}
</script>
</body>
</html>
实现点击小li输出索引号,利用的是每执行一次循环然后创建一个立即执行函数,立即执行函数也称为小闭包,里面的函数都能使用i这个变量,但是有个问题要注意的是内存泄漏。用动态创建的话直接创建点击事件,因为function这个函数是异步任务,执行完for循环这个同步任务在执行所以就会打印4
打印所有小li:
var lis = document.querySelector('.nav').querySelectorAll('li');
for (var i = 0; i < lis.length; i++) {
// 立即执行函数里面的任何函数都可以使用i这个变量
(function(i) {
setTimeout(function() {
console.log(lis[i].innerHTML);
}, 3000)
})(i);
// 三秒之后先打印第0个,在循环第1个
}
易错案例分析:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script>
// 思考题 1:
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function() {
return function() {
return this.name;
};
}
};
// 没有访问到局部变量所以没有闭包产生
console.log(object.getNameFunc()()); //The Window
var f = object.getNameFunc();
// 类似于
var f = function() {
return this.name;
}
f();
// 相当于匿名函数
// function(){this}() 立即执行函数this指向的是window
// 思考题 2:
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function() {
var that = this; //this指向函数调用者Object
return function() {
return that.name;
// 有闭包的产生,因为that使用了getNameFunc这个函数的局部变量,变量所在的函数getNameFunc就是闭包函数
};
}
};
console.log(object.getNameFunc()()); //My Object
var f = object.getNameFunc();
// 后面的小括号相当于调用
var f = function() {
return that.name;
};
f();
</script>
</body>
</html>
闭包是什么
闭包是一个函数(一个作用域可以访问另外一个函数的局部变量)
闭包的作用
延伸变量的作用域范围
递归
什么是递归?
如果一个函数内部可以调用其本身,那么这个函数称为递归函数,通俗的说,函数内部自己调用自己,递归函数的作用和循环效果一样,由于递归很容易发生栈溢出,所以必须加一个退出条件return
利用递归输出id号对应的数据:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var data = [{
id: 1,
name: '家电',
goods: [{
id: 11,
gname: '冰箱'
}, {
id: 12,
gname: '洗衣机'
}]
}, {
id: 2,
name: '服饰'
}];
//我们想要输入id号,就可以返回数据对象
function getID(json, id) {
var o = {};
//保存筛选完之后的数据
json.forEach(function(item) {
// console.log(item); //2个数组元素
if (item.id == id) {
o = item;
// 这个中间值返回给getID,否则getID没有返回值
return item;
//2 我们想要得到里层的数据11-12 可以利用递归函数
//里面有goods这个数组并且数组的长度不为0
} else if (item.goods && item.goods.length > 0) {
o = getID(item.goods, id);
}
});
return o;
}
console.log(getID(data, 1));
console.log(getID(dat 11));
</script>
</body>
</html>
一定要注意return item,返回一个中间结果给下面的getID函数
浅拷贝和深拷贝
浅拷贝:
1.浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用
2.深拷贝拷贝多层,每一级别的数据都会拷贝
3.Object.assign(target,…source)es6新增的方法可以实现浅拷贝
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 浅拷贝知识拷贝一层,更深层次对象级别的只拷贝引用
// 深拷贝拷贝多层,每一级别的数据都会拷贝
var obj = {
id: 1,
name: 'andy',
msg: {
age: 18
}
};
// 浅拷贝遇到对象级别更深层次的拷贝的话,只是把地址拷贝给了o,指向同一个数据,修改任意一个都会修改另外一个
var o = {};
// for (var k in obj) {
// //k是属性名 obj[k]是属性值
// o[k] = obj[k];
// }
// console.log(o);
// o.msg.age = 20;
// // 修改之后都会修改
// console.log(o);
console.log('-------------------');
Object.assign(o, obj);
console.log(o);
o.msg.age = 20;
// 修改之后都会修改
console.log(o);
//把obj拷贝给o 不用for循环那么麻烦
</script>
</body>
</html>
这个案例中是浅拷贝将obj拷贝给o,遇到对象更深层次的拷贝的话,只是把地址拷贝给了o,指向同一数据,修改任意一个数据都会导致改变
深拷贝:
将一个对象赋值给另一个对象时,如果遇到对象对其进行深层拷贝的时候,先将其中属性值赋值一份给新对象,然后再对其进行递归
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
//深拷贝拷贝多层,每一层的数据都会拷贝
var obj = {
id: 1,
name: 'andy',
msg: {
age: 18
},
color: ['blue', 'pink']
};
var o = {};
//封装函数
function deepcopy(newobj, oldobj) {
for (var k in oldobj) {
//判断属性值属于哪种数据类型
//1 获取属性值 oldobj[k]
var item = oldobj[k];
//2 判断这个值是不是数组 要先判断是不是数组不能先判断是不是对象 因为数组也是对象 这样才能避免吧数组当成对象
if (item instanceof Array) {
newobj[k] = [];
deepcopy(newobj[k], item);
} else if (item instanceof Object) {
newobj[k] = {};
deepcopy(newobj[k], item);
} else {
newobj[k] = item;
}
//3 判断这个值是否是对象
// 4 如果都不是就是简单数据类型
}
}
deepcopy(o, obj);
console.log(o);
var arr = [];
console.log(arr instanceof Object); //true
o.msg.age = 20;
//修改值后对原来的obj没有影响,只是修改了现在o,深拷贝是将msg里面的属性和方法复制了一份
console.log(o);
console.log(obj);
obj.msg.age = 123;
console.log(obj);
</script>
</body>
</html>
正则表达式
正则表达式概述
是用于匹配字符串中字符组合的模式,在js中,正则表达式也是对象
作用:
1.匹配
2.替换
3.提取
特点:
- 灵活性、逻辑性和功能性非常强
- 可以迅速地用极简单的方式达到字符串的复杂控制
- 实际开发中一般直接复制写好的正则表达式,但是要求会使用正则表达式并且根据实际情况修改正则表达式
正则表达式在js中的应用
创建正则表达式:
测试正则表达式:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
//正则表达式在js中的使用
//1 利用RegExp对象来创建正则表达式
var regexp = new RegExp(/123/);
console.log(regexp);
//2 利用字面量创建正则表达式
var rg = /123/;
//3 test用于检测字符串是否符合正则表达式要求的规范
console.log(rg.test('fgh'));
console.log(rg.test(1234));
</script>
</body>
</html>
正则表达式的组成:
边界符:
设定次数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
//量词是设定某个模式出现的次数
var reg = /^[a-z0-9]{6,16}$/;
//前面出现的模式可以出现6-16次
console.log(reg.test('dfghefrf67'));
console.log(reg.test('ghjg'));
</script>
</body>
</html>
用户名验证:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
span {
color: #ccc;
font-size: 14px;
}
.right {
color: green;
}
.error {
color: red;
}
</style>
</head>
<body>
<input type="text" class="uname"><span>请输入用户名</span>
<script>
//量词是设定某个模式出现的次数
var reg = /^[a-z0-9]{6,16}$/;
//前面出现的模式可以出现6-16次
// console.log(reg.test('dfghefrf67'));
// console.log(reg.test('ghjg'));
var span = document.querySelector('span');
var uname = document.querySelector('.uname');
uname.addEventListener('blur', function() {
if (reg.test(this.value)) {
console.log('正确');
span.innerHTML = '用户名输入正确';
span.className = 'right';
} else {
console.log('错误');
span.innerHTML = '用户名输入错误';
span.className = 'error';
}
})
</script>
</body>
</html>
括号总结:
- 中括号表示字符匹配,匹配方括号中的任意一个字符
- 方括号是量词符,表示重复次数
- 小括号表示优先级
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
//中括号 字符集合,匹配方括号中的任意字符
var reg=/[asd]/;//a||s||d
//大括号 量词符 里面表示重复次数
var reg=/^[das]{3,6}$/;
var reg=/^abc{3}$/;//只是把c重复了三次
//小括号 表示优先级
var reg=/^(abc){3}$/;//abc重复三次
</script>
</body>
</html>
预定义类
座机号验证
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
//座机号验证: 全国座机号码 两种格式 010-12345678 或者0530-1234567
//正则里面的或者是一个|
// var reg = /^\d{3}-\d{8}$/ | /^\d{4}-\d{7}$/;
var reg = /^\d{3,4}-\d{7,8}$/;
console.log(reg.test('010-12345678'));
</script>
</body>
</html>
品优购中的reg.js
window.onload = function() {
// var reg=/^(13[0-9]|14[5|7]|15[0|1|2|3|4|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$/;正则表达式测试给出的13几/14几/15几/18几
var regtel = /^1[3|4|5|7|8]\d{9}$/; //手机号的正则表达式
var regqq = /^[1-9]\d{4,}$/; //1-9第一位,后面的位数增加
var regnc = /^[\u4e00-\u9fa5]{2,8}$/;//昵称验证
var regmsg = /^\d{6}$/;//验证码
var regpwd = /^[a-zA-Z0-9_-]{6,16}$/;//密码验证
var tel = document.querySelector('#tel');
var qq = document.querySelector('#qq');
var nc = document.querySelector('#nc');
var msg = document.querySelector('#msg');
var pwd = document.querySelector('#pwd');
var surepwd = document.querySelector('#surepwd');
regexp(tel, regtel);
regexp(qq, regqq);
regexp(nc, regnc);
regexp(msg, regmsg);
regexp(pwd, regpwd);
function regexp(ele, reg) {
//ele表示哪个元素
ele.onblur = function() {
if (reg.test(this.value)) {
console.log('正确');
//把下一个兄弟span的类名改成success
this.nextElementSibling.className = 'success';
this.nextElementSibling.innerHTML = '<i class="success_icon"></i>恭喜您输入正确';
} else {
console.log('不正确');
this.nextElementSibling.className = 'error';
this.nextElementSibling.innerHTML = '<i class="error_icon"></i>格式不正确,请重新输入';
}
}
}
surepwd.onblur = function() {
if (this.value == pwd.value) {
this.nextElementSibling.className = 'success';
this.nextElementSibling.innerHTML = '<i class="success_icon"></i>恭喜您输入正确';
} else {
this.nextElementSibling.className = 'error';
this.nextElementSibling.innerHTML = '<i class="error_icon"></i>两次密码输入不一致';
}
}
}
替换
第一个参数可以是正则表达式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<textarea name="" id="msg" cols="30" rows="10"></textarea><button>提交</button>
<div></div>
<script>
//替换replace
var str = 'andy和red';
var newstr = str.replace('andy', 'gjhgjg');
console.log(newstr);
var text = document.querySelector('textarea');
var btn = document.querySelector('button');
var div = document.querySelector('div');
btn.onclick = function() {
div.innerHTML = text.value.replace(/激情/g, '**');
}
</script>
</body>
</html>
如果有更多的敏感词可以在激情后面加|