“Yeah It’s on. ”
正文
rest参数
Rest操作符和Spread操作都是用三个点(…)表示,但作用整好相反。
Rest操作符一般用在函数参数的声明中,而Spread用在函数的调用中。
'use strict';
function func(...args){
console.log(args);//[1,2,3,4]
}
func(1,2,3,4);
我们知道在strict mode下,对arguments做了很多限制,而且arguments是个arrayLike对象,不能像操作数组那样直接操作它。但用Rest操作符以后,args参数就是一个数组了,任何操作数组的方法都可以直接对args使用。
Object.is
在这之前我们比较值使用两等号 == 或 三等号===, 三等号更加严格,只要比较两方类型不同立即返回false。
另外,有且只有一个值不和自己相等,它是NaN
NaN == NaN //false
现在ES6又加了一个Object.is,让比较运算的江湖更加混乱。多数情况下Object.is等价于“===”,如下
1 === 1 // true
Object.is(1, 1) // true
'a' === 'a' // true
Object.is('a', 'a') // true
true === true // true
Object.is(true, true) // true
null === null // true
Object.is(null, null) // true
undefined === undefined // true
Object.is(undefined, undefined) // true
但对于NaN、0、+0、 -0,则和 “===” 不同
NaN === NaN // false
Object.is(NaN, NaN) // true
0 === -0 // true
Object.is(0, -0) // false
-0 === +0 // true
Object.is(-0, +0) // false
其他
在js中我们一般认为正负0是不相等的,但是 -0 == +0 会返回true,那么怎么判断正负0 代码如下
(判断-0)
function(num){
return num == 0 && 1 / num < 0
}
(判断+0)
function(num){
return num == 0 && 1 / num > 0
}
Decorator
http://es6.ruanyifeng.com/#docs/decorator
https://juejin.im/entry/5b74e0525188256143054663
装饰器是一种函数,写成@ + 函数名。它可以放在类和类方法的定义前面。
例子:
@frozen class Foo {
@configurable(false)
@enumerable(true)
method() {}
@throttle(500)
expensiveMethod() {}
}
类的装饰
@eat
class Person {
constructor() {}
}
function eat(target, key, descriptor) {
console.log('吃饭');
console.log(target);
console.log(key);
console.log(descriptor);
target.prototype.act = '我要吃饭';
}
const jack = new Person();
console.log(jack.act);
或者return一个函数,函数里面return一个classDescriptor
let classFactory = (name, fn) => {
return function (classDescriptor) {
debugger
console.log("classDescriptor", classDescriptor)
let {elements} = classDescriptor;
return {
...classDescriptor,
elements: elements.concat([methodDescriptorGenerator(name, fn)]),
};
};
};
const Wxapi = classFactory('$wxapi', ()=>{
console.log("aaaa")
});
@Wxapi
class Person {
constructor() {
}
aaa(){}
bbb(){}
}
我们可以看到打印出来classDescriptor是
{
kind: 'class',
elements:
[ Object [Descriptor] {
kind: 'method',
key: 'aaa',
placement: 'prototype',
descriptor: [Object] },
Object [Descriptor] {
kind: 'method',
key: 'bbb',
placement: 'prototype',
descriptor: [Object] }
]
}
这样子我们可以对elements进行补充,也就是增加类方法
return {
...classDescriptor,
elements: elements.concat([methodDescriptorGenerator(name, fn)]),
};
我们去看methodDescriptorGenerator方法
let methodDescriptorGenerator = (name, fn, placement = 'prototype') => {
return {
key: name,
kind: 'method',
placement,
descriptor: descriptorGenerator({value: fn}),
};
};
return的对象刚好和elements中的一项数据结构对齐
其中descriptor是一个描述对象
我们继续看descriptorGenerator方法
const descriptorGenerator = (des) => {
return {
enumerable: true,
writable: true,
configurable: true,
...des,
};
};
这里最重要的就是des传入的对象要有value,这里value是一个函数
上面写的比较复杂,我们来看一个简单的例子
function addFly(canFly){
return function(target){
target.canFly = canFly;
let extra = canFly ? '(技能加成:飞行能力)' : '';
let method = target.prototype.toString;
target.prototype.toString = (...args)=>{
return method.apply(target.prototype,args) + extra;
}
return target;
}
}
@addFly(true)
class Man{
constructor(def = 2,atk = 3,hp = 3){
this.init(def,atk,hp);
}
通过target.prototype.xxxx就可以在类上面增加方法,非常简单。
方法的装饰
class Math {
@log
add(a, b) {
return a + b;
}
}
function log(target, name, descriptor) {
var oldValue = descriptor.value;
descriptor.value = function() {
console.log(`Calling ${name} with`, arguments);
return oldValue.apply(this, arguments);
};
return descriptor;
}
const math = new Math();
// passed parameters should get logged now
math.add(2, 4);
或者return一个函数,函数里面return一个descriptor
/**
* 延时执行
* @param wait
*/
function Delay(wait = 500) {
return function (target, name, descriptor) {
let fn = descriptor.value;
descriptor.value = delay(fn, wait);
return descriptor;
};
}
一个非常重要的点就是:this的绑定
func.apply(this, args)
/**
* 延迟执行
* @param func
* @param delay
* @returns {Function}
*/
function delay(func, delay = 500) {
return function (...args) {
setTimeout(() => {
func.apply(this, args)
}, delay)
}
}
深入Decorator
Decorators 的本质是利用了 ES5 的 Object.defineProperty 属性,这三个参数其实是和 Object.defineProperty 参数一致的,因此不能更改
可以看看 bable 转换后 的代码,其中有一句是 descriptor = decorator(target, key, descriptor) | descriptor; |
箭头函数
隐式返回值
要返回多行语句(例如对象文本),需要使用( )而不是{ }来包裹函数体。这样可以确保代码以单个语句的形式进行求值。
function calcCircumference(diameter) {
return Math.PI * diameter
}
// 简写为:
calcCircumference = diameter => (
Math.PI * diameter;
)
注意点
- 函数体内的this对象就是定义时所在的对象,而不是使用时所在对象(这点很重要)
- 不可以当作构造函数使用,也就是不能用new命令实例化一个对象,否则会抛出一个错误;
- 不可以使用arguments对象,该对象在函数体内不存在,如果要用的话,可以用rest参数代替;
- 不可以使用yield命令,箭头函数不能用作Generator函数;
例子
箭头函数:
var x=11;
var obj={
x:22,
say:()=>{
console.log(this.x);
}
}
obj.say();
//输出的值为11
所谓的定义时候绑定,就是this是继承自父执行上下文!!中的this,比如这里的箭头函数中的this.x,箭头函数本身与say平级以key:value的形式,也就是箭头函数本身所在的对象为obj,而obj的父执行上下文就是window,因此这里的this.x实际上表示的是window.x,因此输出的是11。(this只有在函数被调用,或者通过构造函数new Object()的形式才会有this)
var a=11;
function test2(){
this.a=22;
let b=()=>{console.log(this.a)}
b();
}
var x=new test2();
//输出22
很奇怪对不对,我是这样理解的,ES6中定义的时候绑定this的具体含义,应该继承的是父执行上下文里面的this
注意:简单对象(非函数)是没有执行上下文的!
箭头函数中,this指向的固定化,并不是因为箭头函数内部有绑定this的 机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外 层代码块的this。正是因为它没有this,所以也就不能用作构造函数。
我们可以来模拟ES5中的箭头函数转化:
// ES6
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
// ES5
function foo() {
var _this = this;
setTimeout(function () {
console.log('id:', _this.id);
}, 100);
}
非箭头函数
var a=11
function test1(){
this.a=22;
let b=function(){
console.log(this.a);
};
b();
}
var x=new test1();
// 输出11
var fullname = 'John Doe';
var obj = {
fullname: 'Colin Ihrig',
prop: {
fullname: 'Aurelio De Rosa',
getFullname: function() {
return this.fullname;
}
}
};
console.log(obj.prop.getFullname());
// Aurelio De Rosa
var test = obj.prop.getFullname;
console.log(test());
// John Doe
JavaScript中关键字this所引用的是函数上下文,取决于函数是如何调用的,而不是怎么被定义的。
在第一个console.log(),getFullname()是作为obj.prop对象的函数被调用。因此,当前的上下文指代后者,并且函数返回这个对象的fullname属性。相反,当getFullname()被赋值给test变量时,当前的上下文是全局对象window,这是因为test被隐式地作为全局对象的属性
class A {
constructor() {
this.b = this.b.bind(this);
}
a() {
console.log('a');
}
b() {
console.log('b')
}
c = () => {
console.log('c')
}
}
babel编译后
"use strict";
function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return !!right[Symbol.hasInstance](left); } else { return left instanceof right; } }
function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
// 向Constructor.prototype中添加东西
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
var A = /*#__PURE__*/function () {
function A() {
_classCallCheck(this, A);
// 箭头函数编译后
_defineProperty(this, "c", function () {
console.log('c');
});
this.b = this.b.bind(this);
}
_createClass(A, [{
key: "a",
value: function a() {
console.log('a');
}
}, {
key: "b",
value: function b() {
console.log('b');
}
}]);
return A;
}();
-
普通函数: 在 babel 编译后,会被放在函数的 prototype 上
-
constructor 里 bind 的函数: 在编译后,它不仅会被放在函数的 prototype 里,而且每一次实例化,都会产生一个绑定当前实例上下文的变量(this.b = this.b.bind(this))。
-
箭头函数:在 babel 编译后,每一次实例化的时候,都会调用 defineProperty 将箭头函数内容绑定在当前实例上下文上。
从编译后的结果来看的话,对于实际开发的时候,如果需要绑定上下文的话,最好还是用箭头函数。因为使用 bind 方式的话,不仅会产生一个 prototype 的函数,每一次实例化都会额外产生多一个函数。
引申出class知识点
class 对于 = 号声明的方法、变量,都会将其作为实例的属性,而对于非 = 号声明的属性,则是放在原型链上
class A {
a() {
}
b = 2;
c = () => {
}
}
对于这个类, 在实例化的时候,b, c 会作为实例的属性,而 a 则是放在原型链上。
举个例子:
class Yideng {
static str = "京程一灯"
// 赋值 会将其作为实例的属性
sayStr = () => {
throw new Error("Need to implement")
}
}
class Student extends Yideng {
constructor(){
super()
}
// 声明的属性 放在原型链上
sayStr(){
console.log(Student.str)
}
}
const laoyuan = new Student()
console.log(Student.str) // 京程一灯
// 先找实例的属性再去原型链找 (存在实例属性)
laoyuan.sayStr() // Error("Need to implement")
class
es6 的类在初始化的时候,如果有基类会先执行基类的构造函数,之后再执行本身的构造函数。
this 的指向
观察最近写的框架插件,思考了一下js元解析器解析插件中链式调用的函数会存在重复调用的问题。
最终,发现可能是this指向的问题
更改前
class BucketControl {
static getInstance(bucketConfig) {
if (!BucketControl.instance) {
BucketControl.instance = new BucketControl(bucketConfig);
}
return BucketControl.instance;
}
constructor(bucketConfig = {}) {
let buckets = {};
Object.keys(bucketConfig).forEach(key => {
buckets[key] = new Bucket({name: key, ...bucketConfig[key]});
});
this.buckets = buckets;
this.init();
}
....
}
module.exports = function (config) {
// 这里之所以要设置成类 是因为bucketControl初始化要依赖一个config
let bucketControl = BucketControl.getInstance(config);
return {
name: 'bucketControl',
customMethod: {
add: bucketControl.add.bind(bucketControl),
addRead: bucketControl.addRead.bind(bucketControl),
remove: bucketControl.remove.bind(bucketControl),
choose: bucketControl.choose.bind(bucketControl),
print: bucketControl.print.bind(bucketControl),
delAll: bucketControl.delAll.bind(bucketControl),
delRead: bucketControl.delRead.bind(bucketControl),
},
};
};
更改后:
class BucketControl {
static getInstance(bucketConfig) {
if (!BucketControl.instance) {
BucketControl.instance = new BucketControl(bucketConfig);
}
return BucketControl.instance;
}
constructor(bucketConfig = {}) {
let buckets = {};
Object.keys(bucketConfig).forEach(key => {
buckets[key] = new Bucket({name: key, ...bucketConfig[key]});
});
this.buckets = buckets;
// this 的指向 (防止函数被单独拿出来使用)
// http://es6.ruanyifeng.com/#docs/class
this.add = this.add.bind(this);
this.addRead = this.addRead.bind(this);
this.remove = this.remove.bind(this);
this.choose = this.choose.bind(this);
this.print = this.print.bind(this);
this.delAll = this.delAll.bind(this);
this.delRead = this.delRead.bind(this);
this.init();
}
...
}
module.exports = function (config) {
// 这里之所以要设置成类 是因为bucketControl初始化要依赖一个config
let bucketControl = BucketControl.getInstance(config);
return {
name: 'bucketControl',
customMethod: {
add: bucketControl.add,
addRead: bucketControl.addRead,
remove: bucketControl.remove,
choose: bucketControl.choose,
print: bucketControl.print,
delAll: bucketControl.delAll,
delRead: bucketControl.delRead,
},
};
};
类的方法内部如果含有this,它默认指向类的实例。但是,必须非常小心,一旦单独使用该方法,很可能报错。
class Logger {
printName(name = 'there') {
this.print(`Hello ${name}`);
}
print(text) {
console.log(text);
}
}
const logger = new Logger();
const { printName } = logger;
printName(); // TypeError: Cannot read property 'print' of undefined
上面代码中,printName方法中的this,默认指向Logger类的实例。但是,如果将这个方法提取出来单独使用,this会指向该方法运行时所在的环境(由于 class 内部是严格模式,所以 this 实际指向的是undefined),从而导致找不到print方法而报错。
一个比较简单的解决方法是,在构造方法中绑定this,这样就不会找不到print方法了。
class Logger {
constructor() {
this.printName = this.printName.bind(this);
}
// ...
}
类方法不可枚举
最近在仿写axios,在写Axios类的时候发现类方法不可枚举
例子
class Axios {
constructor() {
this.ccc = "ccc"
}
aaa(aaa) {
console.log(aaa)
}
bbb(bbb) {
console.log(bbb)
}
request() {
console.log("request")
}
}
let aaa = new Axios()
for (let item in aaa) {
console.log("item", item)
}
// 输出 "ccc"
按理说class只是一个语法糖,aaa,bbb这些存在于Axios.prototype之中,使用for…in是可以打印出来的,但是由于类方法不可枚举,所以这里只能打印出”ccc”
这一点与 ES5 的行为不一致。 这一点与 ES5 的行为不一致。 这一点与 ES5 的行为不一致。
如果我真的很想打印出”aaa”这些,该怎么办?
方法一:
改成function的形式
function Axios() {
}
Axios.prototype.aaa = () => {
console.log("aaa")
}
方法二:
将类方法bind到类实例的this上
class Axios {
constructor() {
this.ccc = "ccc"
this.aaa = this.aaa.bind(this)
}
aaa(aaa) {
console.log(aaa)
}
}
static
在class中
- static属性会定义在实例的 constructor上 (也就是类转义得到的函数上)
- 普通属性会定义在实例上
来看一个例子:
class Yideng {
str = "京城一等"
static aaa="aaa"
}
const obj = new Yideng()
console.log(obj.aaa) // undefined
console.log(obj.constructor.aaa) //aaa
console.log(Yideng.aaa) //aaa
console.log(obj.constructor == Yideng) // true
console.log(obj.__proto__ == Yideng.prototype) // true
经过babel转义后
"use strict";
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
var Yideng = function Yideng() {
_classCallCheck(this, Yideng);
// 定义在实例上
_defineProperty(this, "str", "京城一等");
};
// 定义在this.constructor上
_defineProperty(Yideng, "aaa", "aaa");
计算属性
let propKey = 'foo';
let obj = {//ES6
[propKey]: true,
['b'+'ar']: 123
};
//ES6之前只能这样写:
obj[propKey + "1"] = false;
obj['f'+'oo2'] = 456;
console.log(obj);//Object { foo: true, bar: 123 }
ES6之前要想用变量当作对象的属性名称,必须先声明一个字面量,然后在用中括号语法赋值,ES6中直接可以在声明的时候就使用中括号语法来使用变量。
对象的方法一样可以使用这样写法:
et obj = {
['h'+'ello']() {
return 'hi';
}
};
console.log(obj.hello()); // hi
在Vuex中的mutations里
const mutations = {
[types.SET_SINGER](state, singer) {
state.singer = singer
},
[types.SET_PLAYING_STATE](state, flag) {
state.playing = flag
},
[types.SET_FULL_SCREEN](state, flag) {
state.fullScreen = flag
}
}
Proxy
https://es6.ruanyifeng.com/#docs/proxy
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
var obj = new Proxy({}, {
get: function (target, propKey, receiver) {
console.log(`getting ${propKey}!`);
return Reflect.get(target, propKey, receiver);
},
set: function (target, propKey, value, receiver) {
console.log(`setting ${propKey}!`);
return Reflect.set(target, propKey, value, receiver);
}
});
Proxy 缺点
this 问题
虽然 Proxy 可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。主要原因就是在 Proxy 代理的情况下,目标对象内部的this
关键字会指向 Proxy 代理。
Proxy 拦截函数内部的this
,指向的是handler
对象。
Reflect
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
vue3 proxy劫持数据的幕后支持者之一。
反射的原理是通过编译阶段对对象注入元数据信息,在运行阶段读取注入的元数据,从而得到对象信息。
将Object
对象的一些明显属于语言内部的方法(比如Object.defineProperty
),放到Reflect
对象上。让Object
操作都变成函数行为。某些Object
操作是命令式,比如name in obj
和delete obj[name]
,而Reflect.has(obj, name)
和Reflect.deleteProperty(obj, name)
让它们变成了函数行为。
元数据反射(Reflect Metadata) 是 ES7 的一个提案,它主要用来在声明的时候添加和读取元数据。
要在 ts 中启用元数据反射相关功能需要:
npm i reflect-metadata --save
。- 在
tsconfig.json
里配置emitDecoratorMetadata
选项为true
。
内置元数据
TypeScript 结合自身语言的特点,为使用了装饰器的代码声明注入了 3 组元数据:
design:type
:成员类型design:paramtypes
:成员所有参数类型design:returntype
:成员返回类型
reflect-metadata
https://www.npmjs.com/package/reflect-metadata
Reflect Metadata 是 ES7 的一个提案,它主要用来在声明的时候添加和读取元数据,可以被用于类,类成员以及参数。
reflect-metadata
是一种增强描述,我们可以在不侵入原类、原函数或者原属性的前提下,对其进行额外的描述,例如这样:
class A {
b: {}
}
Reflect.defineMetadata('test', '1', A)
Reflect.defineMetadata('testb', '2', A, 'b')
console.log(Reflect.getMetadata('test', A)) // '1'
console.log(Reflect.getMetadata('testb', A, 'b')) // '2'
Symbol
symbol 英文意思为 符号、象征、标记、记号,在 js 中更确切的翻译应该为 独一无二的值,理解了它的意思之后再看起来就简单多了。
可以通过下面的方式来创建一个 symbol 类型的值
const s = Symbol();
console.log(typeof s); // "symbol"
需要注意的是通过 Symbol 方法创建值的时候不用使用 new 操作符,原因是通过 new 实例化的结果是一个 object 对象,而不是原始类型的 symbol
Symbol 方法接收一个参数,表示对生成的 symbol 值的一种描述
const s = Symbol('foo');
即使是传入相同的参数,生成的 symbol 值也是不相等的,因为 Symbol 本来就是独一无二的意思
const foo = Symbol('foo');
const bar = Symbol('foo');
console.log(foo === bar); // false
Symbol.for 方法可以检测上下文中是否已经存在使用该方法且相同参数创建的 symbol 值,如果存在则返回已经存在的值,如果不存在则新建。
const s1 = Symbol.for('foo');
const s2 = Symbol.for('foo');
console.log(s1 === s2); // true
Symbol.keyFor 方法返回一个使用 Symbol.for 方法创建的 symbol 值的 key
const foo = Symbol.for("foo");
const key = Symbol.keyFor(foo);
console.log(key) // "foo"
使用场景
消除魔法字符
假如现有一个 Tabs 切换的功能
if (type === 'basic') {
return <div>basic tab</div>
}
if (type === 'super') {
return <div>super tab</div>
}
复制代码
上面代码中字符串 basic、super 就是与业务代码无关的魔法字符,接下来使用 Symbol 对这块代码进行改造
const tabTypes = {
basic: Symbol(),
super: Symbol(),
}
if (type === tabTypes.basic) {
return <div>basic tab</div>
}
if (type === tabTypes.super) {
return <div>super tab</div>
}
作为对象属性
当一个复杂对象中含有多个属性的时候,很容易将某个属性名覆盖掉,利用 Symbol 值作为属性名可以很好的避免这一现象。
const name = Symbol('name');
const obj = {
[name]: 'ClickPaas',
}
Symbol.iterator部署遍历器生成方法
https://es6.ruanyifeng.com/#docs/iterator
对于原生部署 Iterator 接口的数据结构,不用自己写遍历器生成函数,for...of
循环会自动遍历它们。除此之外,其他数据结构(主要是对象)的 Iterator 接口,都需要自己在Symbol.iterator
属性上面部署,这样才会被for...of
循环遍历。
class RangeIterator {
constructor(start, stop) {
this.value = start;
this.stop = stop;
}
[Symbol.iterator]() { return this; }
next() {
var value = this.value;
if (value < this.stop) {
this.value++;
return {done: false, value: value};
}
return {done: true, value: undefined};
}
}
function range(start, stop) {
return new RangeIterator(start, stop);
}
for (var value of range(0, 3)) {
console.log(value); // 0, 1, 2
}
上面代码是一个类部署 Iterator 接口的写法。Symbol.iterator
属性对应一个函数,执行后返回当前对象的遍历器对象。
下面是通过遍历器实现指针结构的例子。
function Obj(value) {
this.value = value;
this.next = null;
}
Obj.prototype[Symbol.iterator] = function() {
var iterator = { next: next };
var current = this;
function next() {
if (current) {
var value = current.value;
current = current.next;
return { done: false, value: value };
} else {
return { done: true };
}
}
return iterator;
}
var one = new Obj(1);
var two = new Obj(2);
var three = new Obj(3);
one.next = two;
two.next = three;
for (var i of one){
console.log(i); // 1, 2, 3
}
下面是另一个类似数组的对象调用数组的Symbol.iterator
方法的例子。
let iterable = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
console.log(item); // 'a', 'b', 'c'
}
手写symbol
https://github.com/mqyqingfeng/Blog/issues/87
function SymbolPolyfill(description) {
// 不能使用new
if (this instanceof SymbolPolyfill) {
throw new TypeError('Symbol is not a constructor')
}
const descStr = description == undefined ? undefined : String(description)
const symbol = Object.create(null)
Object.defineProperties(symbol, {
__Description__: {
value: descStr,
writable: false,
enumerable: false,
configurable: false,
},
})
// 返回的是一个新对象,两个对象之间,只要引用不同,就不会相同
return symbol
}
Array
find和findIndex
数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。
[1, 4, -5, 10].find((n) => n < 0)
// -5
[1, 5, 10, 15].find(function(value, index, arr) {
return value > 9;
}) // 10
上面代码中,find方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。
数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
[1, 5, 10, 15].findIndex(function(value, index, arr) {
return value > 9;
}) // 2
这两个方法都可以接受第二个参数,用来绑定回调函数的this对象。 另外,这两个方法都可以发现NaN,弥补了数组的IndexOf方法的不足。
[NaN].indexOf(NaN)
// -1
[NaN].findIndex(y => Object.is(NaN, y))
// 0
上面代码中,indexOf方法无法识别数组的NaN成员,但是findIndex方法可以借助Object.is方法做到。
Array.from
在 JavaScript
中有很多克隆数组的方法。正如你所想,Array.from()
可以很容易的实现数组的浅拷贝。
const numbersCopy = Array.from(numbers);
numbers === numbersCopy; // => false
Array.from(numbers)
创建了对 numbers
数组的浅拷贝,numbers === numbersCopy
的结果是 false
,意味着虽然 numbers
和 numbersCopy
有着相同的项,但是它们是不同的数组对象。
for in
遍历对象 通常用for in来遍历对象的键名
for in 可以遍历到myObject的原型方法method,如果不想遍历原型方法和属性的话,可以在循环内部判断一下,hasOwnPropery方法可以判断某属性是否是该对象的实例属性
for (var key in myObject) {
if(myObject.hasOwnProperty(key)){
console.log(key);
}
}
可以通过ES5的Object.keys(myObject)获取对象的实例属性组成的数组,不包括原型方法和属性
for..of和for..in可以正确响应break、continue和return语句
forEach
无法return和break
我们都知道for循环里要跳出整个循环是使用break,但在数组中用forEach循环如要退出整个循环使用break会报错,使用return也不能跳出循环。
之所以不能break, return
是因为它不是简单的for循环。内部实现类似callback(context, arg1, arg2)。
Array.prototype.forEach = function (callback) {
// this represents our array
for (let index = 0; index < this.length; index++) {
// We call the callback for each entry
callback(this[index], index, this);
}
}
async/await 与 forEach 也没法结合使用
map
const result = [{ aa: "aa" }, { bb: "bb" }]
const res = result.map(item => item)
res[0].aa = "test"
console.log(res) // 改变
console.log(result) // 同时改变
证明map循环中是浅拷贝
正确用法:
const result = [{ aa: "aa" }, { bb: "bb" }]
const res = result.map(item => ({
...item
}))
res[0].aa = "test"
console.log(res) // 改变
console.log(result) // 不会改变
forEach和map
map方法体现的是数据不可变的思想。该思想认为所有的数据都是不能改变的,只能通过生成新的数据来达到修改的目的,因此直接对数组元素或对象属性进行操作的行为都是不可取的。这种思想其实有很多好处,最直接的就是避免了数据的隐式修改。immutable.js是实现数据不可变的一个库,可通过专属的API对引用类型进行操作,每次形成一个新的对象。
forEach和map还存在一个编程思想的区别,前者是命令式编程,后者是声明式编程,如果项目的风格是声明式的,比如React,那么后者显然更统一。
共同点:
- 都是循环遍历数组中的每一项。
- forEach()和map()里面每一次执行匿名函数都支持3个参数:数组中的当前项item,当前项的索引index,原始数组input。
- 匿名函数中的this都是指Window。
- 只能遍历数组。
try catch能捕获到哪些 JS 异常
能捕捉到的异常, 必须是在报错的时候 ,线程执行已经进入 try catch 但 try catch 未执行完的时候抛出来的。
异步方法无法捕捉到
try{
setTimeout(()=>{
console.log(a.b);
},100)}catch(e){
console.log('error',e);
}
console.log(111);
//output111Uncaught ReferenceError: a is not defined
setTimeout 里面报错,实际上是 100ms 之后执行的代码报错,此时代码块 try catch 已经执行完成,111 都已经被执行了,故无法捕捉异常。
一个特殊的例子:
try {
new Promise(function (resolve, reject) {
a.b;
}).then((v) => {
console.log(v);
});
} catch (e) {
console.log("error", e);
}
// outputUncaught (in promise) ReferenceError: a is not defined
看如上报错,线程在执行 a.b 的时候,事实上属于同步执行,try catch 并未执行完成,按理应该能捕捉到异常,这里为啥无法捕捉呢?
事实上,Promise 的异常都是由 reject 和 Promise.prototype.catch 来捕获,不管是同步还是异步。
核心原因是因为 Promise 在执行回调中都用 try catch 包裹起来了,其中所有的异常都被内部捕获到了,并未往上抛异常。
不要用 try catch 包裹 Promise , Promise 很强大,不用担心异常会往上抛!我们只需要给 Promise 增加 Promise.prototype.catch 就 OK 了
Generator
Generator 是 ES6 推出的一种新的数据类型,它本质上是 JS 协程的一种实现
什么是协程
https://www.liaoxuefeng.com/wiki/897692888725344/923057403198272
协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。
正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程。
举个例子:
注意,在一个子程序中中断,去执行其他子程序,不是函数调用,有点类似CPU的中断。比如子程序A、B:
def A():
print '1'
print '2'
print '3'
def B():
print 'x'
print 'y'
print 'z'
假设由协程执行,在执行A的过程中,可以随时中断,去执行B,B也可能在执行过程中中断再去执行A,结果可能是:
1
2
x
y
3
z
但是在A中是没有调用B的,所以协程的调用比函数调用理解起来要难一些。
看起来A、B的执行有点像多线程,但协程的特点在于是一个线程执行
- 最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
- 第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
补充
CORS blocked when type=“module”
https://forum.freecodecamp.org/t/cors-blocked-when-i-use-type-module/340229
<script type="module" src="../toy-react/day2/toy-react.js"></script>
当我们使用 type=”module”引入一个es6模块的本地js,会发生跨域问题
解决:
开一个本地服务器。