js:Promise理解、async和await的理解、构造函数/原型/原型链、防抖和节流、this指向(普通函数,箭头函数,JS/Vue的this)、闭包、作用域和上下文环境

导读:本篇文章讲解 js:Promise理解、async和await的理解、构造函数/原型/原型链、防抖和节流、this指向(普通函数,箭头函数,JS/Vue的this)、闭包、作用域和上下文环境,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

JS_API

一、Promise

1、Promise的基本使用

Promise对象代表一个异步操作,有三种状态,只有异步操作的结果,可以决定当前是哪一种状态。
这里只是new了一个对象,并没有调用它,我们传进去的函数就已经执行了

new Promise((resolve, reject) => {
  // 异步工作处理
  ...
  // 异步工作执行完之后
  resolve(result) // ——如果成功完成,并返回结果result;
  reject(error) // ——如果执行是失败并产生error;
})

2、一般都是结合函数使用

我们用Promise的时候一般是包在一个函数中,在需要的时候去运行这个函数

<div onClick={promiseClick}>开始异步请求</div>
 
const promiseClick =()=>{
  console.log('点击方法被调用')
  let p = new Promise(function(resolve, reject){
	//做一些异步操作
	setTimeout(function(){
	  console.log('执行完成Promise');
	  resolve('要返回的数据可以任何数据例如接口返回数据');
	}, 2000);
	});
   return p
}

3、Promise的执行结果获取

语法:promise.then(function(result),function(error))

案例

let promise = new Promise((resolve, reject) => {
  // 异步工作处理
  ...
  // 异步工作执行完之后
  resolve(result) // ——如果成功完成,并返回结果result;
  reject(error) // ——如果执行是失败并产生error;
})
promise
.then(
  (result) => {
    console.log('成功执行,resolve(result)')
  }, 
  (error) => {
    console.log('失败执行,reject(error)')
  }
)
.catch(
  // 区别:还可以获取到then的第一个函数的报错
  (err)=>{console.log('等同于then的第二个error函数')}
)

4、Promise的链式编程

function myReadFile(path) {
    return new Promise((resolve, reject) => {
        fs.readFile(path, (err, data) => {
            if (err) reject(err)
            console.log(data.toString())
            resolve(data)
        })
    })
}

myReadFile('1.txt')  // 返回Promise
    .then(data => { return myReadFile('2.txt') })  // 再次调用myReadFile,返回Promise
    .then(data => { return myReadFile('3.txt') })  // 依次类推
    .then(data => { return myReadFile('4.txt') })
    .then(data => { return myReadFile('5.txt') })
    .then(data => { return myReadFile('6.txt') })
    .then(data => { return myReadFile('7.txt') })
    .then(data => { return myReadFile('8.txt') })
    .then(data => { return myReadFile('9.txt') })
    .then(data => { return myReadFile('10.txt') })

5、异步和同步

假设现在有A、B两个任务需要处理,使用并行、同步和异步的处理方式会分别采用如下图所示的执行方式:
在这里插入图片描述

6、Promise.all

Promise.all的参数的执行顺序结果的返回顺序
执行顺序:谁执行的快谁先打印结果。
返回顺序:按照promise的添加顺序返回

function promise1 () {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('1');
    }, 10000);
  })
}
function promise2 () {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('2');
    }, 1000);
  })
}

Promise.all([promise1(), promise2()]).then(function(results){
  console.log(results); // results:['1', '2']
});

二、async和await的理解

1、async

async是一个加在函数前的修饰符,被async定义的函数会默认返回一个Promise对象resolve的值

1.1 return:Promise对象

async function fun0(){
  return new Promise(function(resolve,reject){
    resolve('Promise对象')
})
}

console.log(fun0());  // Promise {<fulfilled>: 'Promise对象'}
fun0().then(res=>console.log(res));  // 通过then获取结果:Promise对象

1.2 return:常规变量(包装在Promise中)

async function fun0(){
  return '常规变量';
}

console.log(fun0());  // Promise {<fulfilled>: '常规变量'}
fun0().then(res=>console.log(res));  // 通过then获取结果:常规变量

1.3 无返回值:返回underfind

async function fun0(){}

console.log(fun0());  // Promise {<fulfilled>: undefined}
fun0().then(res=>console.log(res));  // 通过then获取结果:undefined

2、await

await 也是一个修饰符,只能放在async定义的函数内。可以理解为等待。

2.1 await 修饰Promise对象

它会阻塞后面的代码,等Promise中返回的内容(resolve或reject的参数),且取到值后语句才会往下执行

async function fun(){
  let a = await 1;
  let b = await new Promise((resolve,reject)=>{
      setTimeout(function(){
          resolve('setTimeout')
      },3000)
  })
  let c = await function(){
      return 'function'
  }()
  console.log(a,b,c)
}
fun(); // 3秒后输出: 1 "setTimeout" "function"

2.2 await 不是Promise对象

把这个非promise的东西当做await表达式的结果。(相当于没有await)

function log(time){      // 普通的异步函数
  setTimeout(function(){
      console.log(time);
  },time)
}
async function fun(){
  let a = await log(1000); // 按照异步函数的处理事件快慢决定打印殊勋
  let b = await log(3000);
  let c = log(2000);
  console.log(a,b,c);  // 先执行它,上面是三个都是异步,所以开始abc都是undefined
}
fun(); 
// 立即输出 undefined undefined undefined
// 1秒后输出 1000
// 2秒后输出 2000
// 3秒后输出 3000

3、promise和async/await 的区别

3.1 单一的Promise链,两者区别不大

// Promise 写法
function p(){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{resolve('Promise写法')},1000)
  })
}
p().then(res=>{console.log(res)})


// async/await 写法
function a(){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{resolve('async/await写法')},1000)
  })
}
async function fn() {
  const res = await a()
  console.log(res);
}
fn()

3.2 多个 Promise链,async/await阅读友好

// Promise 写法
function p(time){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      console.log('Promise写法' + time);
      resolve('Promise返回数据')},time)
  })
}

p(1000)
  .then(res=>{return p(2000)})
  .then(res=>{return p(3000)})
  .then(res=>{return p(4000)})
  .then(res=>{return p(5000)})

// async/await 写法
function a(time){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      console.log('async/await写法' + time);
      resolve('async/await返回数据')},time)
  })
}
async function fn() {
  const res1 = await a(1000)
  const res2 = await a(2000)
  const res3 = await a(3000)
  const res4 = await a(4000)
  const res5 = await a(5000)
}
fn()

4、promise和async/await 正确用法

前两步骤都是一样的,promise直接执行,async/await 通过第三方函数执行

4.1 promise

  1. fn函数要return一个promise对象:[return new Promise()]
  2. 在promise对象中进行异步处理,通过resolve/reject出去结果/错误
  3. 调用fn函数,通过then方法获取结果/错误

4.2 async/await

  1. fn1函数要return一个promise对象:[return new Promise()]
  2. 在promise对象中进行异步处理,通过resolve/reject出去结果/错误
  3. 定义一个async fn2函数,函数里awaitfn1函数
  4. 通过变量接收fn1函数的结果
  5. 调用(执行)fn2 函数

三、构造函数/原型/原型链

构造函数:也是函数,只是使用方式要通过new关键字来调用,
原型对象:也叫“原型”、“实例原型”,也是一个对象。
①原型对象=构造函数.prototype。
②定义每个构造函数时都会自带一个原型对象——prototype
③原型对象的作用是存放这个类型创建的所有实例共享的属性和方法。
④每一个JavaScript对象(除了 null )都具有__proto__属性(私有属性),指向该对象的原型。
原型链:原型对象指向另一个类型的实例,类似于套娃,就形成了原型链。下图绿色的就是 原型链

在这里插入图片描述

JS中顶级对象是Object,Object.prototype: 最顶层的原型对象
Object.prototype.proto === null // true

四、防抖和节流

防抖:只有在某个时间内,没有再次触发某个函数时,才真正的调用这个函数;
(等待最后一次才进行函数调用)


节流:在某个时间内(比如500ms),某个函数只能被触发一次;
(按照一定的频率进行调用)


五、this指向

1、确认时机

普通函数–this–确定时机(调用

(1)在函数中this到底取何值,是在函数真正被调用执行的时候确定的,函数定义的时候确定不了。

箭头函数–this–确定时机(定义

(1)箭头函数没有自己的this, 它的this是继承而来;
(2)默认指向在定义它时父级作用域(函数作用域)的this,而不是执行时的对象。也可理解为:默认指向最近作用域(函数作用域)中的this
(3)所谓父级,可以理解为:箭头函数外层的打印console.log(this)这个this指向谁就是谁
(4)父级作用域准确来讲是父级的函数作用域

2、JS的this指向的四种情况

个人理解

首先:this一定要指向一个对象。
其次:最顶层的对象,在浏览器环境中指的是window对象
然后:所有属性或者方法,都需要挂靠到对象下,即都是通过:对象.属性,对象.方法这种方式使用
最后:执行时,如果没有对象,那就算到window这个顶层对象上

2.1、构造函数—>指向new出的对象

function Foo() {
  this.bar = '构造函数this指向new出来的对象'
  console.log(this);  // this指向new出来的对象
}
let f1 = new Foo()
console.log(f1,'f1对象');
console.log(f1.bar = 'new出来的对象');

2.2、函数作为对象的一个属性—>指向该对象

var obj = {
  x:10,
  fn:function () {
    console.log(this);  // this指向obj对象
    function f() {        // 函数f虽然在obj.fn内部定义的,但是它仍是个普通函数
      console.log(this);  // this指向window-------->情况特殊
    }
    f()
  }
}
obj.fn()
console.log(obj);

2.3、函数用call或者apply调用—>指向传入对象的值

var obj = { x:10 }
var fn = function () {
  console.log(this);  // this指向obj对象
}
fn.call(obj)

2.4、全局 & 调用普通函数—>指向window对象

function Foo() {
  console.log(this);  // this指向window对象
}
var boo = function(){
  console.log(this)  // this指向window对象
}
boo()
Foo()

3、箭头函数的this指向

遇到箭头函数,直接在箭头函数的外层,打印console.log(this),看外层打印的this指向谁即可

let obj = {
	console.log(this)   // 指向window
	bbb:()=>{
	    console.log(this)    // flag0:定义时判断,取父级的this指向,对象中this->window
	},
	aaa:function(){
		console.log(this)   // flag1:执行时判断,最近一层的对象是obj,this->obj
		setTimeout(function(){
			console.log(this)  // flag2:执行时判断,最近一层无对象,this->window
			setTimeout(function(){
				console.log(this);  //  flag3:执行时判断,最近一层无对象,this->window
			})
			setTimeout(()=>{
				console.log(this)   //  flag4:定义时判断,取父级的this指向,是flag2处的this->window
			})
		})
		setTimeout(()=>{
			console.log(this)  // flag5:定义时判断,取父级的this指向,是flag1处的this->obj
			setTimeout(()=>{
				console.log(this); //flag6:定义时判断,取父级的this指向,是flag5处的this->obj
			})
		})
	}
}
obj.aaa()

4、Vue的this指向(组件中this大多是指向组件实例)

  • 组件中data的this指向当前的组件实例对象,无论是return前后
    • main.js中new Vue()中,data的this指向undefined
  • 所有的生命周期函数中的this都指向vue实例对象[组件实例对象]
  • vue的v-on指令中可以接收JS语句,其中的this是window(vue组件中的v-on指令除外)
  • computed中的this指向vue实例对象[组件实例对象]
  • methods中的this有以下几种情况
    • 普通函数的this指向vue实例对象[组件实例对象]
    • 箭头函数没有自己的this,因此this指向其宿主对象的this(注意宿主对象是函数对象(它被调用后this的指向要进行具体分析),简单对象没有this)
    • 普通函数形式的回调函数的this是window,箭头函数形式的回调函数的this遵循箭头函数的原则(大多数情况下是vue实例对象)
// test.vue
<script>
export default {
  data () {
    let data1 = this // 组件实例: test.vue
    console.log(data1, '---->return前的this');
    return {
      data2: this, // 组件实例: test.vue
      data3: '',
    }
  },
  computed: {
    data4 () {
      console.log(this, '---->computed的this'); //Vue实例(组件实例: test.vue)
      return this
    }
  },
  created: function () {
    console.log(this, '---->所有生命周期里的this') //使用它的Vue实例(组件实例: test.vue)
    this.data3 = this
    this.showMessage1();
    this.showMessage2();
    this.showMessage3();
  },
  methods: {
    // 普通函数
    showMessage3 () {
      console.log('普通函数........................指向vue实例(组件实例)');
      console.log(this)  //vue实例调用了showMessage3方法,this自然指向vue实例(组件实例: test.vue)
    },
    // 箭头函数
    showMessage2: () => {
      console.log('箭头函数........................指向undefined');
      console.log(this);   // this---->undefined
    },
    showMessage1 () {
      setTimeout(function () {
        console.log('函数未调用........................指向window');
        console.log(this) //指向window  因为没人调用这个普通函数
      }, 10)
    }
  }
}
</script>

六、上下文环境、作用域和闭包

1、预处理(解析阶段)——JS执行“代码段”之前

变量预解析(变量提升)

变量预解析就是指,把所有的变量声明提升到当前作用域的最前面,但不提升赋值操作。

例如:
console.log(a);
var a = 10;
上述代码的执行结果为undefined,它的实际执行顺序可以理解为:
var a;
console.log(a);
a = 10;

函数预解析(函数提升)

函数预解析则是把所有的函数声明提升到当前作用域的最前面,但不执行函数。

例如:
fn();
function fn(){ // 函数声明
    console.log("a");
}
相当于:
function fn(){
    console.log("a");
}
fn();

而:
a();
var a=function(){ // 函数表达式
    console.log("a");
}
上述函数执行时则会报错,a is not a function
上述函数可以理解为:
var a;
a();
a=function(){
    console.log("a");
}

2、生成执行上下文环境——对代码段(全局/函数体)进行处理

全局代码段

1.变量、函数表达式 —— 变量声明,默认赋值为undefined;
2.this —— 赋值(window);
3.函数声明 —— 赋值。

函数体代码段

1.变量、函数表达式 —— 变量声明,默认赋值为undefined;
2.this —— 赋值(参考this指向);
3.函数声明 —— 赋值;
4.参数 —— 赋值;
5.argument —— 赋值;
6.自由变量的取值作用域 —— 赋值。

注意:
函数每被调用一次,都会产生一个新的执行上下文环境
函数在定义的时候(不是调用的时候),就已经确定了函数体内部自由变量的作用域

3、执行上下文环境小结

执行上下文的定义可以通俗化为 —— 在执行代码段之前(预处理阶段),把将要用到的所有变量都事先拿出来,有的直接赋值,有的先用undefined占个空,这些变量共同组成的词法环境,即为执行上下文环境

4、多个执行上下文环境

处于活动状态的执行上下文环境只有一个。
其实这是一个压栈出栈的过程——执行上下文栈。
在这里插入图片描述

5、作用域

作用域是一个很抽象的概念,类似于一个“地盘”

在这里插入图片描述

如上图,全局代码和fn、bar两个函数都会形成一个作用域。
而且,作用域有上下级的关系,上下级关系的确定就看函数是在哪个作用域下创建的。
例如,fn作用域下创建了bar函数,那么“fn作用域”就是“bar作用域”的上级。

作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。

6、作用域和执行上下文

在这里插入图片描述

执行过程:

第一步,在加载程序时,已经确定了全局上下文环境,并随着程序的执行而对变量就行赋值。
第二步,程序执行到第27行,调用fn(10),此时生成此次调用fn函数时的上下文环境,压栈,并将此上下文环境设置为活动状态。
第三步,执行到第23行时,调用bar(100),生成此次调用的上下文环境,压栈,并设置为活动状态。
第四步,执行完第23行,bar(100)调用完成。则bar(100)上下文环境被销毁。接着执行第24行,调用bar(200),则又生成bar(200)的上下文环境,压栈,设置为活动状态。
第五步,执行完第24行,则bar(200)调用结束,其上下文环境被销毁。此时会回到fn(10)上下文环境,变为活动状态。
第六步,执行完第27行代码,fn(10)执行完成之后,fn(10)上下文环境被销毁,全局上下文环境又回到活动状态。

总结:

作用域只是一个“地盘”,一个抽象的概念,其中没有变量。
要通过作用域对应的执行上下文环境来获取变量的值。
同一个作用域下,不同的调用会产生不同的执行上下文环境,继而产生不同的变量的值。
所以,作用域中变量的值是在执行过程中产生的确定的,而作用域却是在函数创建时就确定了。

7、从【自由变量】到【作用域链】

自由变量:

在A作用域中使用的变量x,却没有在A作用域中声明(即在其他作用域中声明的),对于A作用域来说,x就是一个自由变量。如下图

var x = 10,

function fn() {
  var b = 20;
  console.log(x + b);     这里的x就是一个自由变量
}

以上代码,在调用fn()函数时,函数体中第6行。取b的值就直接可以在fn作用域中取,因为b就是在这里定义的。而取x的值时,就需要到另一个作用域中取。到哪个作用域中取呢?


要到创建这个函数的那个作用域中取值——是“创建”,而不是“调用”,切记切记,如下图示例:

var x = 10,

function fn() {
  console.log(x); 
}

function show(f) {
  var x = 20;
  (function () {
    f();         -------> 输出的是10,而不是20
  })();
}
show(fn)

自由变量的作用域

取自由变量x的值时,要到哪个作用域中取?
————要到创建fn函数(包含了自由变量x)的那个作用域中取——无论fn函数将在哪里调用。

作用域链

上面描述的只是跨一步作用域去寻找。
如果跨了一步,还没找到呢?——接着跨!——一直跨到全局作用域为止。要是在全局作用域中都没有找到,那就是真的没有了。

这个一步一步“跨”的路线,我们称之为——作用域链

案例:理解作用域取值
在这里插入图片描述

第13行,fn()返回的是bar函数,赋值给x。
执行x(),即执行bar函数代码。
取b的值时,直接在fn作用域取出。
取a的值时,试图在fn作用域取,但是取不到,只能转向创建fn的那个作用域中去查找,结果找到了。

8、闭包

应用的两种场景:

(1)函数作为返回值,
(2)函数作为参数传递。

第一,函数作为返回值

function fn() {
  var max = 10;
  return function bar(x) {
    if(x > max) {
      console.log(x)    -----------> 15
    }
  }
}

var f1 = fn();
f1(15)

bar函数作为返回值,赋值给f1变量。
执行f1(15)时,用到了fn作用域下的max变量的值。
max自由变量跨域取值:找创建时的作用域

第二,函数作为参数被传递

var max = 10
var fn = function (x) {
      if(x > max) {
        console.log(x) ---------> 15
      }
    }

(function (f) {
  var max = 100;
  f(15)
})(fn)

fn函数作为一个参数被传递进入另一个函数,赋值给f参数。
执行f(15)时,max变量的取值是10,而不是100。
max自由变量跨域取值:找创建时的作用域

闭包的理解

闭包就是能够读取其他函数内部变量的函数。
由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,
因此可以把闭包简单理解成”定义在一个函数内部的函数”。
所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

9、推荐:上下文,作用域和闭包文章

参考链接:https://www.cnblogs.com/wangfupeng1988/p/3977924.html

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/84950.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!