ES6新增API

932 阅读16分钟

函数名称扩展

  1. 函数表达式的函数名问题:

在ES5及其之前,因为函数赋值的变量(f)本身是没有name属性的,所以打印函数名都是'',都是ES6可以打印出函数名的

var f = function(){}
console.log(f.name); //f
  1. new Function()问题:

我们在声明一个函数的时候,实际上都实例化了一个Function构造函数,平常不建议用new Function声明函数。

但是需要知道的是new Function的name属性是不一样的

console.log(new Function().name); //anonymous(匿名)
  1. 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]]
  1. getter 还是 setter
  2. writable:false,不让你改
  3. 赋值

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()是否安全整数

正则拓展

  1. 声明正则的变化方式

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'); //这种情况下一个参数的修饰符失效,以后面的参数为准
  1. 字符串上的正则方法进行了调整

正则原型上有如下方法:

和字符串的方法一样,只是原本这些方法上写在String的原型上的,ES6把他们调整到正则的原型上了

String.prototype.match -> RegExp.prototype[Symbol.match]
  1. 新增的修饰符u y s
新增修饰符说明
ysticky粘黏,匹配到连续的字符
u匹配Unicode编码
sdotAll让.代表一切

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+yworld{x + y} world {x * y})

这样就可以定义函数返回什么

function tag($, $1, $2){
	console.log($, $1, $2)
}

控制台打印

以变量为分隔,分隔字符串形成数组,32分别上以变量为分隔,分隔字符串形成数组,3和2分别上1和$2对应表达式输出的结果

这样就可以控制传入的数据变量