正则
认识正则
初识
正则表达式 又叫‘规则表达式’
由我们来书写规则,专门来检测字符串是否符合规则
最主要的应用场景—表单验证
如何创建正则
想要制定规则,必须按人家的要求的方式来制定
把一些字母和符号写在//
中间的东西,叫做正则,比如/abcdefg/
创建正则分为两种方式 字面量和构造函数
- 字面量
let reg = /abcdefg/
- 构造函数
let reg2 = new RegExp("abcdefg")
// 用构造函数和字面量创建的正则,得到的结果是一样的
常用方法
用来检测和捕获字符串中的内容
test
test() 检测
语法:正则表达式.test(“你要检测的字符串”);
返回值:一个布尔值
如果该字符串符合规则,那就是true,反之false
let reg = /abcd/;
// 意义:字符串中必须包含 abcd 字符片段
let res = reg.test("kasjgfkajsbkagjkjfgaksjfgkajf123");
console.log(res);// false
let res2 = reg.test("asdadbweriqhdqwhdoihec")
console.log(res2); // false
let res3 = reg.test("sjdkjadgabcdqwdhqweh123");
console.log(res3);// true
exec
// 留着
元字符-基本字符
-
元字符
所有的文本内容
特殊符号,用符号表示一类内容 -
标识符
书写在正则外面的,用来修饰正则表达式
基本元字符
\d
表示一位数字
let reg = /\d/;
// 意义:字符串中至少包含了一位数字(0-9)
let res = reg.test("asdj1123123123hvh!@#$%^()$");
console.log(res); // true;
\D
表示一位 非数字
let reg = /\D/;
// 意义:字符串中至少包含了一位非数字内容
let res = reg.test("123@12我456789");
console.log(res); // true
\s
表示一位 空白内容(空格/缩进)
let reg = /\s/;
// 意义:字符串中至少包含了一位 空白内容
let res = reg.test("ab cd");
console.log(res); // true
let res2 = reg.test("abcd");
console.log(res2); // false
\S
表示一位 非空白内容
let reg = /\S/;
// 意义:字符串中至少包含一位 非空白内容
console.log(reg.test(" 1 ")); // true
console.log(reg.test(" ")); // false
\w
表示一位 数字(0-9)字母(a-zA-Z)或下划线_
中的任意一个
let reg = /\w/;
console.log(reg.test("~!@#$%^&**()"));// false
console.log(reg.test("_"));true
console.log(reg.test("B"));
let reg2 = /ABC/;
console.log(reg2.test("abcdefg"));
\W
表示一位 非数字字母下划线
let reg = /\W/;
console.log(reg.test("~!@#$%^&*()"));// true
console.log(reg.test("_")); // false
.
表示一位不是 \n 的字符
let reg = /./;
console.log(reg.test("\n\n\n\n\n"));//false
console.log(reg.test("abcd\n"));//true
// \n表示换行字符
document.body.innerText = "你\n好\n啊\nZ\nD"
\
表示转义字符
- 把有意义的符号转成没有意义的普通文本
let reg = /\d\.\d/;
console.log(reg.test("3.1")); // true
console.log(reg.test("3a1"));// 一下都为false
console.log(reg.test("321"));
console.log(reg.test("3!1"));
console.log(reg.test("3_1"));
// 本身 . 是有特殊意义的 和\一起写
// \就可以把 . 转成普通文本
- 把没有意义的文本转成有意义的符号
d 没有意义表示字符 d,但是加了\之后,\d
表示一位数字
元字符–边界符号
^
:表示开头$
:表示结尾
注意:当^和$结尾一起使用的时候,表示的是从开头到结尾
^
let reg = /\d/; // 表示至少一位数字
let reg2 = /^\d/;// 表示字符串中必须以一个数字开头
console.log(reg.test("abc1d"));// true
console.log(reg2.test("abc1d")); // false
console.log(reg2.test("1abc1d")); // true
$
let reg = /\d/;// 表示至少一位数字
let reg2 = /\d$/; // 表示字符串中以一个数字结尾
console.log(reg.test("abd12d;")); // true
console.log(reg2.test("1abcd"));// false
console.log(reg2.test("1abcd2"));// true
小坑
let reg = /^\d$/;
// 表示:从开头到结尾只有一位数字
console.log(reg.test("1a2")); // fasle
console.log(reg.test("1")); // true
console.log(reg.test("12")); // false
console.log(reg.test("123"));// false
console.log(reg.test("11"));// false
let reg2 = /^\d.\d$/;
// 表示:以一个数字开头,之后一个除换行以外的内容,再以一个数字结尾
console.log(reg2.test("1a1")); // true
console.log(reg2.test("11")); // false
console.log(reg2.test("a1a")); // false
console.log(reg2.test("a11")); // false
console.log(reg2.test("11a")); // false
console.log(reg2.test("1111")); // false
console.log(reg2.test("111")); // true
元字符-限定符号
限定内容出现的次数
注意:一个限定符号只能修饰符号前面的一个内容的出现次数
*
表示出现0~多次
前面的内容重复至少0次,也就是0~正无穷次
let reg = /^\d*$/;
// 表示字符串 从开头到结尾 数字出现0~无穷次
// 空字符串
// 只要有字符串 就必须开头到结尾都为数字
console.log(reg.test("")); // true;
console.log(reg.test("$")); // false
console.log(reg.test("!")); // false
console.log(reg.test("1")); // true
console.log(reg.test("12")); // true
console.log(reg.test("123")); // true
console.log(reg.test("1234")); // true
let reg2 = /\d*/;
// 在字符串中数字可以出现0~∞
console.log(reg2.test(""));// 都为true
console.log(reg2.test("abc"));
console.log(reg2.test("abc123"));
+
表示出现1~多次
let reg = /^\d+$/;
// 表示字符串从开头到结尾必须为数字,且至少出现一次
console.log(reg.test("")); // false
console.log(reg.test("1")); // true
console.log(reg.test("12")); // true
console.log(reg.test("123")); // true
console.log(reg.test("1234")); // true
console.log(reg.test("1234a")); // true
console.log(reg.test("12a34")); // true
let reg2 = /\d+/;
// 表示字符串中 数字至少出现一次
console.log(reg2.test("abc"));// false
console.log(reg2.test("abc1"));// true
console.log(reg2.test("abc123"));// true
?
表示出现 0~1次
let reg = /^\d?$/;
console.log(reg.test(""));// true
console.log(reg.test("abc1"));// false
console.log(reg.test("12"));// false
console.log(reg.test("123"));// false
console.log(reg.test("1234"));// false
console.log(reg.test("12345"));// false
let reg2 = /\d?/;
// 表示字符串中 数字只能出现0次或1次
console.log(reg2.test("")); // true
console.log(reg2.test("abc1")); // true
console.log(reg2.test("abc")); // true
console.log(reg2.test("abc123")); // false
{n}表示指定出现n次
let reg = /^\d{2}$/;
// 表示字符串从开头到结尾,数字出现两次
console.log(reg.test(""));// false
console.log(reg.test("1"));// false
console.log(reg.test("12"));// true
console.log(reg.test("123"));// false
let reg2 = /\d{3}/;
// 表示字符串中 数字出先3次
console.log(reg2.test("abc")); // false
console.log(reg2.test("abc1"));// false
console.log(reg2.test("abc123"));// true
{n,} 表示至少出现n次
=》{0,} 等价于*
=》{1,} 等价于+
let reg = /^\d{2,}$/;
// 表示字符串从开头到结尾,数字至少出现2次
console.log(reg.test(""));
console.log(reg.test("1"));
console.log(reg.test("12"));
console.log(reg.test("123"));
console.log(reg.test("1234"));
console.log(reg.test("12345"));
let reg2 = /\d{2,}/;
// 表示字符串中数字出现2~无穷次
console.log(reg2.test("abc"));
console.log(reg2.test("abc1"));
console.log(reg2.test("abc12"));
console.log(reg2.test("1abc12"));
{n,m} 出现 n~m次
=》{0,1} 等价于 ?
let reg = /^\d{3,5}$/;
// 表示字符串从开头到结尾 数字出现3~5次
console.log(reg.test(""));
console.log(reg.test("1"));
console.log(reg.test("12"));
console.log(reg.test("123"));
console.log(reg.test("1234"));
console.log(reg.test("12345"));
console.log(reg.test("123456"));
let reg2 = /\d{3,5}/
// 表示字符串中,数字要出现3~5
console.log(reg2.test("abc"));
console.log(reg2.test("abc1"));
console.log(reg2.test("abc123"));
console.log(reg2.test("abc12345"));
小坑
let reg = /^a{2}b{2}c{2}$/;
console.log(reg.test("aabbcc")); // true
console.log(reg.test("abcc")); // false
console.log(reg.test("acbabc")); // false
// 一个限定符号只能修饰符号前面的一个内容
let reg2 = /a{2}b{2}c{2}/;
console.log(reg2.test("aabbcc")); // true
console.log(reg2.test("abcc")); // false
console.log(reg2.test("acbabc")); // false
// 一个限定符号只能修饰符号前面的一个内容
元字符–特殊符号
()
- 一个整体
// {2}修饰的是前面()内的全部内容
// abc要出现两次
let reg = /^(abc){2}$/;
console.log(reg.test("abc")); // false
console.log(reg.test("abcabc")); // true
let reg2 = /^abc{2}$/;
console.log(reg2.test("abcabc")); // false
console.log(reg2.test("abcc")); // true
console.log(reg2.test("aabbcc")); // false
- 单独捕获
留着
|
或者的意思
注意:或的边界,要么是(),要么是正则的边界
let reg = /^a(b|c)d$/;
// 表示字符串从开头到结尾 必须是 abd或者 acd
// console.log(reg.test("abd")); // true
// console.log(reg.test("acd")); // true
// console.log(reg.test("abcd")); // false
let reg2 = /^ab|cd$/;
// => 以ab开头 ^abxxxxxx
// 或
// => 以cd结尾 xxxxcd$
// console.log(reg2.test("abcd"));// t
// console.log(reg2.test("ababc"));// t
// console.log(reg2.test("1234cd")); // t
// console.log(reg2.test("ab"));// t
// console.log(reg2.test("cd")); // t
let reg3 = /^(奥迪|宝马){2}$/;
// 汽车可以是奥迪或宝马
// {2}修饰的是(xxx) xxx 出现两次
console.log(reg3.test("奥迪奥迪"));
console.log(reg3.test("宝马宝马"));
console.log(reg3.test("奥迪宝马"));
console.log(reg3.test("宝马奥迪"));
// 判断一个手机号
// 以137、138、139开头,总共11位
// 可以有+86也可以没有
let reg = /^(\+86)?1(31|51|86)\d{8}$/;
console.log(reg.test("+8613126172837")); // true
[]
- 意义:包含
- 注意:一个
[]
内可以写多个内容,但是一个[]
只占一个字符的位置,表示[]内的任意一个内容都可以
[0-9]等价于 \d
[0-9a-zA-Z_]等价于 \w
let reg = /^[abcd]$/;
// 表示字符串从开头到结尾只有一个字符
// 这一位字符可以是a、b、c、d
// [abcd] 等价于 (a|b|c|d) [a-d]
// console.log(reg.test("a"));
// console.log(reg.test("b"));
// console.log(reg.test("c"));
// console.log(reg.test("d"));
// console.log(reg.test("e"));
// console.log(reg.test("ab"));
let reg2 = /[abcd]/;
// 表示该字符串中包含a|b|c|d其中一个即为true
console.log(reg2.test("abcd"));
console.log(reg2.test("ab"));
console.log(reg2.test("abc"));
console.log(reg2.test("a"));
console.log(reg2.test("abcd123"));
[^]
- 意义:非
- 注意:一个
[^]
可以写多个内容,但是一个[]只占一个字符的位置,表示[]任意一个都不行
[^0-9] 等价于\D
[^0-9a-zA-Z_]等价于\W
let reg = /^[^abcd]$/;
// 表示字符串从开头到结尾只有一个字符
// 且这个字符不能是abcd其中任意一个。其他都可以。
console.log(reg.test("a"));
console.log(reg.test("b"));
console.log(reg.test("c"));
console.log(reg.test("d"));
console.log(reg.test(" "));// true
console.log(reg.test("1"));// true
console.log(reg.test("\n"));// true
console.log(reg.test(" \n"));// false
-
中划线
- 意义:到 至
- 需要和
[]
或者[^]
连用
let reg = /^[0-9]$/;
// 意义:匹配0-9中的任意一个数字
标识符
书写在正则表达式的外面,专门用来修饰整个正则的符号
- i
- 表示忽略大小写
let reg = /abcd/;
let reg2 = /abcd/i;
console.log(reg.test("ABCD")); // false
console.log(reg2.test("ABCD"));// true
- g
全局
留着
exec
捕获
语法:reg.exec(字符串)
作用:从 字符串 中 把满足正则条件的部分取出来
返回值:
- 原始字符串中没有满足条件的字符串
- null
- 原始字符串有满足条件的字符串
- 正则没有()也没有全局标识符 g
- 他的返回值是一个数组
- 数组索引0项是捕获到的符合规则的字符串
- index:表示第一个满足规则的字符串的开始下标
- input:当前被检测的字符串
- 注意:不管捕获多少次,每次都是从原始字符串的索引0开始检索
- 他的返回值是一个数组
- 有全局标识符 g
- 返回值是一个数组
- 数组索引0项是第一次捕获到的符合规则的字符串
- index:表示满足规则的字符串的开始下标
- input:当前被检测的字符串
- 注意:第二次捕获是从第一次捕获的结束位置开始向后查询,知道最后捕获不到返回null,之后再下一次,又从0开始检索。
- 返回值是一个数组
- 有()
() 有两个意义
- 一个整体
- 单独捕获
如果你只想使用第一个意义,整体所用,不想单独捕获,可以写成(?😃:表示不捕获
// let reg = /\d{3}/;
// let str = "askjdhakjsdhkasjdh";
// let res = reg.exec(str);
// console.log(res); // null
// 有符合要求的片段
// 1-1 没有()没有g
// let str = "ajshdkajhd123ajshdkjahdajksdhk456kudhkajsdha";
// let reg = /\d{3}/;
// let res = reg.exec(str);
// console.log(res);
// let res2 = reg.exec(str);
// console.log(res2);
// 1-2 有g
// let reg = /\d{3}/g;
// let res = reg.exec(str);
// console.log(res);
// let res2 = reg.exec(str);
// console.log(res2);
// let res3 = reg.exec(str);
// console.log(res3);
// let res4 = reg.exec(str);
// console.log(res4);
// 1-3 有 ()
// let str = "110101200204283617";
// let reg = /(\d{2})(\d{2})(\d{2})(\d{4})(\d{2})(\d{2})(\d{4})/;
// let res = reg.exec(str);
// console.log(res);
// let reg2 = /(\d{2})/g;
// console.log(reg2.exec(str));
// console.log(reg2.exec(str));
// console.log(reg2.exec(str));
// console.log(reg2.exec(str));
// console.log(reg2.exec(str));
// console.log(reg2.exec(str));
// console.log(reg2.exec(str));
// console.log(reg2.exec(str));
// console.log(reg2.exec(str));
// console.log(reg2.exec(str));
// console.log(reg2.exec(str));
// console.log(reg2.exec(str));
// console.log(reg2.exec(str));
// console.log(reg2.exec(str));
let str = "11010120020428361x";
let reg = /(?:\d{6})(\d{4})(\d{2})(\d{2})(\d{3})(?:\d|x)/;
console.log(reg.exec(str));
两种创建方式的区别
- 语法不一样
- 书写标识符的区别
- 字面量方式,直接书写在正则的后面
- 构造函数,以第二个参数的形式传递
let reg = /abcd/gi;
let reg2 = new RegExp("abcd","gi")
console.log(reg);
console.log(reg2);
- 拼接字符串
字面量的方式不能接受拼接字符串
构造函数的方式可以拼接字符串
let s = "HH";
let s2 = "MM";
// /(HH|MM)/
let reg = /(\s|\s2)/;
// 字面拉闸
console.log(reg);
let reg2 = new RegExp("(" + s + "|" + s2 + ")");
console.log(reg2);
- 基本元字符的书写
let reg = /\d\w/;
console.log(reg);
let reg2 = new RegExp("\\d\\.\\w")
console.log(reg2);
console.log("\\n");
/*
为什么构造函数要写两个\\
+ 字符串
被引号包裹的内容都叫字符串
放你在字符串中书写的时候,表示转义符号
把紧挨着他的字符 转换
- 有意义的内容转换成无意义的文本
- 无意义的内容转换成有意义的文本
n是一个没有意义的文本
\n 表示换行
+ new RegExp
第一个参数需要一个字符串
你写的字符串就是正则内部的内容
如果你想得到/\d\w/
那我们写的字符串里面是 \d\w
但是,在字符串内\是转义符
所以当你书写'\w'的时候,\就会把w转换成有意义的特殊内容
+ 解决
使用\可以把有特殊意义的\转成没有意义的文本\
当你书写\\d\\w的时候,实际的字符串\d\w
*/
正则的两个特性
-
懒惰性
每一次捕获都是从原始字符串的 索引0 开始的
解决:使用全局标识符 g -
贪婪性
贪婪匹配:能拿多少拿多少,尽可能多的匹配内容
非贪婪匹配:能拿多少拿多少,尽可能少的匹配内容
- 需要使用非贪婪限定符号
- 在原先的限定符号后再写一个?
- *? 0~多次,但是0次能解决问题 就不在多
- +?1~多次,1次能解决,就不在多
- ??0~1次,0次能解决问题 就不在多
- {n,}?
- {n,m}?
let str = '<p class="box"><span>hello world</span></p>';
// 贪婪
let reg = /<.+>/;
console.log(reg.exec(str));;
// 非贪婪
let reg2 = /<.+?>/;
console.log(reg2.exec(str));
es6函数相关
自执行函数
- 一个回自己调用自己的函数
- 当这个函数定义好后,直接被调用
语法:
=> (function(){代码})()
=》~(function(){代码})()
=》!(function(){代码})()
// 自调用函数
(function () {
console.log("hello world");
})();
~(function(){
console.log("你好世界");
})()
!(function(){
console.log("你好世界");
})()
函数默认值
书写:直接在书写函数的时候,以赋值符号 给形参设置默认值就可以了
任何函数都行
// 以前的做法
// function add(a,b){
// a = a || 10;
// b = b || 20;
// return a + b;
// }
// let res = add(1,2);
// console.log(res);
// 允许你在()内设置默认值
// function add(a = 10,b = 20){
// return a + b;
// }
// let res = add(100,200);
// console.log(res); // 300
// 箭头函数设置默认值时,一定要带上小括号
let fn = (a=10) =>{
console.log(a);
}
fn()
箭头函数
是 es6 语法中定义函数的一种新方式
只能用来定义函数表达式
当你把函数当作一个值赋值给另一个内容的时候,叫做函数表达式
// 之前的书写方式
// 声明式函数
function f001() {
// 函数体
}
// 表达式函数
let foo2 = function () {
// 函数体
};
// 箭头函数完整写法
let foo3 = (name, age) => {
// 函数体
console.log(name, age);
};
foo3("yh", 88);
let arr = ["aaa", "bbb", "ccc"];
arr.forEach(function (item, index, arr) {
console.log(item, index, arr);
});
arr.forEach((item, index, arr) => {
console.log(item, index, arr);
});
setTimeout(() => {
console.log("abc");
}, 1000);
注意:声明式函数不可以
语法:()=>{}
- ()写形参的位置
- =>是箭头函数的标志
- {}是书写代码块的位置
箭头函数的特点
- 可以省略小括号不写
- 当形参只有一个的时候,可以不写小括号
- 如果你的形参没有或者两个及两个以上,必须写
let fn1 = a => {
console.log(a);
};
fn1(10);
let fn2 = () => {
console.log("hello");
};
fn2();
let fn3 = (a, b, c, d) => {
console.log(a, b, c, d);
};
fn3(1, 2, 3, 4);
- 可以省略大括号不写
当你的代码只有一句话的时候,可以省略大括号,并且会自动返回这一句话的结果,否则,必须书写大括号
let fn4 = (a, b) => console.log(a, b);
let res = fn4(10, 20);
console.log(res); // undefined
let fn5 = (a, b) => a + b;
let res2 = fn4(10, 20);
console.log(res); // 30
let fn6 = (a, b) => a + b;
// 清除偶数
let arr2 = [1, 2, 3, 4, 5, 6, 7];
let res3 = arr2.filter(item => item % 2);
console.log(res3);
- 箭头函数没有 arguments
let fn1 = function () {
console.log(arguments);
};
fn1(10, 20, 30, 40, 50);
let fn2 = () => {
console.log(arguments); // 报错 未定义
};
fn2(10, 20, 30, 40, 50);
箭头函数内 this 的指向
箭头函数中 this 到底指向哪?
不适用那四条规则,而是根据外层作用域来决定
官方:外部作用域的 this
私人:书写在箭头函数外面的那个函数的 this 是谁,箭头函数的 this 就是谁。
console.log(this); // window
let obj = {
f: function () {
console.log("f的this", this);
},
foo() {
// foo函数作用域
// this指向obj
// 箭头函数继承
return () => {
// this指向obj
console.log("f2的this", this);
};
},
f2: () => {
console.log("f2的this", this);
},
};
obj.f(); // obj
obj.f2(); // window
obj.foo()();
// this永远指向函数 运行时 所在的对象(作用域),而不是函数被创建时所在的对象
// js作用域
// 全局作用域
// 局部作用域(函数作用域)
let obj2 = {
name: "obj2",
foo() {
return () => {
console.log(this); // obj2
return function () {
console.log(this); // window
return () => {
console.log(this); // window
};
};
};
},
};
obj2.foo()()()();
/*
{
this obj2
返回一个fn{
箭头函数拿上一层作用域this
this = obj2
返回一个fn{
独立调用
this = window
返回一个fn{
箭头函数
继承上一层的this
window
this = window
}
}
}
}
*/
面试题
// 1.
var name = "window";
let person = {
name: "person",
sayName: function () {
console.log(this.name);
},
};
function sayName() {
let sss = person.sayName;
sss(); // 默认绑定 window window
person.sayName(); // person 隐式绑定 person
person.sayName(); // person 隐式绑定 person
(b = person.sayName)(); // window 默认 window
// 函数间接引用
// 引用了 person对象中sayName
// person对象中sayName赋值给了b
// b()
}
sayName();
// 2.
var name = "window";
let person1 = {
name: "person1",
foo1: function () {
console.log(this.name);
},
foo2: () => {
console.log(this.name);
},
foo3: function () {
return function () {
console.log(this.name);
};
},
foo4: function () {
return () => {
console.log(this.name);
};
},
};
let person2 = { name: "person2" };
//题目开始
person1.foo1(); // 隐式绑定 person1
person1.foo1.call(person2); // 显示绑定 person2
person1.foo2(); // 默认 外层作用域 window
person1.foo2.call(person2); // 外层 window 箭头函数 没有this call无效
person1.foo3()(); // 默认绑定 window
person1.foo3.call(person2)(); // person2 显示绑定
// window
// 改this为person2 改的是 f003
// foo3 return 出来的函数 没改
// 默认绑定 window
person1.foo3().call(person2); // 显示绑定 person2
// 这里改的就是foo3函数return出来的函数指向
// 指向了person2
person1.foo4()(); // 默认绑定 window 默认绑定 person1
// 把foo4指向person1
// return ()=>{} 拿到foo4的this
// person1 person1
person1.foo4.call(person2)(); // person2
person1.foo4().call(person2); // person1
// foo4内的this -》person1
// 箭头函数call没用 找外层 person1
// 3.
var name = "window";
function Person(name) {
this.name = name;
this.foo1 = function () {
console.log(this.name);
};
this.foo2 = () => console.log(this.name);
this.foo3 = function () {
return function () {
console.log(this.name);
};
};
this.foo4 = function () {
return () => {
console.log(this.name);
};
};
}
let person1 = new Person("person1");
let person2 = new Person("person2");
person1.foo1(); // 隐式绑定 person1
person1.foo1.call(person2); // 显示绑定 person2
person1.foo2(); // person1 上层作用域
person1.foo2.call(person2); // person1 上层作用域
person1.foo3()(); //默认绑定 window
person1.foo3.call(person2)(); // 默认绑定 window
person1.foo3().call(person2); // person2
person1.foo4()(); // person1
person1.foo4.call(person2)(); // person2
person1.foo4().call(person2); // person1
// 4.
var name = "window";
function Person(name) {
this.name = name;
this.obj = {
name: "obj",
foo1: function () {
return function () {
console.log(this.name);
};
},
foo2: function () {
return () => {
console.log(this.name);
};
},
};
}
let person1 = new Person("person1");
let person2 = new Person("person2");
person1.obj.foo1()(); // window
person1.obj.foo1.call(person2)(); // window
person1.obj.foo1().call(person2); // person2
person1.obj.foo2()(); // person1 obj
person1.obj.foo2.call(person2)(); // person2
person1.obj.foo2().call(person2); // obj
this
this 指向 !!重要
this 是一个关键字
是一个作用域内的关键字
- 要么全局使用
this -> window
console.log(this);
console.log(window);
console.log(this === window); // true
- 要么函数内使用
this 表示的是该函数的 context(执行上下文)
概念:函数内的 this,和函数如何定义没有关系,和函数在哪定义也没有关系,只和函数是如何被调用的有关。(箭头函数除外)
几种情况
- 普通调用
- 函数名():普通调用
- 里面的 this 就指向 window
function fn() {
console.log("我是全局的函数 fn");
console.log(this);
}
fn();
// 标准的普通调用
// 函数普通调用时,this永远指向window
- 对象调用
function fn() {
console.log("我是全局的函数 fn");
console.log(this);
}
fn(); // window
// // 标准的普通调用
// // 函数普通调用时,this永远指向window
let obj = {
haha: 1,
fun: fn,
};
// 调用obj内的fun函数 也就是调用全局的fn函数
obj.fun(); // 指向了obj
let arr = [fn, 1, 2];
arr[0](); // arr
- 定时器函数
function fn() {
console.log("我是全局的函数 fn");
console.log(this);
}
setTimeout(fn, 1000); // window
- 事件处理函数
- 事件源.on 事件类型 = 事件处理函数
- 事件源.addEventListener(‘事件类型’,事件处理函数)
- 该函数内的 this 指向事件源
let box = document.querySelector(".box");
box.onclick = fn; // div
- 函数调用时,js 会默认给 this 绑定一个值
- this 的绑定和函数定义的位置无关
- this 的绑定和调用方式以及调用位置有关系
- this 是在运行时被绑定的
四个绑定规则
- 默认绑定
- 隐式绑定
- 显示绑定
- new 绑定
- 默认绑定
独立函数调用
// "use strict";
// 在严格模式下,独立调用函数this指向undefined 而不是window
function foo() {
console.log("foo函数", this);
}
foo();
// 函数定义在对象中,但是独立调用
let obj = {
name: "zd",
bar: function () {
console.log("bar", this);
},
};
// 独立调用
let baz = obj.bar;
baz(); // window
// 高阶函数
function test(cb) {
cb();
}
test(foo); // window
test(obj.bar); // window
// 稍微复杂一点
// 全都是window
function test1() {
console.log("复杂", this);
test2();
}
function test2() {
console.log("复杂", this);
test3();
}
function test3() {
console.log("复杂", this);
}
test1();
setTimeout(function () {
console.log(this);
}, 1000);
- 隐式绑定
通过某个对象进行调用
function foo() {
console.log("foo函数", this);
}
let obj = {
bar: foo,
};
obj.bar(); // obj对象
let obj1 = {
name: "obj1",
foo: foo,
};
let obj2 = {
name: "obj2",
obj1: obj1,
};
obj1.foo(); // obj1
obj2.obj1.foo(); // obj1
let bbb = obj2.obj1.foo;
bbb(); // window
- new 绑定
js 中的函数可以当作类的构造函数来使用,也就是可以使用 new 关键字 new 做了哪四件事? - 创建一个新的对象
- 新的对象执行 prototype 的连接
- 这个新对象会被绑定到函数的 this 上(this 的绑定就在这一步)
- 如果函数没有返回其他复杂数据类型,那就返回这个新对象
function Person(name) {
console.log(this); // this指向实例对象
this.name = name;
}
let p = new Person("zd");
console.log(p);
let p2 = new Person("qt");
console.log(p2);
function foo() {
(this.name = "oobbjj"), console.log(this);
}
console.log(new foo());
- 显示绑定
隐式绑定的前提条件,调用的对象内部必须有一个对函数的引用,如果没有这个引用,在进行调用,会报错(找不到)
let obj = {
name: "zd",
};
function foo() {
console.log(this.name);
}
obj.foo(); // 报错
正是对象内的函数引用,简介的让 this 绑定到了这个对象上
我们不希望在对象内部存着函数引用,同时又希望在这个对象上进行强行调用。
js 的所有函数都可以使用 call/bind/apply 方法
let obj = {
name: "zd",
};
function foo() {
console.log(this.name);
}
obj.foo(); // 报错
foo(); // oobbjj
foo.call(obj); // yh
改变 this 指向
强行改变 this 指向:不管你本身指向哪里,我让你指哪,你就得指哪
call/bind/apply
call
- 语法:跟随在函数名后面调用
- 函数.call(this 指向)
- 对象名.函数.call(this 指向)
意义:修改函数内 this 的指向
- 参数:
- 第一个参数:函数内的 this 指向
- 第二个参数:依次传递给函数的实参
- 特点:会立即调用该函数
function fn(a, b) {
console.group("fn函数内部打印");
console.log("this=>", this);
console.log("a=>", a);
console.log("b=>", b);
console.groupEnd();
}
let obj = {
name: "我是obj对象",
};
let arr = [10, 20, 30, 40, 50];
// 利用call改变指向
// obj就是fn函数内的this
// 100就是给fn的第一个实参,赋值给了a
// 200就是给fn的第一个实参,赋值给了b
fn.call(obj, 100, 200);
/*
this-》obj
a-》100
b-》200
*/
fn.call(arr, 1000, 2000);
/*
this->arr
a->1000
b->2000
*/
apply
- 语法:和 call 一样
- 意义:和 call 一样
- 参数:
- 第一个参数:函数内的 this 指向
- 第二个参数:数组/伪数组(arr)都行,arr 里面的每一项会依次传递给函数的实际参数
- 特点:立即调用函数
- 特殊作用:改变函数传递参数的方式
function fn(a, b) {
console.group("fn函数内部打印");
console.log("this=>", this);
console.log("a=>", a);
console.log("b=>", b);
console.groupEnd();
}
let obj = {
name: "我是obj对象",
};
let arr = [10, 20, 30, 40, 50];
// 因为是利用apply调用
// obj就是fn内的this
// 第二参数传递一个arr数组
// arr[0]是给fn函数的第一个实参,赋值给了形参a
// arr[1]是给fn函数的第二个实参,赋值给了形参b
fn.apply(obj, [100, 200]);
// apply特殊作用
let res = Math.max(1, 2, 3, 4, 5, 6);
console.log(res);
console.log(Math.max);
let arr2 = [10, 90, 101, 312, 800, -100, 50];
let res2 = Math.max.apply(null, arr2);
console.log(res2);
bind
- 语法:跟随在函数名后面调用
- 函数名.bind()
- 对象名.函数.bind()
意义:修改函数内的 this 指向
- 参数:
- 第一参数:函数内的 this
- 第二参数:依次给函数传递的实参
- 特点:
- 不会立即调用该函数,而是返回一个新的函数
- 新的函数就是 this 被改变之后的
- 特殊作用:改变一些不要立即执行的函数内的 this
function fn(a, b) {
console.group("fn函数内部打印");
console.log("this=>", this);
console.log("a=>", a);
console.log("b=>", b);
console.groupEnd();
}
let obj = {
name: "我是obj对象",
};
let arr = [10, 20, 30, 40, 50];
let bb = fn.bind(obj, 100, 200);
console.log(bb);
bb(); // obj 100 200
// bind的特殊作用
// 给定时器函数改变this指向
// call、apply方法都不行,因为他们都会立即调用该函数
// 定时器就失去效果了
// 利用bind调用fn函数
// obj就是fn函数内的this
// 注意:因为是bind,不会执行fn,而是把fn复制一份,把内部的this改变指向obj
setTimeout(fn.bind(obj, 100, 200), 1000);
规则的优先级
- 默认规则的优先级最低
- 显示绑定优先级高于隐式绑定
- new 绑定高于隐式绑定
- new 绑定高于 bind
- bind 高于 call apply
由高到低
new -> bind -> call\apply -> 隐式 -> 默认
// 比较优先级
function foo() {
console.log("foo", this);
}
// 1. 显示绑定优先级高于隐式绑定
// 1-1 call/apply高于隐式
let obj = { foo: foo };
obj.foo(); // obj
obj.foo.call("abc"); // String {'abc'}
obj.foo.apply("abc"); // String {'abc'}
// bind高于隐式
let bar = foo.bind("abc");
let obj1 = {
name: "ooo",
bar: bar,
};
obj1.bar(); // String {'abc'}
// 2. new 绑定高于隐式绑定
let obj2 = {
name: "obj2",
foo1: function () {
console.log("foo", this);
},
};
new obj2.foo1(); // foo1 {}
// 3. new 绑定高于 bind
let bindFn = foo.bind("bbb");
new bindFn(); // foo {}
// 4. bind高于call apply
bindFn.call("call"); // String {'bbb'}
bindFn.apply("apply"); // String {'bbb'}