zl程序教程

您现在的位置是:首页 >  Javascript

当前栏目

个人笔记(js+css篇一)

2023-02-18 16:47:27 时间

promise

虽然写过很多遍,但是还是有点点疑问吧,这次彻底搞定比较好。不过感叹确实在实战项目中感受到了promise的好处。下面的回答转载自知乎回答:网址:https://zhuanlan.zhihu.com/p/26523836,然后自己修改了一点

Promise是什么:

简单来说,promise就是一个用来编写异步代码的东西。

举个最简单的例子:

let p = new Promise((resolve, reject) => {
  // 做一些事情...
  // 然后在某些条件下resolve,或者reject
  if (/*xxxxxx*/) {
    resolve(执行成功后可以传递参数)
  } else {
    reject(执行失败后传递的参数)
  }
})

p.then((res) => {
    // 如果p的状态被resolve了,就进入这里
    // 此时res就是上面resolve里的参数
}, () => {
    // 如果p的状态被reject
})

解释一下

第一段调用了Promise构造函数,第二段是调用了promise实例的.then方法

1. 构造实例

  • 构造函数接受一个函数作为参数
  • 调用构造函数得到实例p的同时,作为参数的函数会立即执行, 所以new Promise这个过程是同步的
  • 参数函数接受两个回调函数参数resolve和reject
  • 在参数函数被执行的过程中,如果在其内部调用resolve,会将p的状态变成fulfilled,或者调用reject,会将p的状态变成rejected,这个状态一旦完成后就不可改变。

2. 调用.then

  • 调用.then可以为实例p注册两种状态回调函数
  • 当实例p的状态为fulfilled,会触发第一个函数(resolve)执行
  • 当实例p的状态为rejected,则触发第二个函数(reject)执行

作用:

有时候我们的程序想要进行下去需要依赖于某一个进程结束后返回的数据,比如说我们有时候需要发网络请求,但是我们要拿到返回的数据才能继续下一步,我们就需要将请求封装起来,返回一个Promise对象,然后就可以.then对得到的结果做后续的事情。懂吧懂吧懂吧!还有一个我用到的场景就是,点击删除按钮,在确认删除后弹出“删除成功”的弹窗。

还有一个就是为了防止回调地狱,但是promise的then就可以很好的防止这种情况,下面我写一个例子:

let p1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(1);
      }, 1000);
    })
      .then((res) => {
      // 此时的res就是上面resolve传来的"1"
        return new Promise((resolve) => {
        // 这里又继续返回promise对象,不然下面then会调用不了
          resolve(res + 1);
        });
      })
      .then((res) => {
        return new Promise((resolve) => {
        // 继续返回promise,这样调用p1时得到的结果
        // 就是下面resolve出去的数据
          resolve(res + 1);
        });
      });

    p1.then((res) => {
      console.log(res);
    });

其实我之前写这个promise链式调用总是写错,但是后面用多了感觉理解更深刻了些。

catch

再来写写catch,因为一直没怎么了解这个东西

我们知道Promise对象除了then方法,还有一个catch方法,它是做什么用的呢?其实它和then的第二个参数一样,用来指定reject的回调。

效果和写在then的第二个参数里面一样。不过它还有另外一个作用:在执行resolve的回调(也就是上面then中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个catch方法中。请看下面的代码

下面是直接用的then, 没有使用catch的情况:

 const flag = ref(false);
    const p1 = new Promise((resolve, reject) => {
      if (flag.value) {
        resolve("成功");
      } else {
        reject("失败");
      }
    });
    p1.then((res) => {
      console.log(res);
    });

此时是肯定会进入到reject函数的,但是会报错,使程序进行不下去

现在再试试使用catch

就不会报错了,现在觉得能够体会到这个的好处,不会卡住程序的运行。

Promise.all

Promise.all 作为能够聚合一些 promises 将异步操作带到了一个新的高度。

Promise.all实际上是一个函数,它接受一个promises 数组并返回一个 Promise。然后当所有的 promises 都完成时会得到 resolve 或者当其中一个被拒绝时会得到 rejected。

适用的场景可以是游戏界面,等到所有的资源加载完毕以后才能正式进入游戏。

demo

  const p1 = new Promise((resolve, reject) => {
      resolve("第一个异步函数成功");
    });
    const p2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve("第二个异步函数成功");
      }, 1000);
    });
    const p3 = new Promise((resolve, reject) => {
      resolve("第三个异步函数成功");
    });

    Promise.all([p1, p2, p3]).then((res) => {
      console.log(res);
    });

注意,我在第二个异步函数里面加了个定时器,但是它的结果依旧在第二个,所以promise.all是按照数组中的参数按顺序执行的。

Promise.race

还是上面的3个异步函数, 现在将all换成race, 看看结果如何:

const p1 = new Promise((resolve, reject) => {
      resolve("第一个异步函数成功");
      console.log(1);

    });
    const p2 = new Promise((resolve, reject) => {
      // setTimeout(() => {
        resolve("第二个异步函数成功");
        console.log(2);

      // }, 1000);
    });
    const p3 = new Promise((resolve, reject) => {
      resolve("第三个异步函数成功");
      console.log(3);

    });

    Promise.race([p1, p2, p3]).then((res) => {
      console.log(res);
    });

race含有竞速的意思,将多个Promise放在一个数组中,数组中有一个promise最先得到结果,不管是" 完成(resolved)"还是" 失败(resolved)" , 那么这个 .race 方法就会返回这个结果。

本文由“壹伴编辑器”提供技术

async/await

一直就没搞懂过这玩意...今天认真学一下哈

参考的是思否https://segmentfault.com/u/jamesfancy作者的的文章:https://zhuanlan.zhihu.com/p/172378607 我觉得写的很清楚

async 和 await 在干什么

任意一个名称都是有意义的,先从字面意思来理解。async 是“异步”的简写,而 await 可以认为是 async wait 的简写。所以应该很好理解async用于声明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。也可以说是promise的一个语法糖。

async 起什么作用

这个问题的关键在于,async 函数是怎么处理它的返回值的

我们当然希望它能直接通过 return 语句返回我们想要的值,但是如果真是这样,似乎就没 await 什么事了。所以,写段代码来试试,看它到底会返回什么:

async function run() {
      return 'hello'
    }
const re = run()
console.log(re);

所以,async 函数返回的是一个 Promise 对象。从文档中也可以得到这个信息。async 函数(包含函数语句、函数表达式、Lambda表达式)会返回一个 Promise 对象,如果在函数中 return 一个直接量,async 会把这个直接量通过 Promise.resolve() 封装成 Promise 对象。

await 到底在等啥

一般来说,都认为 await 是在等待一个 async 函数完成。不过按语法说明,await 等待的是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值(换句话说,就是没有特殊限定)。

因为 async 函数返回一个 Promise 对象,所以 await 可以用于等待一个 async 函数的返回值——这也可以说是 await 在等 async 函数,但要清楚,它等的实际是一个返回值。注意到 await 不仅仅用于等 Promise 对象,它可以等任意表达式的结果,所以,await 后面实际是可以接普通函数调用或者直接量的。所以下面这个示例完全可以正确运行

function getSomething() {
 return "something";
}
 
async function testAsync() {
 return Promise.resolve("hello async");
}
 
async function test() {
 const v1 = await getSomething();
 const v2 = await testAsync();
 console.log(v1, v2);
}
 
test();

await 等到了要等的,然后呢

await 等到了它要等的东西,一个 Promise 对象,或者其它值,然后呢?我不得不先说,await 是个运算符,用于组成表达式,await 表达式的运算结果取决于它等的东西。

如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。

如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。

看到上面的阻塞一词,心慌了吧……放心,这就是 await 必须用在 async 函数中的原因。async 函数调用不会造成阻塞,它内部所有的阻塞都被封装在一个 Promise 对象中异步执行。

async/await 帮我们干了啥

做个简单的比较

上面已经说明了 async 会将其后的函数(函数表达式或 Lambda)的返回值封装成一个 Promise 对象,而 await 会等待这个 Promise 完成,并将其 resolve 的结果返回出来。

现在举例,用 setTimeout 模拟耗时的异步操作,先来看看不用 async/await 会怎么写

function akeLongTime() {
 return new Promise(resolve => {
 setTimeout(() => resolve("long_time_value"), 1000);
 });
}
 
takeLongTime().then(v => {
 console.log("got", v);
});

如果改用 async/await 呢,会是这样

function takeLongTime() {
 return new Promise(resolve => {
 setTimeout(() => resolve("long_time_value"), 1000);
 });
}
 
async function test() {
 const v = await takeLongTime();
 console.log(v);
}
 
test();

眼尖的同学已经发现 takeLongTime() 没有申明为 async。实际上,takeLongTime() 本身就是返回的 Promise 对象,加不加 async 结果都一样,如果没明白,请回过头再去看看上面的“async 起什么作用”。

又一个疑问产生了,这两段代码,两种方式对异步调用的处理(实际就是对 Promise 对象的处理)差别并不明显,甚至使用 async/await 还需要多写一些代码,那它的优势到底在哪?

async/await 的优势在于处理 then 链

单一的 Promise 链并不能发现 async/await 的优势,但是,如果需要处理由多个 Promise 组成的 then 链的时候,优势就能体现出来了(很有意思,Promise 通过 then 链来解决多层回调的问题,现在又用 async/await 来进一步优化它)。

假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。我们仍然用 setTimeout 来模拟异步操作:

这个我自己也做了下,确实非常牛哈哈哈

const takeTime = (n) => {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(n + 200);
      }, n);
    });
};
 const step1 = (n) => {
      console.log(`step1的n: ${n}`);
      return takeTime(n);
    };
    const step2 = (m, n) => {
      console.log(`step2的n: ${n} 计算步骤为${m} + ${n} = ${m + n}`);
      return takeTime(m + n);
    };
    const step3 = (k, m, n) => {
      console.log(`step3的n: ${n} 计算步骤为${k} + ${m} + ${n} = ${k + m + n}`);
      return takeTime(k + m + n);
    };

    async function doIt() {
      const time1 = 300;
      const time2 = await step1(time1);
      const time3 = await step2(time1, time2);
      const result = await step3(time1, time2, time3);
      console.log(result);
    }
    doIt();

但如果用的是.then,会麻烦很多,看起来也没有这么美观。

这个就差不多这样啦,自己写了好几个例子以后也理解了很多嘿嘿。

本文由“壹伴编辑器”提供技术支持

原生js操作DOM

获取body元素: document.body

获取html元素: document.documentElement

这个其实用的也不多,那就写几个用的比较多的吧!毕竟也忘了挺多。。。

查找节点

注意带s的会返回数组哈。

var parentNode=document.getElementById("list");//返回第一个带有指定id的元素
var classList=document.getElementsByClassName("wrapper"); //返回所有带有指定class的元素的集合(返回数组形式)
var tagList=document.getElementsByTagName("li");//返回所有带有指定标签的元素的集合(返回数组形式)   // *表示查找所有标签
var list=document.querySelector(".wrapper");//返回第一个带有指定id或class的元素
var allList=document.querySelectorAll(".wrapper");//返回所有带有指定id或class的元素

新增节点

var newNode=document.createElement("div");  //创建一个元素节点
var textNode=document.createTextNode("hello world!");  //创建一个文本节点
var cloneNode =newNode.cloneNode(true);//克隆一个节点,参数true表示会克隆当前节点以及他的所有子节点,flase表示只克隆当前节点,默认为false
document.createDocumentFragment();//创建文档对象变量,主要用来临时存储节点,大量操纵dom时性能会有很大的提升

删除节点

var deleteNode = parentNode.removeChild(item);//删除指定的子节点,并返回   deleteNode只是在dom树中删除了,但在内存中还可以访问

节点之间关系

element.parentNode    //返回该节点的父节点,每个节点都会有一个父节点,Element的父节点可能是Element,Document或DocumentFragment;
element.parentElement    //返回父节点,只有父节点为元素节点时返回,否则返回null

element.children    //返回所有元素子节点的集合,仅包含元素节点
element.childNodes    //返回当前元素所有的子节点,包含元素节点,文本节点,属性节点。(注释、空格、换行等也会被当作一个节点)

element.firstChild    //返回当前元素的第一个子节点,该子节点可以是任意节点,如果没有则返回null
element.firstElementChild    //返回当前元素的第一个子节点,该子节点只可以是元素节点,如果没有则返回null
element.lastChild    //返回当前元素的最后一个子节点,该子节点可以是任意节点,如果没有则返回null
element.lastElementChild    //返回当前元素的最后一个子节点,该子节点只可以是元素节点,如果没有则返回null

element.previousSibling    //返回该节点的前一个兄弟节点
element.previousElementSibling    //返回该节点的前一个元素兄弟节点
element.nextSibling    //返回该节点的后一个兄弟节点
element.nextElementSibling    //返回该节点的后一个元素兄弟节点

修改属性:

var node =document.getElementById("list");
// classList方法操作元素的class属性
node.classList.add("test"); //给node节点添加一个class
node.classList.remove("test");//删除node节点名为test的class
node.classList.replace("old-class","new-class");//替换node节点的class
var hasClass = node.classList.contains("test");//node节点是否存在给定的class,如果存在则返回 true,否则返回 false。
node.classList.toggle("test")//如果节点已经存在给定的class则删除,如果没有则添加

node.setAttribute("id","123");//给node节点设置id=123
var id = node.getAttribute("id");//获取node节点的id属性值
var hasAttr =node.hasAttribute("id");//判断node节点是否存在id属性

// dataset方法获取元素的data- 属性值
var dataId=node.dataset.id; //获取node节点的data-id属性值
var dataName=node.dataset.userName;//类似data-user-name属性必须使用驼峰式来访问

获取当前节点的文本值

element.innerHtml  //返回当前节点的所有文本包含html
element.innerText //返回当前节点及所有后代节点的文本值,不包含html

本文由“壹伴编辑器”提供技术支持

ES6解构

数组的赋值解构

  1. 模式完全匹配的数值解构

2.不完全匹配的解构:即等号左边的模式,只匹配一部分的等号右边的数组,这种情况下解构依旧成功。

注意这个的写法

3.如果等号右边不是数组,或者说不是可以遍历的结构,那么将会报错。

默认值

数组成员为undefined时,默认值仍会生效

如果一个数组成员是null,默认值就不会生效

对象的解构

对象的解构与数组有一个重要的不同,数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值

也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋值给对应的变量,真正被赋值的是后者,而不是前者,也就是说在上面的例子里,先找到同名的属性a和b,然后再赋值给对应的变量name和age,所以要打印name/age才有结果,而不是a/b。

const node = {
    grand : {
        father : {
            line : 1,
            column : 5
        }
    }
 }

 let {grand,grand : { father},grand : {father : {column}}} = node;
 console.log(father); // {line : 1, column : 5}
 console.log(column); // 5
 // grand、fahter、column 分别对这三个属性解构赋值,grand、father是模式,只有column 是变量

本文由“壹伴编辑器”提供技术支持

深浅拷贝

关于这个一直没有去深究,现在来学习一下,概念什么的我都懂,所以复制一些别人的哈。

栈堆、基本数据类型、引用数据类型

栈堆:存放数据的地方

基本数据类型:number,string,boolean,null,undefined

引用数据类型:function,Array,object

tips:基本数据类型保存在栈内存,引用类型保存在堆内存中。根本原因在于保存在栈内存的必须是大小固定的数据,引用类型的大小不固定,只能保存在堆内存中,但是可以把它的地址写在栈内存中以供我们访问。

浅拷贝

浅拷贝指的就是仅拷贝对象的地址,这样会使被拷贝的对象会因为拷贝的对象的数据改变而改变

 let a = [1,2,3];
    let b = a;
    console.log('添加元素前的 a=' + a);
    console.log('添加元素前的 b=' + b);
    b.push(4)
    console.log('添加元素后的 a=' + a);
    console.log('添加元素后的 b=' + b);

如上图,b拷贝a就是浅拷贝,b拷贝的仅是a的地址,当b改变时其实就是堆里面的数据改变了,因为a的地址指向的也是这个堆,所以a的内容也改变了。

浅拷贝方法

Object.assign

const obj2 = {}
  Object.assign(obj2, obj1)
   console.log(obj2.school.name);
   obj2.school.name='12345'
   console.log(obj1.school.name);
   console.log(obj2.school.name)

创建一个新的数组用于存放数据

 let a = [1,2,3];
   let b = []
   for(let i=0; i<a.length;i++) {
     b.push(a[i])
   }
    console.log('添加元素前的a= ' + a);
    console.log('添加元素前的b= ' + b);
    b.push(4)
    console.log('添加元素后的a= ' + a);
    console.log('添加元素后的b= ' + b);

第二种原理也差不多:

 let a = [1,2,3];
   let b = [...a]
    console.log('添加元素前的a= ' + a);
    console.log('添加元素前的b= ' + b);
    b.push(4)
    console.log('添加元素后的a= ' + a);
    console.log('添加元素后的b= ' + b);

slice和concat方法:

let a = [1,2,3];
    let b = a.slice();
    console.log(a===b);
    console.log('添加元素前的 a=' + a);
    console.log('添加元素前的 b=' + b);
    b.push(4)
    console.log('添加元素后的 a=' + a);
    console.log('添加元素后的 b=' + b);

concat也是同理,但是事情没那么简单

  let arr = [1,2,[3,4,5]]
  let arr1 = arr.slice()
  console.log('arr',arr[2]); // [1,2,[3,4,5]]
  console.log('arr1',arr1[2]); // [1,2,[3,4,5]]
  arr1[0]=9; // 一层数据的时候
  console.log('arr',arr); // [1, 2, [3, 4, 5]]
  console.log('arr1',arr1); // [9, 2, [3, 4, 5]]
  arr1[2][0] = 8; // 多层数据的时候
  console.log('arr',arr[2]); // [8, 4, 5]
  console.log('arr1',arr1[2]); //[8, 4, 5]

可以发现,其实上面的方法对于只有一层的数据来说可以做到深拷贝,上面的方法也是一样的,都在第一层是深拷贝,有深层数据就不行了。

本文由“壹伴编辑器”提供技术支持

深拷贝

深拷贝是指拷贝一个对象的数据之前先给拷贝的对象创建一个堆地址,这样当拷贝的对象指向的堆中的数据改变时,被拷贝的对象堆中的数据并不会被改变(意思就是a,b指向不同的堆。指向不同的地址。

深拷贝的方法

JSON的parse和stringify

也是最简单的一种方法吧

let obj = {name: 'alice', age: 23, company: {name: '阿里'}}
  let obj_json = JSON.parse(JSON.stringify(obj))
    console.log('obj ', obj.company);
    console.log('obj_json= ', obj_json.company);
    obj_json.company.name="淘宝"
    console.log('obj= ', obj.company);
    console.log('obj_json= ', obj_json.company);

发现了很神奇的现象:

展开的结果是不同的,于是我找了下

递归深拷贝

onst obj = {name:'yft', info: {id: 100, gender: 'female'}}
  //  递归深拷贝
  const deepClone = (obj) => {
  // 判断传过来的参数的类型
    let obj_clone = Array.isArray(obj)? [] :{}
    if(obj && typeof obj === 'object') {
      for(var key in obj) {
        if(obj.hasOwnProperty(key)) {
        // hasOwnProperty表示是否有自己的属性。
        // 这个方法会查找一个对象是否有某个属性,但是不会去查找它的原型链。
          if(obj[key] && typeof obj[key] === 'object') {
          // 如果还有更深层次, 那就继续调用这个函数
          obj_clone[key] = deepClone(obj[key])
          }
          else {
          obj_clone[key] = obj[key]
          }
        }
      }
    }
    return obj_clone
  }
  
  const re = deepClone(obj);
  console.log(re); // name: 'yft', info: {id: '100', gender: 'female'}
  re.info.id = "12345"
  console.log(re) // // name: 'yft', info: {id: '12345', gender: 'female'}
  console.log(obj); // name: 'yft', info: {id: '100', gender: 'female'}

这块先写这么多吧,写不下了, 接着下一个笔记写。