文章目录
- 正则表达式
- 字面量字符
- 元字符
- 转义符
- 特殊字符
- 字符类
- 预定义模式
- 重复类
- 量词符
- 贪婪模式
- 修饰符
- This关键字
- 使用场合
- 使用注意点
- 避免多层 this
- 避免数组处理方法中的 this
- 避免回调函数中的 this
- 绑定 this 的方法
- Function.prototype.call()
- Function.prototype.apply()
- Function.prototype.bind()
- 闭包
- 变量作用域
- 读取函数内部的局部变量
- 闭包概念
正则表达式
字面量字符
某个字符只表示它字面的含义,例如/a/
匹配a
,/b/
匹配b
。
元字符
(1)点字符(.)
点字符可以匹配除回车(\r
)、换行(\n
)、行分隔符(u2028
)和段分隔符(\u2029
)以外的所有字符。
同时,点字符对于码点大于0xFFFF
字符也不能正确的匹配,会认为这是两个字符
c.t
会匹配c
和t
之间包含任意一个字符的情况,只要字符在同一行。
(2)位置字符
位置字符用来提示字符所处的位置,主要有两个字符。
^
表示字符串的开始位置$
表示字符串的结束位置
// test必须出现在开始位置
/^test/.test('test123') // true// test必须出现在结束位置
/test$/.test('new test') // true// 从开始位置到结束位置只有test
/^test$/.test('test') // true
/^test$/.test('test test') // false
(3)选择符(|
)
(|)表示“或关系”(or),即a|b
匹配a
或b
。
多个选择符可以联合使用。
// 匹配fred、barney、betty之中的一个
/fred|barney|betty/
其他的元字符还包括\
、*
、+
、?
、()
、[]
、{}
等
转义符
正则表达式中,需要反斜杠转义的,一共有12个字符:^
、.
、[
、$
、(
、)
、|
、*
、+
、?
、{
和\
。
这些有特殊含义的元字符,如果要匹配它们本身,就需要在它们前面要加上反斜杠。
特殊字符
对于不能打印的特殊字符,正则表达式提供了表达方法
\cX
表示Ctrl-[X]
,其中的X
是A-Z之中任一个英文字母,用来匹配控制字符。[\b]
匹配退格键(U+0008),不要与\b
混淆。\n
匹配换行键。\r
匹配回车键。\t
匹配制表符 tab(U+0009)。\v
匹配垂直制表符(U+000B)。\f
匹配换页符(U+000C)。\0
匹配null
字符(U+0000)。\xhh
匹配一个以两位十六进制数(\x00
-\xFF
)表示的字符。\uhhhh
匹配一个以四位十六进制数(\u0000
-\uFFFF
)表示的 Unicode 字符。
字符类
字符类(class)表示有一系列字符可供选择,只要匹配其中一个就可以了
例如[abc]
表示a
、b
、c
之中任选一个匹配。
(1)脱字符(^)
如果方括号内的第一个字符是[^]
,则表示除了字符类之中的字符,其他字符都可以匹配。
例如[^abc]
表示a
、b
、c
之外都可以匹配。
如果方括号内没有其他字符,即只有[^]
,就表示匹配一切字符,其中包括换行符。
点号作为元字符(.
)是不包括换行符的
(2)连字符(-)
某些情况下,对于连续序列的字符,连字符(-
)用来提供简写形式,表示字符的连续范围。
例如[123456789]
可以写成[1-9]
,同理[A-Z]
表示26个大写字母。
/a-z/.test('b') // false
/[a-z]/.test('b') // true
如果(-)
中没有出现在[]
中,就只是匹配字面的含义,不具备简写的作用。
只有当(-)
用在[]
之中,才表示连续的字符序列。
合法的字符类简写形式:
[0-9.,]
[0-9a-fA-F]
[a-zA-Z0-9-]
[1-31] //不代表`1`到`31`,只代表`1`到`3`
连字符还可以用来指定 Unicode 字符的范围。
var str = "\u0130\u0131\u0132";
/[\u0128-\uFFFF]/.test(str)
// true
预定义模式
预定义模式指的是某些常见模式的简写方式
\d
匹配0-9之间的任一数字,相当于[0-9]
。\D
匹配所有0-9以外的字符,相当于[^0-9]
。\w
匹配任意的字母、数字和下划线,相当于[A-Za-z0-9_]
。\W
除所有字母、数字和下划线以外的字符,相当于[^A-Za-z0-9_]
。\s
匹配空格(包括换行符、制表符、空格符等),相等于[ \t\r\n\v\f]
。\S
匹配非空格的字符,相当于[^ \t\r\n\v\f]
。\b
匹配词的边界。\B
匹配非词边界,即在词的内部。
通常,正则表达式遇到换行符(\n
)就会停止匹配。
重复类
模式的精确匹配次数,使用大括号({}
)表示。{n}
表示恰好重复n
次,{n,}
表示至少重复n
次,{n,m}
表示重复不少于n
次,不多于m
次。
量词符
量词符用来设定某个模式出现的次数。
?
问号表示某个模式出现0次或1次,等同于{0, 1}
。*
星号表示某个模式出现0次或多次,等同于{0,}
。+
加号表示某个模式出现1次或多次,等同于{1,}
。
贪婪模式
量词符默认情况下都是最大可能匹配,即匹配到下一个字符不满足匹配规则为止。这被称为贪婪模式。
var s = 'aaa';
s.match(/a+/) // ["aaa"]
非贪婪模式,即最小可能匹配。只要一发现匹配,就返回结果,不往下检查。
将贪婪模式改为非贪婪模式,可以在量词符后面加一个问号。
var s = 'aaa';
s.match(/a+?/) // ["a"]
除了非贪婪模式的加号(+?
),还有非贪婪模式的星号(*?
)和非贪婪模式的问号(??
)。
+?
:表示某个模式出现1次或多次,匹配时采用非贪婪模式。*?
:表示某个模式出现0次或多次,匹配时采用非贪婪模式。??
:表格某个模式出现0次或1次,匹配时采用非贪婪模式。
修饰符
修饰符(modifier)表示模式的附加规则,放在正则模式的最尾部。
修饰符可以单个使用,也可以多个一起使用。
// 单个修饰符
var regex = /test/i;// 多个修饰符
var regex = /test/ig;
(1)g修饰符
正则模式含有g
修饰符,每次都是从上一次匹配成功处,开始向后匹配。
var regex = /b/g;
var str = 'abba';regex.test(str); // true
regex.test(str); // true
regex.test(str); // false
因为字符串abba
只有两个b
,所以前两次匹配结果为true
,第三次匹配结果为false
。
(2)i 修饰符
正则对象区分字母的大小写,加上i
修饰符以后表示忽略大小写。
/abc/.test('ABC') // false
/abc/i.test('ABC') // true
(3)m 修饰符
m
修饰符表示多行模式(multiline),会修改^
和$
的行为。默认情况下(即不加m
修饰符时),^
和$
匹配字符串的开始处和结尾处,加上m
修饰符以后,^
和$
还会匹配行首和行尾,即^
和$
会识别换行符(\n
)。
/world$/.test('hello world\n') // false
/world$/m.test('hello world\n') // true
This关键字
this
就是属性或方法“当前”所在的对象。
下面是一个实际的例子。
var person = {name: '张三',describe: function () {return '姓名:'+ this.name;}
};person.describe()
// "姓名:张三"
由于对象的属性可以赋给另一个对象,所以属性所在的当前对象是可变的,即this
的指向是可变的。
function f() {return '姓名:'+ this.name;
}var A = {name: '张三',describe: f
};var B = {name: '李四',describe: f
};A.describe() // "姓名:张三"
B.describe() // "姓名:李四"
函数f
内部使用了this
关键字,随着f
所在的对象不同,this
的指向也不同。
只要函数被赋给另一个变量,this
的指向就会变。
使用场合
(1)全局环境
全局环境使用this
,它指的就是顶层对象window
。
this === window // truefunction f() {console.log(this === window);
}
f() // true
(2)构造函数
构造函数中的this
,指的是实例对象。
var Obj = function (p) {this.p = p;
};
//在构造函数obj,this指向p属性
var o = new Obj('Hello World!');
o.p // "Hello World!"
(3)对象的方法
如果对象的方法里面包含this
,this
的指向就是方法运行时所在的对象。该方法赋值给另一个对象,就会改变this
的指向。
var obj ={foo: function () {console.log(this);}
};obj.foo() // obj
//此时this指向obj
//以下的用法会改变this的指向
// 情况一
(obj.foo = obj.foo)() // window
// 等同于
(obj.foo = function () {console.log(this);
})()
// 等同于
(function () {console.log(this);
})()// 情况二
(false || obj.foo)() // window
//等同于
(false || function () {console.log(this);
})()// 情况三
(1, obj.foo)() // window
//等同于
(1, function () {console.log(this);
})()
使用注意点
避免多层 this
由于this
的指向是不确定的,所以切勿在函数中包含多层的this
。
var o = {f1: function () {console.log(this);var f2 = function () {console.log(this);}();}
}o.f1()
// Object
// Window
这种情况,f1第一次指向对象o,然后代码运行到f2时,this
的指向又变成了全局对象。
避免数组处理方法中的 this
数组的map
和foreach
方法,允许提供一个函数作为参数。这个函数内部不应该使用this
。
var o = {v: 'hello',p: [ 'a1', 'a2' ],f: function f() {this.p.forEach(function (item) {console.log(this.v + ' ' + item);});}
}o.f()
// undefined a1
// undefined a2
上面的this
其实也是类似于多层this
,函数f
的调用也是相当于在全局环境下调用,所以这里的this
指向也是指向window
对象。
避免回调函数中的 this
回调函数中的this
往往会改变指向,最好避免使用。
var o = new Object();
o.f = function () {console.log(this === o);
}// jQuery 的写法
$('#button').on('click', o.f);
以上的代码,在调用函数f
时,是在按钮对象的环境下调用的,所以this
的指向也不是o
,而是指向按钮的DOM对象。
绑定 this 的方法
JavaScript 提供了call
、apply
、bind
这三个方法,来切换/固定this
的指向。
Function.prototype.call()
函数实例的call
方法,可以指定函数内部this
的指向(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数。
call
方法的参数,应该是一个对象。如果参数为空、null
和undefined
,则默认传入全局对象。
var n = 123;
var obj = { n: 456 };function a() {console.log(this.n);
}//如果`call`方法没有参数,或者参数为`null`或`undefined`,则等同于指向全局对象。
a.call() // 123
a.call(null) // 123
a.call(undefined) // 123
//如果指向全局对象,返回结果为`123`
a.call(window) // 123
//如果使用`call`方法将`this`关键字指向`obj`对象,返回结果为`456`
a.call(obj) // 456
如果call
方法的参数是一个原始值,那么这个原始值会自动转成对应的包装对象,然后传入call
方法。
var f = function () {return this;
};f.call(5) //5为参数,不是对象
// Number {[[PrimitiveValue]]: 5}
call
方法还可以接受多个参数。
func.call(thisValue, arg1, arg2, ...)
Function.prototype.apply()
apply
方法的作用与call
方法类似,也是改变this
指向,然后再调用该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数,使用格式如下。
func.apply(thisValue, [arg1, arg2, ...])
(1)找出数组最大元素
JavaScript 不提供找出数组最大元素的函数。结合使用apply
方法和Math.max
方法,就可以返回数组的最大元素。
var a = [10, 2, 4, 15, 9];
Math.max.apply(null, a) // 15
(2)将数组的空元素变为undefined
通过apply
方法,利用Array
构造函数将数组的空元素变成undefined
。
Array.apply(null, ['a', ,'b'])
// [ 'a', undefined, 'b' ]
(3)转换类似数组的对象
另外,利用数组对象的slice
方法,可以将一个类似数组的对象(比如arguments
对象)转为真正的数组。
Array.prototype.slice.apply({0: 1, length: 1}) // [1]
Array.prototype.slice.apply({0: 1}) // []
Array.prototype.slice.apply({0: 1, length: 2}) // [1, undefined]
Array.prototype.slice.apply({length: 1}) // [undefined]
//前提:被处理的对象必须有`length`属性,以及相对应的数字键。
Function.prototype.bind()
bind()
方法用于将函数体内的this
绑定到某个对象,然后返回一个新函数。
var counter = {count: 0,inc: function () {this.count++;}
};var func = counter.inc.bind(counter);
func();
counter.count // 1//`counter.inc()`方法被赋值给变量`func`。这时必须用`bind()`方法将`inc()`内部的`this`,绑定到`counter`,否则就会出错。
bind()
方法有一些使用注意点。
(1)每一次返回一个新函数
bind()
方法每运行一次,就返回一个新函数,这会产生一些问题。
(2)结合回调函数使用
回调函数是 JavaScript 最常用的模式之一,但是一个常见的错误是,将包含this
的方法直接当作回调函数。
(3)结合call()
方法使用
利用bind()
方法,可以改写一些 JavaScript 原生方法的使用形式。
闭包
闭包就是能够读取外层函数内部变量的函数。
变量作用域
变量的作用域为两种:全局作用域和局部作用域
1)函数内部可以读取全局变量
2)函数外部无法读取函数内部的局部变量
读取函数内部的局部变量
1)在函数内部再定义一个函数
function f1() {let code = 200;function f2() {console.log(code);}
}
函数f1内部的函数f2可以读取f1中所有的局部变量。因此,若想在外部访问函数f1中的局部变量code
,可通过函数f2间接访问。
2)为外部程序提供访问函数局部变量的入口
function f1() {let code = 200;function f2() {console.log(code);}return f2;
}f1()(); // 200
闭包概念
-
闭包访问的变量,是每次运行上层函数时重新创建的,是相互独立的。
-
不同的闭包,可以共享上层函数中的局部变量
使用闭包的注意点:
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。