常用的js方法

102 阅读15分钟

一、事件循环

浏览器有哪些线程

  • GUI渲染线程---负责渲染页面,例如解析html,css,布局并渲染页面
  • js引擎线程---最出名的就是我们熟知的V8引擎,解析并执行js
  • 事件触发线程
  • 定时器触发线程
  • 异步请求线程---浏览器会新开一个线程进行请求,当检测到请求状态发生变化时,如果设置了回调函数,会将此函数放入任务队列中。

JavaScript并不是一行一行的分析并执行代码的,而是分段一段一段的进行分析并执行。这里要说明一下,怎么才算一段。这里的段指的是JavaScript中的执行上下文。

执行上下文分为三种

  • 全局执行上下文----这里指的就是全局代码
  • 函数执行上下文----这里指的是一个一个函数,一个函数执行前会先创建一个执行上下文
  • eval执行上下文----eval不推荐使用,这里不做过多说明

JS内存机制

在JS中,每一个数据都需要一个内存空间。内存空间又被分为两种,栈内存(stack)*与*堆内存(heap)

wechatimg104

对象放在heap(堆)里,常见的基础类型和函数放在stack(栈)里,函数执行的时候在栈里执行。栈里函数执行的时候可能会调一些Dom操作,ajax操作和setTimeout定时器,这时候要等stack(栈)里面的所有程序先走**(注意:栈里的代码是先进后出)**,走完后再走WebAPIs,WebAPIs执行后的结果放在callback queue(回调的队列里,注意:队列里的代码先放进去的先执行),也就是当栈里面的程序走完之后,再从任务队列中读取事件,将队列中的事件放到执行栈中依次执行,这个过程是循环不断的。

var a = "aa";
function one(){
    let a = 1;
    two();
    function two(){
        let b = 2;
        three();
        function three(){
            console.log(b)
        }
    }
}
console.log(a);
one();

wechatimg107

最先走的肯定是three,因为two要是先销毁了,那three的代码b就拿不到了,所以是先进后出(先进的后出),所以,three最先出,然后是two出,再是one出。

任务队列的分类

js主线程它是有一个执行栈的,所有的js代码都会在执行栈里运行。在执行代码过程中,如果遇到一些异步代码(比如setTimeout,ajax,promise.then以及用户点击等操作),那么浏览器就会将这些代码放到一个线程(在这里我们叫做幕后线程)中去等待,不阻塞主线程的执行,主线程继续执行栈中剩余的代码,当幕后线程(background thread)里的代码准备好了(比如setTimeout时间到了,ajax请求得到响应),该线程就会将它的回调函数放到任务队列中等待执行。而当主线程执行完栈中的所有代码后,它就会检查任务队列是否有任务要执行,如果有任务要执行的话,那么就将该任务放到执行栈中执行。如果当前任务队列为空的话,它就会一直循环等待任务到来。因此,这叫做事件循环。

js是有两个任务队列的,一个叫做Macrotask Queue(Task Queue),一个叫做Microtask Queue.

接下来看看都有哪些属于宏任务,哪些属于微任务。

宏任务(macrotask):script( 整体代码)、setTimeout、setInterval、I/O(http请求)、UI 渲染、DOM 事件

微任务(microtask ):Promise.then()、MutationObserver(监听dom的更改)、

img

console.log(1)
setTimeout(function () {
  console.log(2)
}, 0);
new Promise(function (resolve) {
  console.log(3)
  resolve()
})
  .then(function () {
    console.log(4)
  })
  .then(function () {
    console.log(5)
  })
console.log(6)

注意:new Promise中的是同步代码,Promise.then()是微任务

二、array

concat

concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。

const array1 = ['a', 'b', 'c'];
const array2 = ['d', 'e', 'f'];
const array3 = array1.concat(array2);

console.log(array3);
// expected output: Array ["a", "b", "c", "d", "e", "f"]

copyWithin()

copyWithin() 方法浅复制数组的一部分到同一数组中的另一个位置,并返回它,不会改变原数组的长度。

copyWithin(target, start, end)
target:复制序列到该位置。如果是负数,target 将从末尾开始计算。如果 target 大于等于 arr.length,将不会发生拷贝。
start:开始复制元素的起始位置。如果是负数,start 将从末尾开始计算。如果 start 被忽略,copyWithin 将会从 0 开始复制。
end:开始复制元素的结束位置。copyWithin 将会拷贝到该位置,但不包括 end 这个位置的元素。如果是负数, 将从末尾开始计算。如果 end 被忽略,copyWithin 方法将会一直复制至数组结尾(默认为 arr.length)。

[1, 2, 3, 4, 5].copyWithin(-2)
[1, 2, 3, 4, 5].copyWithin(0, 3)
[1, 2, 3, 4, 5].copyWithin(0, 3, 4)

at()

at()* 方法接收一个整数值并返回该索引对应的元素,允许正数和负数。负整数从数组中的最后一个元素开始倒数。

在传递非负数时,at() 方法等价于括号表示法。例如,array[0]array.at(0) 均返回第一个元素。但是,当你需要从数组的末端开始倒数时,则不能使用 array[-1],因为方括号内的所有值都会被视为字符串属性,因此你最终读取的是 array["-1"],这只是一个普通的字符串属性而不是数组索引。

const cart = ['apple', 'banana', 'pear'];
console.log(cart[-1]);
console.log(cart.at(-1));

every()

every() 测试一个数组内的所有元素是否都能通过某个指定函数的测试。它返回一个布尔值。

备注: 若收到一个空数组,此方法在任何情况下都会返回 true

[12, 5, 8, 130, 44].every(x => x >= 10);

filter()

filter() 方法创建给定数组一部分的浅拷贝,其包含通过所提供函数实现的测试的所有元素。

filter(element,index,array)

element:数组中当前正在处理的元素。

index:正在处理的元素在数组中的索引。

array:调用了 filter() 的数组本身。

const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
array.filter(item=>item>10)

find()

find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined

find(element,index,array)

const inventory = [
  {name: 'apples', quantity: 2},
  {name: 'bananas', quantity: 0},
  {name: 'cherries', quantity: 5}
];
inventory.find(item=>item.name==='apples')

findIndex

findIndex()*方法返回数组中满足提供的测试函数的第一个元素的*索引。若没有找到对应元素则返回 -1。

findLast()

findIndex()*方法返回数组中满足提供的测试函数的第一个元素的*索引。若没有找到对应元素则返回 -1。

findLastIndex()

findLastIndex() 方法返回数组中满足提供的测试函数条件的最后一个元素的索引。若没有找到对应元素,则返回 -1。

flat()

flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。(扁平化数组)

flat(depth) depth:指定要提取嵌套数组的结构深度,默认值为 1。

var arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flat();

var arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];
arr4.flat(Infinity);

flatMap()

flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。它与 map 连着深度值为 1 的 flat 几乎相同,但 flatMap 通常在合并成一种方法的效率稍微高一些。

var arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flatMap(item => item);

forEach

Array.from()

Array.from() 方法对一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。

console.log(Array.from('foo'));
console.log(Array.from([1, 2, 3], x => x + x));

includes()

includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回 false

[1, 2, 3].includes(2);     // true
[1, 2, 3].includes(4);     // false
[1, 2, 3].includes(3, 3);  // false
[1, 2, 3].includes(3, -1); // true
[1, 2, NaN].includes(NaN); // true

indexOf

Array.isArray()

Array.isArray() 用于确定传递的值是否是一个 Array

const arr = [1,2]
Array.isArray(arr)

join()

join() 方法将一个数组(或一个类数组对象)的所有元素连接成一个字符串并返回这个字符串,用逗号或指定的分隔符字符串分隔。如果数组只有一个元素,那么将返回该元素而不使用分隔符。

keys()

keys() 方法返回一个包含数组中每个索引键的 Array Iterator 对象。

var arr = ["a", , "c"];
var denseKeys = [...arr.keys()];
console.log(denseKeys);

lastIndexOf()

方法返回指定元素(也即有效的 JavaScript 值或变量)在数组中的最后一个的索引,如果不存在则返回 -1。从数组的后面向前查找,从 fromIndex 处开始。

lastIndexOf(searchElement, fromIndex)

searchElement:被查找的元素。

fromIndex:从此位置开始逆向查找。默认为数组的长度减 1(arr.length - 1),即整个数组都被查找。如果该值大于或等于数组的长度,则整个数组会被查找。如果为负值,将其视为从数组末尾向前的偏移。即使该值为负,数组仍然会被从后向前查找。如果该值为负时,其绝对值大于数组长度,则方法返回 -1,即数组不会被查找。

var array = [2, 5, 9, 2];
var index = array.lastIndexOf(2);
var index = array.lastIndexOf(2, 2);
console.log(index)

pop() 方法从数组中删除最后一个元素,并返回该元素的值。此方法会更改数组的长度。

push() 方法将一个或多个元素添加到数组的末尾,并返回该数组的新长度。

slice() 方法返回一个新的数组对象,这一对象是一个由 beginend 决定的原数组的浅拷贝(包括 begin,不包括end)。原始数组不会被改变。

unshift() 方法将一个或多个元素添加到数组的开头,并返回该数组的新长度

splice() 方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组。

sort() 方法用原地算法对数组的元素进行排序,并返回数组。默认排序顺序是在将元素转换为字符串,然后比较它们的 UTF-16 代码单元值序列时构建的

some()

some() 方法测试数组中是不是至少有 1 个元素通过了被提供的函数测试。它返回的是一个 Boolean 类型的值。

如果用一个空数组进行测试,在任何情况下它返回的都是false

some() 方法会循环数组中的数据,当某个数据符合条件时,将不再循环数组,返回一个true 。如果当前数组中数据都不复合条件,则返回false;回调函数中不写return 则返回false;

[2, 5, 8, 1, 4].some(item=>item>7)

reduce()

reduce() 方法对数组中的每个元素按序执行一个由您提供的 reducer 函数,每一次运行 reducer 会将先前元素的计算结果作为参数传入,最后将其结果汇总为单个返回值。

reduce((previousValue, currentValue, currentIndex, array) => { /* … */ }, initialValue)

一个“reducer”函数,包含四个参数:

  • previousValue:上一次调用 callbackFn 时的返回值。在第一次调用时,若指定了初始值 initialValue,其值则为 initialValue,否则为数组索引为 0 的元素 array[0]

  • currentValue:数组中正在处理的元素。在第一次调用时,若指定了初始值 initialValue,其值则为数组索引为 0 的元素 array[0],否则为 array[1]

  • currentIndex:数组中正在处理的元素的索引。若指定了初始值 initialValue,则起始索引号为 0,否则从索引 1 起始。

  • array:用于遍历的数组。

    initialValue 可选

    作为第一次调用 callback 函数时参数 previousValue 的值。若指定了初始值 initialValue,则 currentValue 则将使用数组第一个元素;否则 previousValue 将使用数组第一个元素,而 currentValue 将使用数组第二个元素。

    const array1 = [1, 2, 3, 4];
    let initialValue = 0
    array1.reduce((previousValue, currentValue) => {
        initialValue += currentValue
    } , initialValue)
    console.log(initialValue)
    

reverse() 方法将数组中元素的位置颠倒,并返回该数组。数组的第一个元素会变成最后一个,数组的最后一个元素变成第一个。该方法会改变原数组。

三、Reflect

ES6 为了操作对象而提供的新 API。Reflect对象的设计目的有这样几个。

(1) 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在ObjectReflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法。

(2) 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false

(3) 让Object操作都变成函数行为。某些Object操作是命令式,比如name in objdelete obj[name],而Reflect.has(obj, name)Reflect.deleteProperty(obj, name)让它们变成了函数行为。

var myObject = {
  foo: 1,
};

// 旧写法
'foo' in myObject // true

// 新写法
Reflect.has(myObject, 'foo') // true

(4)Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。

var loggedObj = new Proxy(obj, {
  get(target, name) {
    console.log('get', target, name);
    return Reflect.get(target, name);
  },
  deleteProperty(target, name) {
    console.log('delete' + name);
    return Reflect.deleteProperty(target, name);
  },
  has(target, name) {
    console.log('has' + name);
    return Reflect.has(target, name);
  }
});

上面代码中,每一个Proxy对象的拦截操作(getdeletehas),内部都调用对应的Reflect方法,保证原生行为能够正常执行。有了Reflect对象以后,很多操作会更易读。

Reflect对象一共有 13 个静态方法。

  • Reflect.apply(target, thisArg, args):等同于Function.prototype.apply.call(func, thisArg, args),用于绑定this对象后执行给定函数。

    一般来说,如果要绑定一个函数的this对象,可以这样写fn.apply(obj, args),但是如果函数定义了自己的apply方法,就只能写成Function.prototype.apply.call(fn, obj, args),采用Reflect对象可以简化这种操作。

    // 老写法
    Function.prototype.apply.call(Math.floor, undefined, [1.75]) // 1
    
    // 新写法
    Reflect.apply(Math.floor, undefined, [1.75]) // 1
    
  • Reflect.construct(target, args):等同于new target(...args),这提供了一种不使用new,来调用构造函数的方法。

    function Greeting(name) {
      this.name = name;
    }
    
    // new 的写法
    const instance = new Greeting('张三');
    
    // Reflect.construct 的写法
    const instance = Reflect.construct(Greeting, ['张三']);
    
  • Reflect.get(target, name, receiver):查找并返回target对象的name属性,如果没有该属性,则返回undefined

  • Reflect.set(target, name, value, receiver):设置target对象的name属性等于value

  • Reflect.defineProperty(target, name, desc):基本等同于Object.defineProperty,用来为对象定义属性。

  • Reflect.deleteProperty(target, name):等同于delete obj[name],用于删除对象的属性。

  • Reflect.has(target, name):对应name in obj里面的in运算符。

  • Reflect.ownKeys(target):返回对象的所有属性,基本等同于Object.getOwnPropertyNamesObject.getOwnPropertySymbols之和。

  • Reflect.isExtensible(target):对应Object.isExtensible,返回一个布尔值,表示当前对象是否可扩展。

  • Reflect.preventExtensions(target)Object.preventExtensions方法,用于让一个对象变为不可扩展。它返回一个布尔值,表示是否操作成功。

  • Reflect.getOwnPropertyDescriptor(target, name):用于得到指定属性的描述对象,基本等同于Object.getOwnPropertyDescriptor

  • Reflect.getPrototypeOf(target):读取对象的__proto__属性,对应Object.getPrototypeOf(obj)Reflect.getPrototypeOfObject.getPrototypeOf的一个区别是,如果参数不是对象,Object.getPrototypeOf会将这个参数转为对象,然后再运行,而Reflect.getPrototypeOf会报错。

    Object.getPrototypeOf(1) // Number {[[PrimitiveValue]]: 0}
    Reflect.getPrototypeOf(1) // 报错
    
  • Reflect.setPrototypeOf(target, prototype):设置目标对象的原型(prototype),对应Object.setPrototypeOf(obj, newProto)方法。它返回一个布尔值,表示是否设置成功。

四、Proxy

Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

下面是 Proxy 支持的拦截操作一览,一共 13 种。

  • get(target, propKey, receiver):拦截对象属性的读取,比如proxy.fooproxy['foo']
  • set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = vproxy['foo'] = v,返回一个布尔值。
  • has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值。
  • deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值。
  • ownKeys(target):拦截Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
  • getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
  • defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDescs),返回一个布尔值。
  • preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。
  • getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。
  • isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。
  • setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
  • apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)proxy.call(object, ...args)proxy.apply(...)
  • construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)

vue3数据响应式原理的实现:

  1. 通过Proxy代理实现

  2. 通过Reflect(反射)被代理的对象,属性进行操作

    const person = {
        name: '小浪',
        age: 22,
    }
    const p = new Proxy(person, {
        // 有人读取p 身上的某个属性
        get(target, propName) {
          console.log(`有人读取了p身上的${propName}属性`);
          return Reflect.get(target, propName)
        }, 
        // 给p对象身上增加/ 修改某个属性时调用
        set(target, propName, value) {
          console.log(`有人修改/增加了p身上的${propName}属性`);
          Reflect.set(target, propName, value)
        },
     
        deleteproperty(target, propName) {
          console.log(`有人删除了p身上的${propName}属性`);
          return Reflect.deleteProperty(target, value)    // 删除一个属性
        }
    })