当前位置: 首页 > news >正文

js执行机制

变量提升

在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

调用栈

1. 在执行之前就进行编译并创建执行上下文

  1. 当 JavaScript 执行全局代码的时候,会编译全局代码并创建全局执行上下文,而且在整个页面的生存周期内,全局执行上下文只有一份。
  2. 当调用一个函数的时候,函数体内的代码会被编译,并创建函数执行上下文,一般情况下,函数执行结束之后,创建的函数执行上下文会被销毁。
  3. 当使用 eval 函数的时候,eval 的代码也会被编译,并创建执行上下文。

2. 调用栈

JavaScript 中有很多函数,经常会出现在一个函数中调用另外一个函数的情况,调用栈就是用来管理函数调用关系的一种数据结构

调用栈特点:后进先出

JavaScript 引擎会将执行上下文压入栈中,通常把这种用来管理执行上下文的栈称为执行上下文栈,又称调用栈

var a = 1;
function sum(b,c){ 
	return b+c
};
function addSum(d,e){
	var f = 10;
	result = add(d,e);
	return a+result+f
};
addSum(3,6)

第一步:创建全局上下文,并将其压入栈底,执行a = 1;

第二步:调用addSum函数,JavaScript 引擎会编译该函数,并为其创建一个执行上下文,最后还将该函数的执行上下文压入栈中

第三步:当执行到 add 函数调用语句时,同样会为其创建执行上下文,并将其压入调用栈

第四步:当 add 函数返回时,该函数的执行上下文就会从栈顶弹出,并将 result 的值设置为 add 函数的返回值,也就是 9

第五步:addSum 执行最后一个相加操作后并返回,addSum 的执行上下文也会从栈顶部弹出,此时调用栈中就只剩下全局上下文了

作用域链和闭包

1. 作用域

作用域是指在程序中定义变量的区域,该位置决定了变量的生命周期。通俗地理解,作用域就是变量与函数的可访问范围,即作用域控制着变量和函数的可见性和生命周期。

在 ES6 之前,ES 的作用域只有两种:全局作用域和函数作用域。

  • 全局作用域中的对象在代码中的任何地方都能访问,其生命周期伴随着页面的生命周期。

  • 函数作用域就是在函数内部定义的变量或者函数,并且定义的变量或者函数只能在函数内部被访问。函数执行结束之后,函数内部定义的变量会被销毁。

ES6支持块级作用域

  • 块级作用域特点:在代码块内部定义的变量在代码块外部是访问不到的,并且等该代码块中的代码执行完成之后,代码块中定义的变量会被销毁。

  • **块级作用域形式:**就是使用一对大括号包裹的一段代码,比如函数、判断语句、循环语句,甚至单独的一个{}都可以被看作是一个块级作用域。

    //if块
    if(1){}
    //while块
    while(1){}
    //函数块
    function foo(){} 
    //for循环块
    for(let i = 0; i<100; i++){}
    //单独一个块
    {}
    
  • ES6中如何使块级作用域生效:使用let和const关键字

    for(var i = 0; i<100; i++){
    }
    console.log(i)
    
    for(let i = 0; i<100; i++){
    }
    console.log(i)
    

    引申考题:隔一秒钟打印出来一个自然数,自然数递增

    for(let i = 0; i<10; i++){
    	setTimeout(function(){
    		console.log(i)
    	},1000*i)	
    }
    
    for(var i = 0; i<10; i++){
    	setTimeout(function(){
    		console.log(i)
    	},1000*i)	
    }
    

let、const关键字解决var关键字变量提升的问题

  • 由于变量提升,变量值容易被覆盖

    var myname = "王美丽"
    function showName(){ 
    	console.log(myname); 
    	if(0){ 
    		var myname = "闷倒驴" }
    		console.log(myname);
    	}
    showName()
    
  • 本该销毁的变量销毁不掉

    for(var i = 0; i<100; i++){
    }
    console.log(i)
    

let、const关键字解决问题的原理

  • let和const关键字创建的变量存储在词法环境中,var关键字创建的变量存储在变量环境中

  • 块级内部代码执行结束,立马销毁内部let、const创建的变量

  • let和const创建的变量,初始化不提升,创建提升,所以造成暂时性死区

  • 访问变量先在当前执行上下文的词法环境中查找,再到变量环境中查找

    function fun(){ 
    	var a = 11;
    	let b = 22;
    	{ 	
    		let b = 33;
    		var c = 44;
            let d = 55;
            console.log(a);
            console.log(b);
        }
    	console.log(b);
    	console.log(c);
    	console.log(d);
    } 
    fun()
    

    第一步:刚开始执行fun函数

    第二步:执行内部代码块

    第三步:执行代码块中console.log(a) console.log(b)

第四步:代码块执行结束,相应词法环境中的变量弹出

​

第五步:执行console.log(b);console.log©;

在这里插入图片描述

第六步:执行console.log(d),找不到报错

关于变量提升问题

  • var的创建和初始化被提升,赋值不会被提升。
  • let的创建被提升,初始化和赋值不会被提升,所以会造成暂时性死区(就是访问不到)。
  • function的创建、初始化和赋值均会被提升。

作用域的特点:是代码编译阶段就决定好的,和函数是怎么调用的没有关系。

function bar() { 
	var myName = "王美丽";
    let test1 = 100;
    if (1) { 
    	let myName = "丑八怪"; 
    	console.log(test);
    }
}
function foo() { 
	var myName = "闷倒驴";
    let test = 2;
    { 
    	let test = 3;
        bar();
    }
};
var myName = "大喇叭";
let myAge = 10;
let test =1;
foo();

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pQ4EqeI5-1666523828649)(01.png)]

2. 作用域链

作用域链:当一个函数中使用了某个变量,首先会在自己内部作用域查找,然后再向外部一层一层查找,直到全局作用域,这个链式查找就是作用域链

let num = 1;
        function fun1 (){
            function fun2(){
                function fun3(){
                    console.log(num);
                }
                fun3()
            }
            fun2()
        }
        fun1();

3.闭包

3.1 什么是闭包

function foo() { 
	var myName = "王美丽";
	let test1 = 1;
	const test2 = 2; 
	var innerBar = { 
		getName:function(){ console.log(test1) return myName }, 
		setName:function(newName){ myName = newName } 
	} 
	return innerBar
}
var bar = foo();
bar.setName("闷倒驴");
bar.getName();
console.log(bar.getName())

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lashlpW3-1666523828650)(03.png)]

3.2 闭包形成的原理

  • 作用域链,当前作用域可以访问上级作用域中的变量
  • 全局变量只用页面关闭才会销毁

3.3 闭包解决的问题

  • 函数作用域中的变量在函数执行结束就会销毁,但是有时候我们并不希望变量销毁
  • 在函数外部可以访问函数内部的变量

3.4 闭包带来的问题

  • 容易造成内存泄露

    • 内存泄漏:占用的内存没有及时释放,内存泄露积累多了就容易导致内存溢出

      • 闭包

        function fn1() {
            var a = 4
            function fn2() {
              console.log(++a)
            }
            return fn2
          }
          var f = fn1()
          f()
        

3.5 闭包的应用

  • 模仿块级作用域

    for(var i = 0; i < 5; i++) {
        (function(j){
            setTimeout(() => {
                console.log(j);
            }, j * 1000);
        })(i)
    }
    
      for (var i = 0; i < lis.length; i++) {
                (function (j) {
                    lis[j].onclick = function () {
                        alert(j)
                    }
                })(i)
            }
    
  • 埋点计数器

    function count () {
    	var num = 0;
    	return function(){
    	 num++;
    	 return num;
    	}
    }
    var num = count();
    
  • 柯里化

     function curryingCheck(reg) {
                return function (txt) {
                    return reg.test(txt)
                }
            }
    
            var isPhone = curryingCheck(/^(13[0-9]|14[5|7]|15[0|1|2|3|4|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$/)
            console.log(isPhone('15810606459'))    // true
    
            var isEmail = curryingCheck(/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/)
            console.log(isEmail('wyn@nowcoder.com'))      // false
    

this指向

1. this关键字由来

this关键字由来:在对象内部的方法中使用对象内部的属性是一个非常普遍的需求。但是 JavaScript 的作用域机制并不支持这一点,基于这个需求,JavaScript 又搞出来另外一套 this 机制。

var bar = { 
	myName:"闷倒驴", 
	printName: function () { console.log(myName) } 
}
let myName = '王美丽';
bar.printName(); // '王美丽'
var bar = { 
	myName:"闷倒驴", 
	printName: function () { console.log(this.myName) } 
}
let myName = '王美丽';
bar.printName(); // '王美丽'

作用域链和 this 是两套不同的系统,它们之间基本没太多联系

2. this在哪里可以使用

全局上下文中的this

console.log(this)来打印出来全局执行上下文中的 this,最终输出的是 window 对象。所以你可以得出这样一个结论:全局执行上下文中的 this 是指向 window 对象的。这也是 this 和作用域链的唯一交点,作用域链的最底端包含了 window 对象,全局执行上下文中的 this 也是指向 window 对象

函数上下文中的this

  • 在全局环境中调用一个函数,函数内部的 this 指向的是全局变量 window。

  • 通过一个对象来调用其内部的一个方法,该方法的执行上下文中的 this 指向对象本身

function foo(){
    // 'use strict';
	console.log(this)
};
foo() // window

3. this指向总结

this指向总结

  • 当函数被正常调用时,在严格模式下,this 值是 undefined,非严格模式下 this 指向的是全局对象 window;

  • 通过一个对象来调用其内部的一个方法,该方法的执行上下文中的 this 指向对象本身

  • ES6 中的箭头函数并不会创建其自身的执行上下文,所以箭头函数中的 this 取决于它的外部函数

  • new 关键字构建好了一个新对象,并且构造函数中的 this 其实就是新对象本身

    • 当执行 new CreateObj() 的时候,JavaScript 引擎做了如下四件事:
      • 首先创建了一个空对象 tempObj;
      • 接着调用 CreateObj.call 方法,并将 tempObj 作为 call 方法的参数,这样当 CreateObj 的执行上下文创建时,它的 this 就指向了 tempObj 对象;
      • 然后执行 CreateObj 函数,此时的 CreateObj 函数执行上下文中的 this 指向了 tempObj 对象;
      • 最后返回 tempObj 对象。
  • 嵌套函数中的 this 不会继承外层函数的 this 值。

    var myObj = { 
    	name : "闷倒驴", 
    	showThis: function(){ 
    		console.log(this); // myObj
            var bar = function(){ 
            	this.name = "王美丽"; 
            	console.log(this) // window
            } 
            bar(); 
        }
    };
    myObj.showThis();
    console.log(myObj.name);
    console.log(window.name);
    
    • 解决this不继承的方法

      • 内部函数使用箭头函数
      • 将在外层函数中创建一个变量,用来存储this,内层函数通过作用域链即可访问
      var myObj = { 
      	name : "闷倒驴", 
      	showThis:function(){ 
      		console.log(this); // myObj
              var bar = ()=>{ 
              	this.name = "王美丽"; 
              	console.log(this) // window
              } 
              bar(); 
          }
      };
      myObj.showThis();
      console.log(myObj.name);
      console.log(window.name);
      
      var myObj = { 
      	name : "闷倒驴", 
      	showThis:function(){ 
      		console.log(this); // myObj
              var self = this;
              var bar = function (){ 
              	self.name = "王美丽"; 
              	console.log(self) // window
              } 
              bar(); 
          }
      };
      myObj.showThis();
      console.log(myObj.name);
      console.log(window.name);
      

4. 改变this指向的方法

4.1 call 和 apply 的共同点

都能够改变函数执行时的上下文,将一个对象的方法交给另一个对象来执行,并且是立即执行的

调用 call 和 apply 的对象,必须是一个函数 Function

4.2 call 和 apply 的区别

call 的写法

Function.call(obj,[param1[,param2[,[,paramN]]]])

需要注意以下几点:

  • 调用 call 的对象,必须是个函数 Function。
  • call 的第一个参数,是一个对象。 Function 的调用者,将会指向这个对象。如果不传,则默认为全局对象 window。
  • 第二个参数开始,可以接收任意个参数。每个参数会映射到相应位置的 Function 的参数上。但是如果将所有的参数作为数组传入,它们会作为一个整体映射到 Function 对应的第一个参数上,之后参数都为空。
function func (a,b,c) {}

func.call(obj, 1,2,3)
// func 接收到的参数实际上是 1,2,3

func.call(obj, [1,2,3])
// func 接收到的参数实际上是 [1,2,3],undefined,undefined

apply 的写法

Function.apply(obj[,argArray])

需要注意的是:

  • 它的调用者必须是函数 Function,并且只接收两个参数,第一个参数的规则与 call 一致。
  • 第二个参数,必须是数组或者类数组,它们会被转换成类数组,传入 Function 中,并且会被映射到 Function 对应的参数上。这也是 call 和 apply 之间,很重要的一个区别。
func.apply(obj, [1,2,3])
// func 接收到的参数实际上是 1,2,3

func.apply(obj, {
    0: 1,
    1: 2,
    2: 3,
    length: 3
})
// func 接收到的参数实际上是 1,2,3

4.3 call 和 apply 的用途

下面会分别列举 call 和 apply 的一些使用场景。声明:例子中没有哪个场景是必须用 call 或者必须用 apply 的,只是个人习惯这么用而已。

call 的使用场景

1、对象的继承。如下面这个例子:

function superClass () {
    this.a = 1;
    this.print = function () {
        console.log(this.a);
    }
}

function subClass () {
    superClass.call(this);  // 执行superClass,并将superClass方法中的this指向subClass
    this.print();
}

subClass();
// 1

subClass 通过 call 方法,继承了 superClass 的 print 方法和 a 变量。此外,subClass 还可以扩展自己的其他方法。

2、借用方法。还记得刚才的类数组么?如果它想使用 Array 原型链上的方法,可以这样:

let domNodes = Array.prototype.slice.call(document.getElementsByTagName("*"));

这样,domNodes 就可以应用 Array 下的所有方法了。

原理:执行数组的slice方法,把this指向伪数组

// slice2()
Array.prototype.slice2 = function (start, end) {
  start = start || 0
  end = start || this.length
  const arr = []
  for (var i = start; i < end; i++) {
    arr.push(this[i])
  }
  return arr
}

apply 的一些妙用

1、Math.max。用它来获取数组中最大的一项。

let max = Math.max.apply(null, array);

同理,要获取数组中最小的一项,可以这样:

let min = Math.min.apply(null, array);

2、实现两个数组合并。在 ES6 的扩展运算符出现之前,我们可以用 Array.prototype.push来实现。

let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];

Array.prototype.push.apply(arr1, arr2);
console.log(arr1); // [1, 2, 3, 4, 5, 6]

4.4 bind

bind 的使用

在 MDN 上的解释是:bind() 方法创建一个新的函数,在调用时设置 this 关键字为提供的值。并在调用新函数时,将给定参数列表作为原函数的参数序列的前若干项。

它的语法如下:

Function.bind(thisArg[, arg1[, arg2[, ...]]])

bind 方法 与 apply 和 call 比较类似,也能改变函数体内的 this 指向。

不同的是,bind 方法的返回值是函数,并且需要稍后调用,才会执行。而 apply 和 call 则是立即调用。

来看下面这个例子:

function add (c) {
    return this.a + this.b + c;
}

var obj = {a:1,b:2}

add.bind(obj, 5); // 这时,并不会返回 8
add.bind(sub, 5)(); // 调用后,返回 8

如果 bind 的第一个参数是 null 或者 undefined,this 就指向全局对象 window。

在vue或者react框架中,使用bind将定义的方法中的this指向当前类

相关文章:

  • Pytorch+Python实现人体关键点检测
  • 【1024】程序员福利
  • Redis产生的问题:在关闭虚拟机退出后重启发生网卡启动失败
  • 浅谈Vue3的优势
  • 如何在Vue+ElementUI项目中使用iconfont图标库
  • Python基础加强学习
  • C语言-简单的程序设计
  • 链队列基本操作
  • 多旋翼无人机仿真 rotors_simulator:基于PID控制器的位置控制---高度控制
  • Python是“真火”还是“虚火”?
  • 为什么要做数据治理以及如何进行数据治理?
  • 那些被渐渐遗忘的python知识点
  • LeetCode16. 最接近的三数之和
  • 外包干了三年,真废了。。。
  • WinLicense 3.1.3.0 Crack
  • [译]前端离线指南(上)
  • 《剑指offer》分解让复杂问题更简单
  • android 一些 utils
  • canvas绘制圆角头像
  • Docker容器管理
  • es6(二):字符串的扩展
  • miaov-React 最佳入门
  • Mysql优化
  • spring security oauth2 password授权模式
  • Work@Alibaba 阿里巴巴的企业应用构建之路
  • 温故知新之javascript面向对象
  • 智能合约开发环境搭建及Hello World合约
  • 阿里云服务器购买完整流程
  • #Z2294. 打印树的直径
  • #我与Java虚拟机的故事#连载04:一本让自己没面子的书
  • $(function(){})与(function($){....})(jQuery)的区别
  • $jQuery 重写Alert样式方法
  • (04)Hive的相关概念——order by 、sort by、distribute by 、cluster by
  • (react踩过的坑)antd 如何同时获取一个select 的value和 label值
  • (八)Docker网络跨主机通讯vxlan和vlan
  • (八)五种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB
  • (简单) HDU 2612 Find a way,BFS。
  • (接口自动化)Python3操作MySQL数据库
  • (七)c52学习之旅-中断
  • (删)Java线程同步实现一:synchronzied和wait()/notify()
  • (实战篇)如何缓存数据
  • .NET 发展历程
  • .NET 将多个程序集合并成单一程序集的 4+3 种方法
  • .net 使用$.ajax实现从前台调用后台方法(包含静态方法和非静态方法调用)
  • .net2005怎么读string形的xml,不是xml文件。
  • .pyc文件还原.py文件_Python什么情况下会生成pyc文件?
  • /usr/bin/python: can't decompress data; zlib not available 的异常处理
  • @modelattribute注解用postman测试怎么传参_接口测试之问题挖掘
  • @PreAuthorize注解
  • [Android]竖直滑动选择器WheelView的实现
  • [GN] DP学习笔记板子
  • [go 反射] 进阶
  • [Kubernetes]4. 借助腾讯云TKE快速创建Pod、Deployment、Service部署k8s项目
  • [Luogu 2816]宋荣子搭积木
  • [MySQL FAQ]系列 -- 如何利用触发器实现账户权限审计