Skip to content

Latest commit

 

History

History
289 lines (237 loc) · 10 KB

杂.md

File metadata and controls

289 lines (237 loc) · 10 KB

Call和Apply

用法:

fn.apply(this, argsArray);
fn.call(this, arg1, arg2, ...);

什么时候用什么: A for array and C for comma,有array的时候用A(apply),有comma(很多个args)的时候用C (call)。

apply的一个方便的场景就是可以把arguments伪数组直接传进来。

fn.apply(this, arguments);

伪数组

伪数组 (ArrayLike) ,又称类数组。是一个类似数组的对象,但是有如下几个特征。

  • 按索引方式储存数据
  • 具有length属性,但是length属性不是动态的,不会随着成员的变化而改变
  • 不具有数组的push(), forEach()等方法
const arrLike = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3,
};

console.log(arrLike[0]);        // 'a'
console.log(arrLike.length);    // 3 
arrLike.push('x');              // Uncaught TypeError: arrLike.push is not a function

console.log(arrLike.__proto__ === Object.prototype);    // true
console.log(arrLike instanceof Object);                 // true
console.log(arrLike instanceof Array);                  // false

常见的伪数组

  • jQuery中通过 $() 获取的DOM元素集
  • 函数中的的 arguments 对象
  • 以及字符串String对象

转化成真数组的方法

  • 遍历添加进空数组
const arr = [];
for(let i = 0; i < arrLike.length; i++){
    arr.push(arrLike[i]);
}

// 简洁版,虽然有一点不一样,思路类似
Array.prototype.concat.apply([], arrayLike)
  • 利用数组的slice()方法 (推介) 注意这个返回的数组中,不会保留索引值以外的其他额外属性。比如jQuery中$()获取的DOM伪数组,里面的context属性在被此方法转化之后就不会保留。
const arr = [].slice.call(arrLike);
// or
const arr = Array.prototype.slice.call(arrLike);
  • 直接修改原型链,继承Array.prototype的属性,这样就能使用push()等方法。但是Array.isArray和Object.prototype.toString不认,所以只是行为上相似(不推介)
arrLike.__proto__ = Array.prototype;
  • ES6+的Array.from()
const arr = Array.from(arrLike);
  • ES6的spread operator
const arr = [...arrLike];

arguments

arguments就指向该函数的Arguments对象,它是一个伪数组。arguments伪数组有很多作用:

// 1) 拿到实参的长度
function foo(b, c, d){
  console.log("实参的长度为:" + arguments.length)
}

console.log("形参的长度为:" + foo.length)

foo(1)

// 形参的长度为:3
// 实参的长度为:1

// 2) 通过callee属性可以调用函数自身
var data = [];

for (var i = 0; i < 3; i++) {
  (data[i] = function () {
    console.log(arguments.callee.i) 
  }).i = i;
}

data[0]();    // 0
data[1]();    // 1
data[2]();    // 2

// 3) 使用 apply 将 foo 的参数传递给 bar
function foo() {
  bar.apply(this, arguments);
}
function bar(a, b, c) {
  console.log(a, b, c);
}

foo(1, 2, 3)

优化

循环方法的使用

  • 少用for of,因为会编译成带try catch的generator,非常冗余(3行变30行)。
  • 少用for in,因为遍历数组的时候,除了index,还会把自己增加的属性也遍历进去
  • 多用正常循环,和forEach()
const arr = ['a', 'b']
arr.c = 'c'
for (let i in arr) { console.log(i) }   // 0, 1, c

传参的优化

这个不是性能优化,但是会美化代码的结构。但是这点react源码里面都没有遵守,而且源码里面还总是用全局变量。。

const data = { x: 1, y: 2 };

const do = data => {
  // x和y都是data一部分,不需要分开传进来!
  const { x, y } = data;
}

const dont = (data, x, y) => {
  ...
}

各种遇到过的优化

在Nextjs中:

  • Link组件通过预取功能(在生产环境中)自动优化应用程序以获得最佳性能
  • 如果服务端需要用到node的API,比如getServerSideProps()调用fs模块,next打包客户端代码的时候,会自动treeshaking掉node相关的代码,因为客户端是不需要的。

数据流

数据流可以用两种处理方式

  • 浏览器环境下的stream API
  • for await of,需要对象有Symbol.asyncIterator
const asyncIterable = {
  [Symbol.asyncIterator]() {
    return {
      i: 0,
      next() {
        if (this.i < 3) {
          return Promise.resolve({ value: this.i++, done: false });
        }

        return Promise.resolve({ done: true });
      }
    };
  }
};

(async function() {
   for await (let num of asyncIterable) {
     console.log(num);
   }
})();

判断是不是array的四种方法

const arr = []

arr instanceof Array          // 用法:xxx instanceof constructor
arr.constructor === Array     // 假如constructor被改写了就会有问题
Object.prototype.toString.call(arr) === '[object Array]'
Array.isArray(arr)

// 注意不能用typeof判断!JS的typeof是有bug的
typeof arr === 'object' // true
typeof arr === 'array'  // false

垃圾回收

标记清除

  • 从根节点出发,标记阶段即为所有活动对象做上标记,清除阶段则把没有标记(也就是非活动对象)销毁。
  • 缺点:在清除之后,剩余的对象内存位置是不变的,也会导致空闲内存空间是不连续的,出现了内存碎片
  • 解决办法:标记整理。标记结束后,标记整理算法会将活着的对象(即不需要清理的对象)向内存的一端移动,最后清理掉边界的内存

引用计数

  • 是早先的一种垃圾回收算法,把对象是否不再需要,简化定义为:对象有没有其他对象引用到它。如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收,目前很少使用这种算法了。
  • 问题:循环引用,计数器会占用内存,可能变很大

V8分代式垃圾回收

我们上面所说的垃圾清理算法在每次垃圾回收时都要检查内存中所有的对象,这样的话对于一些大、老、存活时间长的对象来说同新、小、存活时间短的对象一个频率的检查很不好,因为前者需要时间长并且不需要频繁进行清理。(林迪效应)

V8 中将堆内存分为新生代和老生代两区域,采用不同的垃圾回收器也就是不同的策略管理垃圾回收。

  • 新生代:分为使用区和空闲区。当使用区快被写满时,就需要执行一次垃圾清理操作,将活动的对象复制到空闲区,清空使用区,然后把使用区和空闲区调换。
  • 老生代:标记清除

V8也引入了并发标记和并发清除的使用,甚至还可以把垃圾清除打碎成很多个可打断可恢复的步骤,跟fiber又有点像。具体实现叫做三色标记法,标记成黑白灰三种颜色,把灰色节点缓存起来,当做要继续迭代的起点。

完整克隆一个对象的三种方式

// 这两个是相等的,只是浅拷贝了a的实例属性,但是没有拷贝原型链
let aClone = { ...a };
let aClone = Object.assign({}, a);

// 3种完整的克隆方法 
const clone1 = {  
  // __proto__属性在非浏览器的环境不一定部署,因此推荐使用写法二和写法三。
  __proto__: Object.getPrototypeOf(obj),
  ...obj
};

const clone2 = Object.assign(
  Object.create(Object.getPrototypeOf(obj)),
  obj
);

const clone3 = Object.create(
  Object.getPrototypeOf(obj),
  Object.getOwnPropertyDescriptors(obj)
)

this到底指向谁

基本上,理解this指向它的调用者,已经够用了。 找到this的一些总结:

  • 由new调用:绑定到新创建的对象
  • 由call或apply、bind调用:绑定到指定的对象
  • 由上下文对象调用:绑定到上下文对象
  • 箭头函数:继承外层函数调用的this绑定
  • 作为一个DOM事件处理函数: 指向触发事件的元素e.target
  • 默认:全局对象

5种遍历对象的方式

  • for...in:遍历自身 + 继承的可枚举属性(不含 Symbol 属性)。
  • Object.keys(obj): 返回对象自身的(不含继承)所有可枚举属性(不含 Symbol 属性)的键名。
  • Object.getOwnPropertyNames(obj): 返回对象自身的(不含继承)所有可枚举 + 不可枚举属性(不含 Symbol)的键名。
  • Object.getOwnPropertySymbols(obj): 返回对象自身的所有 Symbol 属性的键名。
  • Reflect.ownKeys(obj): 返回对象自身的(不含继承)所有可枚举 + 不可枚举属性(含 Symbol)的键名。

jwt token应该存在哪里?

主要考虑的是安全问题。

存在local storage和cookie里其实安全性是一样的,xss都能拿到,除非cookie设置了httpOnly和secure,不能被js读取。但是因为cookie是自动携带上的,又会容易被CSRF攻击。Cookie也有4kb大小限制,如果jwt太大了就存不下。

所以,最好存在local storage里面,并且防范好XSS攻击,通过手动加入到请求中的Authorization Header发送。

refenreces