kangkai124 / blog

开发笔记
https://kangkai124.github.io/blog/
MIT License
26 stars 4 forks source link

js 知识点及问题汇总(一) #4

Open kangkai124 opened 5 years ago

kangkai124 commented 5 years ago

js 从入门到继续学习

kangkai124 commented 5 years ago

数字转化为千分位格式

function toThousands(num) {
    var num = (num || 0).toString(), result = '';
    while (num.length > 3) {
        result = ',' + num.slice(-3) + result;
        num = num.slice(0, num.length - 3);
    }
    if (num) { result = num + result; }
    return result;
}
kangkai124 commented 5 years ago

Array.from

const getItems = count =>

  Array.from({ length: count }, (v, k) => k).map(k => ({

    id: item-${k},

    content: item ${k},

  }));

上面没看懂,主要是 { length: count } 这里没看明白。

Array.from 方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。

查了一下才知道,原来 { length: count } 是类数组对象,再加一个例子就看明白了,如下:

let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};

// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']

// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

Array.from(arrayLike, (v, k) => v); // ["a", "b", "c"]
Array.from(arrayLike, (v, k) => k); // [0, 1, 2]
kangkai124 commented 5 years ago

Object.defineProperty(obj, prop, descriptor)

Parameters

Return value

The object that was passed to the function.

Property descriptors present in objects come in two main flavors: data descriptors and accessor descriptors. A data descriptor is a property that has a value, which may or may not be writable. An accessor descriptor is a property described by a getter-setter pair of functions. A descriptor must be one of these two flavors; it cannot be both.

configurable enumerable value writable get set
数据描述符 Yes Yes Yes Yes No No
存取描述符 Yes Yes No No Yes Yes

如果一个描述符不具有 value, writable, get 和 set 任意一个关键字,那么它将被认为是一个数据描述符。如果一个描述符同时有( value 或 writable)和( get 或 set)关键字,将会产生一个异常。

Bear in mind that these attributes are not necessarily the descriptor's own properties. Inherited properties will be considered as well. In order to ensure these defaults are preserved, you might freeze the Object.prototype upfront, specify all options explicitly, or point to nullwith Object.create(null).

MDN示例

kangkai124 commented 5 years ago

String.prototype.match()

const name = '@kk/eslint-plugin-myself'
const matchs = name.match(/^(@[^/]+)\/eslint-plugin(?:-(.*))?$/)
console.log(matchs)
/* 
[ '@kk/eslint-plugin-myself',
  '@kk',
  'myself',
  index: 0,
  input: '@kk/eslint-plugin-myself',
  groups: undefined ]
*/
kangkai124 commented 5 years ago

self 返回一个指向当前 window 对象的引用

if (window.parent.frames[0] != window.self) {
    // this window is not the first frame in the list
}
kangkai124 commented 5 years ago

检测当前环境下全局对象下的属性

function getGlobal(property) {
  if (typeof self !== 'undefined' && self && property in self) {
    return self[property];
  }

  if (typeof window !== 'undefined' && window && property in window) {
    return window[property];
  }

  if (typeof global !== 'undefined' && global && property in global) {
    return global[property];
  }

  if (typeof globalThis !== 'undefined' && globalThis) {
    return globalThis[property];
  }
};
kangkai124 commented 5 years ago

reduce 应用

计算数组中每个元素出现的次数

var names = ['kk', 'mivi', 'dd', 'minghao', 'kk', 'mivi', 'kk', 'dd']
var countss = names.reduce((allNames, name) => {
  if (name in allNames) {
    allNames[name] = allNames[name] + 1
  } else {
    allNames[name] = 1
  }
  return allNames
}, {})

// {kk: 3, mivi: 2, dd: 2, minghao: 1}

按属性把对象分类

var people = [
  { name: 'kk', age: 25 },
  { name: 'mivi', age: 29 },
  { name: 'dd', age: 27 },
  { name: 'minghao', age: 26 },
  { name: 'kk', age: 25 },
  { name: 'nana', age: 24 }
]
var groupBy = (objectArray, property) => (
  objectArray.reduce((acc, cur) => {
    var key = cur[property]
  if (!acc[key]) {
    acc[key] = []
  }
    acc[key].push(cur)
    return acc
  }, {})
)
var ageGroup = groupBy(people, 'age')

数组去重

let arr = [1,2,1,2,3,5,4,5,3,4,4,4,4];
let result = arr.sort().reduce((init, current)=>{
    if(init.length===0 || init[init.length-1]!==current){
        init.push(current);
    }
    return init;
}, []);
console.log(result); //[1,2,3,4,5]

功能型函数管道

// Building-blocks to use for composition
const double = x => x + x;
const triple = x => 3 * x;
const quadruple = x => 4 * x;

// Function composition enabling pipe functionality
const pipe = (...functions) => input => functions.reduce(
    (acc, fn) => fn(acc),
    input
);

// Composed functions for multiplication of specific values
const multiply6 = pipe(double, triple);
const multiply9 = pipe(triple, triple);
const multiply16 = pipe(quadruple, quadruple);
const multiply24 = pipe(double, triple, quadruple);

// Usage
multiply6(6); // 36
multiply9(9); // 81
multiply16(16); // 256
multiply24(10); // 240
kangkai124 commented 5 years ago

Object.prototype.hasOwnProperty()

hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性,该方法会忽略掉那些从原型链上继承到的属性。

kangkai124 commented 5 years ago

Class 中的 super

1、Class 中的 super(),它在这里表示父类的构造函数,用来新建父类的 this 对象

super() 相当于 Parent.prototype.constructor.call(this)

class Demo {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  customSplit() {
    return [...this.y]
  }

}

class Demo2 extends Demo {
  constructor(x, y) {
    super(x, y);
  }

  customSplit() {
    return [...this.x]
  }

  task1() {
    return super.customSplit();
  }

  task2() {
    return this.customSplit();
  }
}

let d = new Demo2('hello', 'world');
d.task1()   //["w", "o", "r", "l", "d"]
d.task2()   //["h", "e", "l", "l", "o"]

2、子类没有自己的 this 对象,而是继承父亲的 this 对象,然后进行加工。如果不调用 super,子类就得不到 this 对象

class Demo2 extends Demo{
  constructor(x,y){
    this.x = x;    //this is not defined
  }
 }

ES5 的继承,实质上是先创造子类的实例对象 this,然后再将父类的方法添加到 this 上(Parent.call(this)). ES6 的继承,需要先创建父类的 this,子类调用 super 继承父类的 this 对象,然后再加工。

如果子类没有创建 constructor,这个方法会被默认添加:

class Demo{ 
  constructor(x) {
     this.x = x;
   }
}

class Demo2 extends Demo{}

let d = new Demo2('hello');

d.x   // hello

3、super 在静态方法之中指向父类,在普通方法之中指向父类的原型对象

class Parent {
  static myMethod(msg) {
    console.log('static', msg);
  }
  myMethod(msg) {
    console.log('instance', msg);
  }
}
class Child extends Parent {
  static myMethod(msg) {
    super.myMethod(msg);
  }
  myMethod(msg) {
    super.myMethod(msg);
  }
}

Child.myMethod(1);  // static 1

var child = new Child();
child.myMethod(2);  // instance 2
kangkai124 commented 5 years ago

Promise.race()

Promise.race 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.race([p1, p2, p3]);

上面代码中,只要 p1、p2、p3 之中有一个实例率先改变状态,p 的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给 p 的回调函数。

Promise.race 方法的参数与 Promise.all 方法一样,如果不是 Promise 实例,就会先调用Promise.resolve 方法,将参数转为 Promise 实例,再进一步处理。

下面是一个例子,如果指定时间内没有获得结果,就将 Promise 的状态变为 reject,否则变为 resolve,用于 timeout 实现。

const p = Promise.race([
  fetch('/resource-that-may-take-a-while'),
  new Promise(function (resolve, reject) {
    setTimeout(() => reject(new Error('request timeout')), 5000)
  })
]);

p
.then(console.log)
.catch(console.error);

上面代码中,如果 5 秒之内 fetch 方法无法返回结果,变量 p 的状态就会变为 rejected,从而触发 catch 方法指定的回调函数。

kangkai124 commented 5 years ago

URL & URLSearchParams

URL() 构造函数返回一个新创建的 URL对象 ,表示由参数定义的 URL。

var path = '/hello'
var url = new URL(path, 'http://api.com')
// url is as show as follows
// {
//   hash: ""
//   host: "api.com"
//   hostname: "api.com"
//   href: "http://api.com/hello"
//   origin: "http://api.com"
//   password: ""
//   pathname: "/hello"
//   port: ""
//   protocol: "http:"
//   search: ""
//   searchParams: URLSearchParams {}
//   username: ""
//   }
console.log(url.toString())
// "http://api.com/hello"

URLSearchParam 是一个构造函数,处理 URL 的查询字符串

if (searchParams) {
  const url = new URL(this._input, document && document.baseURI);
    if (typeof searchParams === 'string' || (URLSearchParams && searchParams instanceof URLSearchParams)) {
      url.search = searchParams;
    } else if (Object.values(searchParams).every(param => typeof param === 'number' || typeof param === 'string')) {
    url.search = new URLSearchParams(searchParams).toString();
  } else {
    throw new Error('The `searchParams` option must be either a string, `URLSearchParams` instance or an object with string and number values');
  }

  this._input = url.toString();
kangkai124 commented 5 years ago

Object.is() 和 ===

不同之处只有两个:一是+0不等于-0,二是NaN等于自身。

+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

ES5 可以通过下面的代码,部署 Object.is()

Object.defineProperty(Object, 'is', {
  value: function(x, y) {
    if (x === y) {
      // 针对+0 不等于 -0的情况
      return x !== 0 || 1 / x === 1 / y;
    }
    // 针对NaN的情况
    return x !== x && y !== y;
  },
  configurable: true,
  enumerable: false,
  writable: true
});
kangkai124 commented 5 years ago

文件批量上传

async multipleUpload (toBeUploadList) {
      let formData = new FormData()
      toBeUploadList.forEach(t => {
        formData.append('files', t.file, t.name)
      })
      try {
        const res = await this.$post(`towerTasks/${this.$route.params.taskId}/batchUpload?fileKey=markov`, {
          headers: null,
          body: formData
        })
        if (res.code === 0) Message.success('马尔科夫文件上传成功')
      } catch (error) {
        Message.error('马尔科夫文件上传失败')
      }
    }
kangkai124 commented 5 years ago

TypeError: Invalid attempt to spread non-iterable instance

这个错误是和变量解构有关,于是定位到了代码

formatData () {
      const { headers, items } = this.data
      return [headers, ...items]
    }

this.data 初试值是 {},因此 items 是 undefined,所以对 items 使用 延展符报了错。 解决方法如下:

formatData () {
      const { headers, items } = this.data
      if (headers && items) return [headers, ...items]
      return []
    }
kangkai124 commented 5 years ago

对象循环引用

var a = { n: 1}
a.b = a;

这里 a 就是循环引用,那么如何判断呢?

其实 JSON.stringify() 就可以,JSON.stringify() 如果遇到参数里有循环引用的,就会抛出一个循环调用的错误 Uncaught TypeError: Converting circular structure to JSON

其次的话,用递归也可以实现

function isCircular() {
  let stack = []
  return function fn(obj) {
    let len = stack.length
    for (; len--;) {
      if (stack[len] === obj) {
        throw new TypeError('There is circular structure')
      }
    }
    stack.push(obj)
    for (let k in obj) {
      const value = obj[k]
      if (typeof value === 'object') fn(value)
    }
  }
}

isCircular()(a)
kangkai124 commented 5 years ago

装箱转换

每一种基本类型 Number、String、Boolean、Symbol 在对象中都有对应的类,所谓装箱转换,正是把基本类型转换为对应的对象,它是类型转换中一种相当重要的种类。

强制 Symbol 装箱:

    var normal = Symbol("aa");
        var symbolObject = (function(){ return this; }).call(Symbol("a"));

    console.log(typeof normal); //symbol
    console.log(typeof symbolObject); //object
    console.log(symbolObject instanceof Symbol); //true
    console.log(symbolObject.constructor == Symbol); //true

        // 当然,可以显式的调用装箱能力
        var symbolObject2 = Object(Symbol('a'))

Object.prototype.toString 是可以准确识别对象对应的基本类型的方法,它比 instanceof 更加准确。

拆箱转换

即对象类型到基本类型的转换。

对象到 String 和 Number 的转换都遵循“先拆箱再转换”的规则。通过拆箱转换,把对象变成基本类型,再从基本类型转换为对应的 String 或者 Number。

拆箱转换会尝试调用 valueOf 和 toString 来获得拆箱后的基本类型。如果 valueOf 和 toString 都不存在,或者没有返回基本类型,则会产生类型错误 TypeError。

到 Number 的拆箱,会先调用 valueOf 再调用 toString,到 String 的拆箱,则相反。

ES6 允许对象显式指定 Sumbol.toPrimitive 覆盖原有行为:

    var o = {
        valueOf : () => {console.log("valueOf"); return {}},
        toString : () => {console.log("toString"); return {}}
    }

    o[Symbol.toPrimitive] = () => {console.log("toPrimitive"); return "hello"}

    console.log(o + "")
    // toPrimitive
    // hello
kangkai124 commented 5 years ago

函数的 length 属性

指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。

(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2

上面代码中,length属性的返回值,等于函数的参数个数减去指定了默认值的参数个数。比如,上面最后一个函数,定义了 3 个参数,其中有一个参数c指定了默认值,因此length属性等于3减去1,最后得到2

这是因为length属性的含义是,该函数预期传入的参数个数。某个参数指定默认值以后,预期传入的参数个数就不包括这个参数了。同理,后文的 rest 参数也不会计入length属性。

(function(...args) {}).length // 0

如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。

(function (a = 0, b, c) {}).length // 0
(function (a, b = 1, c) {}).length // 1