八--JS作用域、this和原型链(一)

一、程序运行机制

1)编译阶段:
1、分词
2、构建ast
3、代码生成----转换成一段可执行的机器指令。

2)js运行阶段
1、预编译阶段(由编译器将js编译成可执行的代码)

  • 进行变量声明
  • 进行变量提升,但其值是undefined
  • 所有非表达式的函数声明,进行提升
    2、代码执行阶段
    一共有三个角色:编译器、执行引擎、作用域。

二、作用域

1、JS作用域及作用域链
作用域就是根据名称,去查找变量的规则。
作用域分为词法作用域/静态作用域和动态作用域/上下文。词法作用域就是定义在词法阶段的作用域。上下文就是在运行时的作用域。在闭包的时候上下文和作用域不一致。

var a=2

以上代码编译器先去问作用域,是不是有了一个a在当前作用域中?
作用域:是,编译器就会忽略这个声明,然后继续往下;
不是,编译器会在当前的作用域中,产生一个新的变量,命名为a;
然后,生成a=2这句话所能够被引擎执行的代码。
runtime(运行时)
引擎在执行时,会问作用域,是不是已经有一个a,在当前的作用域中?
如果是:赋值为2
如果不是:去上一层的作用域中查找。
如果找到了,就直接赋值为2,如果没找到,引擎就会抛出一个异常。

一个函数里面有函数,最外层的函数的生命周期是最久的,它们的地址和值就是栈的形式,先进后出。它们的变量作用域查找,是从内向外的一个方式查找。
在这里插入图片描述

  let a = 'global';
  console.log(a);

  function course() {
    let b = 'zhuawa';
    console.log(b);

    session();
    function session() {
      let c = 'this';
      console.log(c);

      teacher();
      function teacher() {
        let d = 'yy';
        console.log(d);

        // 作用域查找
        console.log(b);
      }
    }
  }
  course();

三、this上下文

this是在执行时动态读取上下文决定的,不是在定义时决定。
箭头函数:this永远指向定义它的对象的上一级,不管以什么方式调用。this改变只能说它外层的对象被不同的方式调用,外层对象的this被改变了

//都是指打印的this
//箭头函数
function far() {
	this.bar=23;
	const foo = {
    bar: 10,
    fn: ()=> {
      console.log(this.bar);
      console.log(this);
    }
  };
  let xv = foo.fn;//如果这里是foo.fn(),执行far(),this指向windows,new出来的指向far{bar:23}对象
  xv()
}
far();//this指向windows
new far();//this指向far{bar:23}这个对象
//普通函数
function far() {
	this.bar=23;
	const foo = {
    bar: 10,
    fn: function() {
      console.log(this.bar);
      console.log(this);
    }
  };
  let xv = foo.fn;//这里改成foo.fn(),不管是far()执行还是new出来都是指向foo这个对象的
  xv();//这就是普通函数的直接调用,指向windows
}
far();//this指向windows
new far();//this指向windows
//剪头函数
function far() {
	this.bar=23;
	const foo = {
    bar: 10,
    fn: ()=> {
      console.log(this.bar);
      console.log(this);
    }
  };
  this.xv = foo.fn;
  this.xv()
}
far();//this指向windows
new far();//this指向far{bar:23,xv:f()}这个对象
//普通函数
function far() {
	this.bar=23;
	const foo = {
    bar: 10,
    fn: function() {
      console.log(this.bar);
      console.log(this);
    }
  };
  this.xv = foo.fn;
  this.xv()
}
far();//this指向windows,因为far()内部this指向windows,xv属于this属性,这个xv方法内的this是普通函数自然指向调用它的对象外层的this
new far();//this指向far{bar:23,xv:f()}这个对象,同理

由上面可知:
普通函数:

  1. 函数直接调用,this指向windows
 function foo() {
    console.log('函数内部的this:', this);
  }

  foo();//this指向windows

2)隐式绑定 - this指向调用堆栈的上一级

function fn() {
    console.log('隐式绑定:', this.a);
  }
  const obj = {
    a: 1
  }

  obj.fn = fn;//obj属性fn的值指向fn()方法的地址
  obj.fn();//a 为1,this指向obj

如:

const o1 = {
    text: 'o1',
    fn: function() {
      return this.text;
    }
  }
  const o2 = {
    text: 'o2',
    fn: function() {
      return o1.fn();
    }
  }
  const o3 = {
    text: 'o3',
    fn: function() {
      let fn = o1.fn;
      return fn();
    }
  }

  console.log(o1.fn());//o1
  console.log(o2.fn());//01
  console.log(o3.fn());//undefined,this指向全局,因为它没有被某个对象值引用,是独立直接调用

如果要改变this指向,如o2.fn调用指向o2,
1)、人为改变this,使用call,apply,bind改变this指向
2)、不改变,即下面的方法,隐式

const o2 = {
  text: 'o2',
  fn: o1.fn//将o2的fn属性指向o1.fn地址,这样就是以o2对象调用o1.fn
}

显式绑定(bind | apply | call)

  function foo() {
    console.log('函数内部的this:', this);
  }
  foo();

  foo.call({a: 1});
  foo.apply({a: 1});

  const bindFoo = foo.bind({a: 1});
  bindFoo();

call、apply、bind的区别:
1)传参不同,apply是数组,call是逗号隔开,bind是逗号隔开
2)直接返回不同,最终执行返回相同,bind是返回一个函数,需要调用,其他是直接调用
new 关键字出来的对象里的this指向这个对象。

class Course {
    constructor(name) {
      this.name = name;
      console.log('构造函数中的this', this);
    }

    test() {
      console.log('类方法中的this', this);
    }

    asyncTest() {
      console.log('异步方法外', this);
      setTimeout(function() {
        console.log('异步方法中的this', this);
      }, 100)
    }
  }
  const course = new Course('this');
  course.test();
  course.asyncTest();

这里的异步函数settimeout传入匿名function执行,效果和全局执行函数效果相同,指向windows,把function改为独立于上下文的箭头函数即可
bind原理,手写bind

 function sum(a, b, c) {
    console.log(a, b, c, this);
    return a + b + c;
  }
Function.prototype.newBind = function(){
	//bind返回一个方法
	//传参不变
	//返回原函数执行结果
	const _this = this;
	const args = Array.prototype.slice.call(arguments);
	//arguments是一个伪数组,需要用array原型链方法slice处理变成真的数组
	const newThis = args.shift();
	return function...innerArgs){
		return _this.apply(newThis,[...args,...innerArgs);
	}
}

手写called方法:

function called(context,...rest){
	context.fn = this;
	if(context){
		const result = context.fn(...rest);
		delete context.fn;
		return result;
	} else {
		this(...rest);
	}
}

apply还可以用于多传参数组化,如:

Math.max(1,2,3);
const arr = [1,2,3];
let max = Math.max.apply(this,arr);

this指向的优先级为new>显式>隐式>默认

function fn(){
	console.log(this);
}
//隐式
const obj = {fn}
obj.fn;//this指向obj
//显式,显式》隐式
obj.fn.bind(111);//this为111
function foo(a){
	this.a = a;
}
const obj1 = {};
var bar = foo.bind(obj1);
bar(2);
console.log(obj1);//{a:2}
let bar2 = new bar();
bar2(3);
console.log(obj1);//{a:2}
//new > 显式
console.log(bar2);//{a:3}

3、闭包
闭包就是函数的定义作用域和执行作用域不一致,内部函数引用了外部的变量,当外部函数执行完时,该变量不会销毁。
闭包是一个函数和它周围状态的引用捆绑在一起的组合
1)函数作为返回值的场景:

function mail(){
let content = '';
	return function(){
	console.log(content);
	}
}
const event = mail();
event();//函数外部可以访问函数内部变量content

2)函数作为参数

let content;
function eventloop(fn){
	content = 1;
	fn();
}
function mail(){
	console.log(content)
}
eventloop(mail);

3)函数嵌套

let counter = 0;
function outer(){
	function inner(){
	counter++;
	console.log(counter);
	}
	return inner;
}

4)异步执行的闭包

for(var i = 0; i<5;i++){
	setTimeout(()=>{
		console.log(i);
	},100)
}
//以上执行,会打印5遍,每遍i的值都是5;因为等异步定时到时,在这个作用域里i已经循环完了为5
//修正:
for(var i = 0; i<5;i++){
!function(i){//立即执行方法里是可以访问外部变量的
	setTimeout(()=>{
		console.log(i);
	},100)
	}(i)//用立即执行的方法形成闭包,这是一个独立作用域,每次循环传入i,在相应的作用域内,i就是当时传入的那个值
	//还有一个种方式就是将var改成有块级作用域的let,这样也会形成一个独立作用域
}

5)立即执行遇上块级作用域

let count = 0;
(function immediate(){
	if(count == 0){
		let count = 1;
		console.log(count);//1,往最近查找就是1
	}
	console.log(count);//0
})()

6)拆分执行,多个闭包

function createIncrement() {
    let count = 0;

    function increment() {
      count++;
    }

    let message = `count is ${count}`;

    function log() {
      console.log(message);
    }

    return [increment, log];
  }
  const [increment, log] = createIncrement();
  increment();
  increment();
  increment();
  log();//这里打印出来message为count is 0
  //但是,如果在log里直接打印count,count的值是3;因为message是一个基本类型的变量,在createIncrement执行时它被赋值了,这个时候是count的初始值,之后message都不会改变,所以是0,同理,由于count是基本类型,如果在message后面紧接着将count赋值给一个变量singer = count;在log里打印singer,也是0;
  //假设count是一个引用类型,由于message是一个基本类型,所以还是打印的是count的初始值,但是在message后面紧接着将count赋值给一个变量singer = count;在log里打印singer,那就是三次操作之后的结果;

7)实现私有变量

const stack = {
	items:[],
	push:function(a){
		items.push(a)
	}
}
function createStack(){
	items = [];//私有变量,外界只能通过push访问
	return {
		push:function(a){
			items.push(a)
		}
	}
}

4、一个this🈯️向的题

var number = 5;
var obj = {
  number:3,
  fn1:(function(){//自执行,this指向windows/global
    var number;
    this.number *= 2;
    number = number * 2;
    number = 3;
    return function(){
      var num = this.number;
      this.number *= 2;
      console.log(num);//10 3
      number *= 3;
      console.log(number);//9 27
    }
  })()
}
var fn1 = obj.fn1;
fn1.call(null);
obj.fn1();
console.log(window.number);//20
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值