函数名称扩展
- 函数表达式的函数名问题:
在ES5及其之前,因为函数赋值的变量(f)本身是没有name属性的,所以打印函数名都是'',都是ES6可以打印出函数名的
var f = function(){}
console.log(f.name); //f
- new Function()问题:
我们在声明一个函数的时候,实际上都实例化了一个Function构造函数,平常不建议用new Function声明函数。
但是需要知道的是new Function的name属性是不一样的
console.log(new Function().name); //anonymous(匿名)
- bind
function foo(){}
console.log(foo.bind({}).name); //bound foo
call和bind不一样,因为call是函数执行,函数执行完了是不会有名字的
function foo(){}
console.log(foo.call({}).name); //报错
对象拓展
属性名都会被处理成字符串
例1:
var arr = [5,6,8,88];
console.log(arr['1']); //5数组也是特殊的变量
例2:
let a = 'hello';
let b = 'world';
let obj = {
[a + b] = true;
}
例3:
var myObj = {};
myObj[true] = 'foo';
myObj[3] = 'bar';
myObj[myObj] = 'baz';
console.log(myObj); //{'true':'foo','3':'bar','myObj':'baz'}
直接变成字符串,属性处理成字符串都要经过隐式转换(toString)
true和3经过原型上的toString方法都会变成对应的字符串,myObj经过toString方法会变成[object Object],所以直接通过[object Object]可以访问到baz
console.log(myObj["[object Object]"]); //baz
例4:
const a = {a: 1};
const b = {b: 2};
const obj = {
[a]: 'valueA',
[b]: 'valueB'
}
console.log(obj); //{[object Object]: 'valueB'}
引用变量一定要加括号,不然就变成{a: "valueA", b: "valueB"},直接转化不引用了。
获取对象方法的名称
const person = {
sayName(){
console.log('hello');
}
}
console.log(person.sayName.name); //sayName
属性描述符 getOwnPropertyDescriptor
ES5之前没有检测属性特性(是否只读,可遍历等)的方法,ES6提供了属性描述符
let obj = {a: 2};
console.log(Object.getOwnPropertyDescriptor(obj, 'a'));
传入的属性要是字符串
configurable: true //可配置的
enumerable: true //可枚举的
value: 2
writable: true //可写
defineProperty修改和添加一个新的属性
let obj = {};
Object.defineProperty(obj, 'a', {
value: 2,
enumerable: true, //false的话就不能再用defineProperty配置了
configurable: true,
writable: false //不可写但是可以删除
})
obj.a = 3;//静默失败,因为不可写,这条语句执行失败,但也不报错;但是在严格模式下会报错
console.log(obj); //{a: 2}
console.log(Object.defineProperty(obj, 'a'));
delete obj.a;
console.log(obj); //{}
writable设置false仅仅是不可写,不可删要是configurable为false的情况下才可以
defineProperties
var obj = {};
Object.defineProperties(obj, {
a: {
value: true,
writable: true
},
b: {
value: 'hello',
writable: true
}
})
getter / setter
get操作
let obj = {a: 1};
obj.a; //属性获取,[[Get]]默认操作:查找当前属性,如果没有,查找原型
put操作
obj.a = 3; //赋值操作[[Put]]
- getter 还是 setter
- writable:false,不让你改
- 赋值
getter和setter覆盖了原本的[[Get]]和[[Set]]
getter:取值函数
var obj = {
log: ['example', 'test'],
get lastest(){ //get方式的伪属性
if(this.log.length === 0){
return undefined;
}
return this.log[this.log.length - 1];
}
}
console.log(obj.lastest); //访问属性,实际上调用的是get方法
通过defineProperty定义getter,value和writable不能用
var obj = {
get a(){
return 2;
}
}
Object.defineProperty(obj, 'b', {
get: function)() {
return this.a * 2;
}
enumerable: true,
value: 6 //报错,和get操作重复
writable: true //不管是true还是false都报错,不能定义
})
setter:设值函数
var language = {
set current(name){ //一定要有参数
this.log.push(name)
},
log: []
}
language.current = 'en';
get和set一般情况下都是成对存在的,get和set的函数名称不能直接获取到,要通过getOwnPropertyDescriptor获取到函数才行
var obj = {
get foo(){
return this._a;
},
set foo(val){
this._a = val * 2;
}
}
obj.foo = 3; //set操作
console.log(obj.foo); //6
console.log(obj.foo.name); //undefined,不能取到name
var descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');
console.log(descriptor.get.name);
console.log(descriptor.set.name);
对象密封
对象常量:不可删除,不可修改
1. Object.defineProperty配置属性描述符
configurable: false
enumerable: true
value: 2
writable: false
正常添加属性obj.b=3,默认情况下除了value以外的三个值都是true
通过Object.defineProperty添加属性,默认情况下这三个值都是false
2. preventExtensions 不可拓展(添加新的属性或方法)
preventExtensions不能改变属性描述符
isExtensible 判断对象是否可以拓展
var obj = {a: 2};
console.log(Object.preventExtensions(obj)); //返回该对象
obj.b = 3; //静默失败,严格模式下会报错
console.log(Object.isExtensible(obj)); //false,不可拓展
3. Object.seal(obj)
seal将configurable变成false
isSealed 是否密封
var obj = {a: 2};
console.log(Object.seal(obj));
4. Object.freeze(obj)
freeze会把configurable和writable都会是false,这只是浅拷贝,要想深度冻结,需要循环冻结对象上的方法
isFrozen 是否被冻住
function myFreeze(obj) {
Object.freeze(obj);
for(var key in obj) {
if (typeof(obj[key]) !== 'object' && obj[key] !== null) {
myFreeze(obj[key]);
}
}
}
一般用freeze
对象原型上的其他方法
Object.is()判断是否全等
‘==’会隐式转换
‘===’严格相对运算符,会调用底层的sameValue算法
ES5要判断两个值是否相等,需要用运算符判断
console.log(NaN === NaN); //false
console.log(+0 === -0); //true
ES6可以调用is方法判断,用的是‘===’
console.log(Object.is(NaN, NaN));
Object.keys() 获取自身可枚举的键名
Object.value() 获取自身可枚举的键值
Object.entriese() 获取自身可枚举的键名和键值
不含原型上的属性
coonst foo = {a: 1};
Object.defineProperties(foo, {
d: {
value: 4,
enumerable: true
},
f: {
value: 5,
enumerable: false
}
})
console.log(Object.keys(foo)); //["a", "d"],f不可枚举
console.log(Object.value(foo)); //[1,4,5]
console.log(Object.entries(foo)); //[["a",1],["d",4]]
传入的参数不是对象的话,会隐式转换,进行包装类
let obj = 1;
console.log(Object.keys(obj)); //[]
let obj = 'abc';
console.log(Object.keys(obj)); //["1","2","3"]
Object.assign(tar, ...sourses)合并方法
对象拷贝(浅拷贝)
let obj = {a: {b: 1}};
let tar = {};
let copy = Object.assign(tar, obj); //返回值就是第一个参数
console.log(copy === tar); //true
console.log(copy === obj); //false
obj.a.b = 2;
console.log(obj.a.b); //2
同名属性的替换:
const tar = {a: 1, b: 1};
const tar1 = {b: 2, c: 2};
const tar2 = {c: 3};
Object.assign(tar, tar1, tar2);
console.log(tar); //{a: 1, b: 2, c: 3}后面的覆盖前面的
数组替换:两个属性下标相同的部分会被替换
Object.assign([1,2,3],[4,5]);//[4,5,3]
传值不是对象的情况:
Object.assign(undefined, {a: 1});//报错
var test = Object.assign(1, {a: 1};//用包装类的方式,转换为对象
console.log(test); //Number{1, a: 1}
第一个参数至少要是一个对象,undefined没有对应的包装类,无法进行合并
Object.assign({a: 1}, undefined);//{a: 1}
Object.assign({a: 1}, 1);//{a: 1}
Object.assign({a: 1}, true);//{a: 1}
Object.assign({a: 1}, '123');//{0: "1", 1: "2", 2: "3", a: 1}
Object.assign({}, '123', true, 10);//{0: "1", 1: "2", 2: "3"}
如果第二个参数无法转换为对象,就不进行任何处理,直接返回第一个参数的对象
如果第二个参数是对象,还要注意是否具有可枚举性,不具有枚举性的也会忽略
let obj = {a: 1};
Object.defineProperty(obj, 'b', {})
Object.create(proto, [propertiesObject])
第一个参数指定原型,第二个参数配置属性和对应的描述符
var obj = Object.create({foo: 1}, {
bar: {
value: 2
},
baz: {
value: 3,
enumerable: true
}
})
console.log(obj); //{baz: 3, bar: 2}
let copy = Object.assign({}}, obj);
console.log(copy); //{baz: 3},并且原型上的foo没有拷贝上去
继承属性和不可枚举属性,不能拷贝
symbol()可以生成完全不一样的,永远不会重复的,类似于字符串的原始类型
那么Symbol可以用assign进行拷贝吗? 可
var test = Object.assign({a: b}, {[Symbol('c')] : 'd'});//{a: 1}
console.log(test)//{a: "b", Symbol(c): "d"}
在原型上扩充:
function Person(){}
var age = 1;
Object.assign(Person.prototype, {
eat(){},
age,
})
覆盖的准确做法:
const DEFAULT = {
url: {
host: 'www.baidu.com',
port: 7070
}
}
function test(opt){
opt = Object.assign({}, DEFAULT, option);
}
test({url: {port: 8080}})
用户如果没有给值,就用DEFAULT,如果用户配置了,就会替代DEFAULT
拷贝取值函数:
const source = {
get foo(){
return 1;
}
}
const target = {};
Object.assign(target, source); //{foo: 1}
拷贝的不是函数体本身,直接拷贝具体的值
所以用assign不能达到我们的目的,要用getOwnPropertyDescriptor
Object.defineProperties(tar, Object.getOwnPropertyDescriptor(source));
console.log(Object.getOwnPropertyDescriptor(tar, 'foo'));
getOwnPropertyDescriptors 很容易浅拷贝
利用getPropertyOf获取原型,getOwnPropertyDescriptors获取属性,然后创建对象即可
const clone = Object.create(Object.getPropertyOf(obj), Object.getOwnPropertyDescriptors(obj));
部署对象的方式
const obj = {a: 1};
const obj = Object.create(port);
没有get和set的情况下是可以用assign的
const obj = Object.assign(Object.create(port), {
foo: 123
})
const obj = Object.create(port, Object.getOwnPropertyDescriptors({
foo: 123
}));
原型
person.__proto__用这种方法访问原型是非常消耗性能的,并且会影响所有继承这个原型的对象;语义化也不好,这是个内部属性
所以用下列方式替代:
Object.setPropertyOf() 设置或修改原型
Object.getPropertyOf() 获取原型
Object.create() 创建原型
let proto = {
y: 20
}
let obj = {x: 10};
let obj1 = Object.setPropertyOf(obj, proto);
console.log(obj1 === obj); //true 返回值就是第一个参数,所以obj1也是可以不要的
Object.setPropertyOf(obj, proto);
console.log(obj); //这样就行了
第一个参数如果不是对象,会指定失效,如果是undefined/null会报错
let obj = Object.setPropertyOf(1, {a: 1});
console.log(Object.getPropertyOf(obj)); //Number{0}
1首先会包装类,通过包装类的方式变成Number,所以打印原型打印的是Number的原型,指定失败
Object.getPropertyOf(1) === Number.prototype
super 指向对象的原型
要让super关键字生效,只能是对象的方法的简写写法才可以
let proto = {
y: 20,
z: 40,
}
let obj = {
x: 10,
foo(){
console.log(super.y)
}
};
Object.setPropertyOf(obj, proto);
obj.foo(); //20
Symbol
ES6新引入的原始值类型,ES5会存在对象属性重名的问题
Symbol虽然第一个值大写,但不是构造函数,给不给括号都行,但是代表的意思不一样。
不给括号就是它的构造函数,要调用它就要打括号执行
var s1 = Symbol;
console.log(s1); //ƒ Symbol() { [native code] }
var s2 = Symbol();
console.log(s1); //Symbol()
并且这个构造函数和奇怪的一点,就是不能new
var s1 = new Symbol;
它是独一无二的,为了区分可以传入字符串作为标识符
var s1 = Symbol('foo');
var s2 = Symbol('foo');
console.log(s1 == s2); //false
console.log(s1); //Symbol()
console.log(typeof s1); //symbol
s1.a = 1;
console.log(s1.a); //undefined 原始值没有属性
symbol的标识符永远都是字符串,传undefined就没有(空),传null就变成字符串‘null’
显式类型转换只有Number类型不能转
var obj = {a: 1};
let s1 = Symbol(obj); //自动调用Object.prototype.toString()方法,将对象转换为字符串
console.log(s1); //Symbol([Object object])
console.log(s1 + 1); //报错,s1不能通过number类型进行隐式转化,也就是说symbol类型不能进行计算
console.log(String(s1)); //Symbol([object Object])
console.log(s1 + ''); //报错,String类型只能通过传参或者调用方法进行显示转换,不能隐式转换
console.log(Boolean(s1)); //true
console.log(!s1); //false,这是唯一能进行隐式转换的方法
console.log(Object.setPropertyOf(s1)); //Symbol,Symbol也有自己的构造函数
console.log(s1.toString()); //Symbol([object Object])原型上有toString方法
调用
let name = Symbol();
let person = {};
person.name = 'jackson'; // 底层转换person['name']
console.log(person); //{name: 'jackson'}
一般调用是不行的,要通过[]的方式才可以
person[name] = 'jackson';
console.log(person); //{Symbol(): "jackson"}
let name = Symbol();
let eat = Symbol();
let person = {
[name] : 'jackson',
[eat](){
console.log(this[name])
}
}
let name = Symbol();
let person = {};
Object.defineProperty(person, name, {
value: 'jackson'
})
访问:
console.log(person.name);
直接这样访问是不行的,因为会转换成person['name'],person里面是没有键名为name字符串的属性的
还是要这样
console.log(person[name]);
person[eat]();
相关方法:
调用方法就要在构造函数上调用,不能在Symbol后面打括号
Symbol.for()
Symbol永远拿不到一样的值,这个方法可以拿到一样的值
用Symbol的方式不会有一个key值,用Symbol.for可以生成一个key值,根据key值拿到想要是Symbol
var s = Symbol('foo');
var s1 = Symbol.for('foo');
var s2 = Symbol.for('foo');
console.log(s == s2); //false
console.log(s1 == s2); //true
Symbol.keyFor()
可以拿到key值
console.log(Symbol.keyFor(s1)); //foo
console.log(Symbol.keyFor(s)); //undefined
遍历:getOwnPropertySymbols
for...in/of... 循环不能遍历Symbol属性,针对这个有一个特有的API:Object.getOwnPropertySymbols(obj),这个方法只会遍历Symbol属性
const obj = {};
let a = Symbol('a');
let b = Symbol('b');
obj[a] = 'hello';
obj[b] = 'world';
obj.c = '1';
const objSymbols = Object.getOwnPropertySymbols(obj); //返回遍历的数组
console.log(objSymbols); //[Symbol(a),Symbol(b)]
例:
const obj = {c: 1, d: 2};
let a = Symbol('a');
let b = Symbol('b');
let _a = Symbol('_a');
let _b = Symbol('_b');
obj[a] = 'hello';
obj[b] = 'world';
obj.c = '1';
Object.defineProperties(obj, {
e: {
value: 5,
enumerable: true
},
f: {
value: 6,
enumerable: false
},
[_a]: {
value: -1,
enumerable: true
},
[_b]: {
value: -2,
enumerable: false
}
})
let h = Symbol('h');
let i = Symbol('i');
let j = Symbol('j');
const obj1 = {
g: 7,
[h]: 8
}
Object.defineProperties(obj1, {
[i]: {
value: 9,
enumerable: true
},
[j]: {
value: 10
},
k: {
value: 11
}
})
Object.setPrototypeOf(obj, obj1);
console.log(obj);
for(let i in obj){ //遍历自身及原型上可枚举的,非Symbol属性
console.log(i);
}
console.log(Object.keys(obj)); //遍历自身可枚举的,非Symbol属性
console.log(Object.getOwnPropertySymbols(obj)); //遍历自身的非Symbol类型的值,不管可不可枚举
var obj3 = {};
Object.assign(obj3, obj); //拷贝只拷贝自身可枚举的,包含Symbol的属性
console.log(obj3);
还有一种遍历的方法就是JSON.stringify(),遍历自身可枚举的属性
iterator 迭代器
let arr = [1,2,3,4];
console.log(arr);
arr在原型上部署了一个迭代器接口Symbol(Symbol.iterator): ƒ values()
构造器(构造器属性):方法
通过arr[Symbol.iterator]可以拿到对应的方法
let itar = arr[Symbol.iterator]
console.log(itar); //ƒ values() { [native code] }
let itar = arr[Symbol.iterator]();
console.log(itar);
执行方法可以看见原型里有next方法
console.log(itar.next()); //{value: 1, done: false}
执行返回对象,value代表读取的值,false代表还没有执行完
let arr = [1,2,3,4];
let itar = arr[Symbol.iterator]();
console.log(itar.next());
console.log(itar.next());
console.log(itar.next());
console.log(itar.next());
console.log(itar.next());
console.log(itar.next());
连续执行六次,可以看到以下结果
迭代器:读取数据结构的一种方式,有序的,连续的,基于拉取的一种消耗数据的组织方式(抽取过了就不再访问了)
封装迭代器
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{ value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
}
}
let itar = makeIterator([5,2,3,4]);
但是这种方式一直next,有点累赘,ES6中借鉴了c++,java,c#,python,有了for...of...循环 -> 迭代
for...of...调用的就是iterator接口
let arr = [1,2,3,4]
for(let i of arr) {
console.log(i); //1,2,3,4
}
for..in..拿的是下标,遍历对象;for..of..直接拿的值,迭代部署过迭代器接口的数据类型(Array、Map、weakMap、weakSet、类数组[arguments、nodeList]、typeArray),除了对象(对象是无序的)
如果想让对象迭代的话,就要部署迭代器接口
let obj = {
start: [1,2,3,4],
end: [5,6,7],
[Symbol.iterator](){
let index = 0,
arr = [...this.start, ...this.end],
len = arr.length;
return {
next(){
if(index < len) {
return {
value: arr[index++],
done: false
}
}else{
return {
value: undefioned,
done: true
}
}
}
}
}
}
for(let i of obj){
console.log(i);
}
var arr = [6,7,5,1];
var iter = arr[Symbol.iterator]();
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
for(let i of arr){ //两种方式都用的话,for of有可能会失效
console.log(i);
}
...在对象部署迭代器接口的情况下是可以用的,没有部署拓展对象会报错
let arr = [...obj];
console.log(arr); //[1,2,3,4,5,6,7]
typeArray 在js中其实是不存在的,是用来处理二进制数据的
是类型数组,不是类数组
const tArray = new Int8Array(8); //默认填充8个0
数组拓展
Array.of()声明数组
数组声明的时候有时候会有歧义,用Array.of()就可以解决这个问题
console.log(new Array(3)); //[empty x 3]
console.log(Array.of(3)); //[3]
Array.from() 转化数组
参数:arrayLike、mapFn(map方法)、thisArg(this指向)
将类数组或者部署了iterator接口的对象转化为数组
ES5是用数组原型上的slice方法转化,ES6直接用Array.from()就行
var oList = document.getElementsByClassName('p');
console.log([].slice.call(oList));
console.log( Array.from(oList));
部署了iterator接口的对象(见上例)
console.log(Array.from(obj)); //[1,2,3,4,5,6,7]
console.log(Array.from(obj, function mapper(val, idx){
return val * 2; //[2,4,6,8,10,12,14]
}))
Array.prototype.fill(val, start, end)
修改原数组,start默认从0开始,end默认为this.length,没有指定后两个参数,就全部替代
let arr1 = [1,2,3,4];
let arr = arr1.fill(5);
console.log(arr, arr1); //都是[5,5,5,5]
区间左闭右开
let arr1 = [1,2,3,4];
arr1.fill(5, 1, 2);
console.log(arr1); // [1,5,3,4]
如果起始和结束一样,就不处理
负数
arr1.fill(5, -3, -2);
console.log(arr1); // [1,5,3,4]
如果start给的数字不合法,就用默认的0
arr1.fill(5, NaN, -2);
console.log(arr1); // [5,5,3,4]
如果start和end都不合法,不做任何处理
还要注意的是这个方法是个通用方法,也可以对对象强制指定
console.log([].fill.call({lenngth: 3}, 4)); //{0: 4, 1: 4, 2: 4}
长度为3的对象强制填充4
[].keys()索引 / [].values()值 / [].entries()键值对
Object.keys()/Object.values()/Object.entries()返回数组,数组自然可以各种循环,for循环、for of都可
let obj = {a: 1, b: 2};
console.log(Object.keys(obj)); //["a", "b"]
[].keys() / [].values() / [].entries() 返回迭代器对象
let arr = ["a", "b"];
console.log(arr.keys()); //Array Iterator
既然是迭代器,就可以用next方法和for of
let arr = ["a", "b"];
var iter = arr.keys();
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
{value: 0, done: false}
{value: 1, done: false}
{value: undefined, done: true}
循环
for(let i of iter){
console.log(i); //0 1
}
用普通的for循环,因为iter没有length属性,所以不行
对象和数组都有这几种方法,但是不同的是,返回值不同
[].copyWithin(target, start, end)
target指的是位置,在第几位插入,插入的数值由后两位决定,并且保持没替代的值和数组长度不变。
let arr = [1,2,3,4,5,6,7];
console.log(arr.copyWithin(2)); //[1, 2, 1, 2, 3,4,5]
在第2位,开始用原数组依次填充,直到填满
console.log(arr.copyWithin(-2)); //[1, 2, 3, 4, 5, 1, 2]
console.log(arr.copyWithin(0,3)); //[4, 5, 6, 7, 5, 6, 7]
在0的位置插入,从第三位开始到结尾4567替换值,没被替换到的值不变
console.log(arr.copyWithin(0,3,4)); //[4, 2, 3, 4, 5, 6 , 7]
console.log(arr.copyWithin(-2,-3,-1)); //[1,2,3,4,5,5,6]
这个属性用的比较少,一般用fill代替这个方法
[].find() / [].findIndex
获取数组索引,之前都是用indexOf,indexOf语义化不是特别明显,并且在NaN这里有一点问题
console.log([NaN].indexOf(NaN)); //-1
[NaN].findIndex(y => Object.is(NaN, y)); //0
find()返回第一个满足条件的值
var arr = [1,2,3,4];
var arr 1 = arr.find(function(value, index, arr) {
return value > 2;
})
console.log(arr1); //3,没有4,有一个成功就会返回
但是这个方法返回点永远都是一个值,就算是return idx > 2,结果也是4,找不到返回undefined
所以就需要findIndex,返回第一个满足条件的索引
var arr 1 = arr.findIndex(function(value, index, arr) {
return value > 2;
})
console.log(arr1); //2
找不到返回-1
[].includes()
判断数组是否包含某个值
console.log([1,2,NaN].includes(NaN)); //true
数值的拓展
ES5的16进制表示方法:在数值前面加0x
ES6新增了二进制(0b)和八进制(0o)的表示方法,大小写不分
十进制转二进制
Number.prototype.toString.call(502, 2);
二进制转十进制
parseInt(111110111, 2);
在ES6里可以不用这么麻烦,直接0b + 要转的二进制就好了
0b111110111
已有方法点调整
ES6把和数值有关的方法挂在Number的构造器上,不在是全局上了
- isNaN()
- isFinite()[判断是否有限]
- parseInt()、parseFoloat():parseInt和parseFoloat用法没有变,只是挪了个位置
并且对isNaN()和isFinite()进行了调整:
之前全局的isNaN(),会进行隐式转化
console.log(isNaN("NaN")); //true
调整之后点isNaN()不会
console.log(Number(isNaN("NaN"))); //false
isFinite()同理,全局上如果传入的是字符串,会隐式转换
新增方法
- Number.isInteger()判断是否整数:24.0也是整数
- Number.MAX_SAFE_INTEGER最大安全整数:整数处理的上限,全等于Math.pow(2, 53) - 1
- Number.MIM_SAFE_INTEGER === -Math.pow(2, 53) + 1
- Number.isSafeInteger()是否安全整数
正则拓展
- 声明正则的变化方式
ES5
var reg = new RegExp('xyz', 'i');
var reg = new RegExp(/xyz/i);
var reg = /xyz/i;
ES6
var reg = new RegExp(/xyz/gi, 'm'); //这种情况下一个参数的修饰符失效,以后面的参数为准
- 字符串上的正则方法进行了调整
正则原型上有如下方法:
和字符串的方法一样,只是原本这些方法上写在String的原型上的,ES6把他们调整到正则的原型上了
String.prototype.match -> RegExp.prototype[Symbol.match]
- 新增的修饰符u y s
| 新增修饰符 | 说明 |
|---|---|
| y | sticky粘黏,匹配到连续的字符 |
| u | 匹配Unicode编码 |
| s | dotAll让.代表一切 |
y修饰符
修饰符对应着正则的属性
var reg = new RegExp('xyz', 'ig');
console.log(reg.global); //true
var str = 'aaa_aa_a';
var reg1 = /a+/g;
var reg2 = /a+/y;
console.log(reg1.exec(str));
console.log(reg2.exec(str)); //两个都匹配到里3个a
console.log(reg1.exec(str)); //匹配到两个a
console.log(reg2.exec(str)); //null,aaa_aa中间不连续,所以无法匹配
只匹配一个的话,g和y上没有什么区别的
u修饰符
js使用UTF-16、16进制的Unicode编码,范围U+0000到U+FFFF上常用字符(2个字节)
有些汉字在上面的范围里表示不了,比如说可能会有这样的情况\u20bb7,5位的码点表示方式,但是浏览器识别不了,就需要4个字节表示,会有一部分内容和正常范围里的字节重复(映射),比如说\uD83D和\uD83D\uDC2A
在U+D800到U+DFFF范围内就超出正常部分了,可能会有\uD83D\uDC2A代表一个字符的情况出现
在ES5中,下列情况是有错误的,实际上\uD83D和\uD83D\uDC2A根本不是一个东西
console.log(/^\uD83D/.test('\uD83D\uDC2A')); //true
ES6就弥补了这个缺点
console.log(/^\uD83D/u.test('\uD83D\uDC2A')); //false
当编码超出U+0000到U+FFFF的范围之后,就超出能够匹配的物理极限了,.也访问不到它
var s = '\uD83D\uDC2A';
console.log(/^.$/.test(s)); //false
console.log(/^.$/u.test(s)); //true
并且ES6又进行了进一步优化,只要加{}就可以识别5位的码点表示方式
console.log('\u{20bb7}')
括号内只写两位数,也会自动补全
console.log('\u{0041}\u{0042}\u{0043}'); //ABC
console.log('\u{41}\u{42}\u{43}'); //ABC
s修饰符
.无法匹配\n和\r,加s修饰符即可,这样.就真的可以代表一切了
console.log(/foo.bar/.test('foo\nbar')); //s
console.log(/foo.bar/s.test('foo\nbar')); //s
source返回正则的主体,flags返回所有修饰符
var reg = /\wabc/giy;
console.log(reg.source); // /\wabc/
console.log(reg.flags); //giy
字符串方法
codePointAt()
var s = '\u{20bb7}';
console.log(s.length); //2
js内部上由UTF-16的编码方式,所以\u{20bb7}要转成\uD842\uDC2A,一个\uxxxx代表一个字符,所以总共是两个字节。
ES5用charAt输出索引对应的字符
console.log(s.charAt(0)); //乱码
console.log(s.charAt(1)); //乱码
以为这两个字符分开来分别没有意义,不能表示一个字符,所以会乱码
charCodeAt输出索引对应的字符0进制码点
console.log(s.charCodeAt(0)); //55362 返回10进制的码点
console.log(s.charCodeAt(1)); //57271
转换进制
console.log(Number.prototype.toString.call(55362, 16)); //d842
console.log(Number.prototype.toString.call(57271, 16)); //dfb7
ES5的处理方式不是很理想,当字符超过正常范围,就不太好用了。
ES6 codePointAt
var s = '𠮷a';
console.log(s.codePointAt(0)); //134071,\u20bb7
console.log(Number.prototype.toString.call(134071, 16)); //20bb7
console.log(s.codePointAt(1)); //57271,\uDC2A
console.log(s.codePointAt(2)); //91,a
console.log(s.length); //3
这个方法不管length多长,第0位就可以解析整个字符的编码
判断是否4个字节,一个字节占8个bit
function is32Bit(c){
return c.codePointAt(0) > 0xFFFF;
}
console.log(is32Bit('𠮷')); //true
codePointAt方法返回的值可以直接和16进制的值进行比较,当我们直接打印16进制的值的时候,控制台打印出来是10进制,所以大家都是10进制,可以互相比对
String.fromCodePoint
传入码点,返回字符
之前是用fromCharCode,超出正常范围就会乱码
console.log(String.fromCharCode(0x20bb7)); //ஷ
console.log(String.fromCharCode(0x20bb7) === String.fromCharCode(0x0bb7));
它的处理方式就是舍弃最高位‘2’
String.fromCodePoint就没有这个问题
console.log(String.fromCodePoint(0x20bb7)); //𠮷
迭代器
在字符串原型上有Symbol的iterator接口,可以通过for of循环
既然上迭代器,就可以抽取字符
for(let value of s){
console.log(value); //𠮷 a
}
for of可以处理超出字符极限的值
let str = String.fromCodePoint(0x20bb7);
for(let i = 0; i < str.length; i++){
console.log(str[i]); //两个乱码
}
for(let i of str){
console.log(i); //𠮷
}
includes(), startWith(), endWith()
返回布尔值,是否在这几个方法的条件下包含某些字符
let s = "hello world!";
console.log(s.startWith("hello"));
console.log(s.endWith("!"));
console.log(s.includes("o"));
repeat()
将原本的字符串重复n次,有小数的话直接舍弃小数,NaN是0
console.log("x".repeat(3)); //xxx
console.log("x".repeat('3')); //xxx
console.log("x".repeat(3.9)); //xxx
console.log("x".repeat(NaN)); //
console.log("x".repeat(0)); //
padStart() padEnd() 填充
console.log("x".padStart(5, "ab")); //ababx
填充之后的位数是5位
console.log("x".padStart(4, "ab")); //abax
console.log("x".padEnd(5, "ab")); //xabab
模板字符串
之前我们使用正则替换模板达到目的
模板字符串基本用法
let name = 'web';
let info = 'developer';
let m = `I am a ${name} ${info}`; //I am a web developer
之前是用字符串拼接的方法
let m = 'I am a ' + name + '' + info;
还要注意的是{}内部是表达式,表达式意味着可以运算并且传入方法
let x = 1;
let y = 2;
console.log(`${x} + ${y} = ${x + y}`); //1 + 2 = 3
let obj = {x: 1, y: 2};
console.log(`${obj.x} + ${obj.y} = ${obj.x + obj.y}`);//1 + 2 = 3
function fn(){
return 'hello world';
}
console.log(`function result ${fn()}`); //function result hello world
函数执行返回的任何结果会被转换为字符串
function fn(){
return [1,2,3,4];
}
console.log(`function result ${fn()}`); //function result 1,2,3,4
字符串和模板的嵌套
let msg = `hello, ${'place'}`;
console.log(msg); //hello, place
模板渲染
const temp = arr1 => `
<table>
${
arr1.map( addr => `
<tr><td>${addr.first}</td></tr>
<tr><td>${addr.last}</td></tr>
`)
}
</table>
`;
const data = [ {first: 'zhang', last: 'san'}, {first: 'li', last: 'si'}];
console.log(temp(data));
控制台打印
<table>
<tr><td>zhang</td></tr>
<tr><td>san</td></tr>
,
<tr><td>li</td></tr>
<tr><td>si</td></tr>
</table>
返回逗号上因为map方法返回的也是一个数组,转换为字符串会有一个逗号
const temp = arr1 => `
<table>
${
arr1.map( addr => `
<tr><td>${addr.first}</td></tr>
<tr><td>${addr.last}</td></tr>
`).join('')
}
</table>
`;
在map方法后面加一个jion方法,以空的方式连接,就不会有逗号了
但是这种方式可能会被截取数据,恶意注入代码
const data = [
{first: 'zhang', last: '<script>alert('abc')</script>'},
{first: 'li', last: 'si'}
];
所以标签模板出现了,标签模板可以拿到模板字符串里面的变量
let x = 1;
let y = 2;
console.log(`hello ${x + y} world ${x * y}`);
tag`hello ${x + y} world ${x * y}`
标签模板的执行,这个就相当于函数执行tag(hello {x * y})
这样就可以定义函数返回什么
function tag($, $1, $2){
console.log($, $1, $2)
}
控制台打印
1和$2对应表达式输出的结果
这样就可以控制传入的数据变量