一、作用域 js中作用域是指可访问变量,对象,函数的集合,也就是调用它们能生效的代码区块。在js中没有块级作用域,只有全局作用域和函数作用域

1、全局,函数作用域

var a = 10
function f1(){
		var b = c = 20;
		console.log(a);     //10
		console.log(c);     //20
		function f2() {
			 console.log(b); //20
		}f2();
}
f1();
console.log(a);     //10
console.log(c);     //20
console.log(b);     //error

var b = c = 20 是指 var b = c; c = 20 在f1函数中c没使用var声明,所以c为全局变量,b为局部变量,绑定在f1函数下,外部访问不到。

2、模仿块级作用域 没有块级作用域,但是有if(),for()等块语句,在块语句内部定义的变量会保留在它们已经存在的作用域内,举个栗子:

if(true) {
		var word = 'hello';
		console.log(word);  //hello
}
console.log(word);      //hello

if()语句存在全局作用域下,所以内部定义的变量存在于全局作用域中,无论在哪都可以访问。

function add(num) {
		if(num > 10) {
				var num = 10;
				console.log(num);   //10
		}
		console.log(num);       //10
}
add(11);
console.log(num);   //Uncaught ReferenceError: num is not defined

此时if()在add函数中,内部定义的变量存在于add函数的作用域中,只有在add函数和块语句中才可以访问到,外部无法访问。

3、使用自执行的匿名函数包裹块语句构建块作用域,也叫私有作用域

function add(num) {
		for(var i = 0; i < num; i++) {
			console.log(i);		//0,1,2,3,4,5,6,7,8,9
		}
		console.log(i);		//10
	}
add(10);

将代码改为

function add(num) {
		(function () {
			for(var i = 0; i < num; i++) {
					 console.log(i);	//0,1,2,3,4,5,6,7,8,9
				}
		})()
	console.log(i);		//Uncaught ReferenceError: i is not defined
}
add(10);

此时变量i只能在for()循环中访问到,在add函数和外部都无法访问,并且在匿名函数中定义的任何变量都会在执行结束时被销毁,所以变量i只能在for()循环中使用。

二、执行上下文 javascript运行的代码环境有三种:

  • 全局代码:代码默认运行的环境,最先会进入到全局环境中
  • 函数代码:在函数的局部环境中运行的代码
  • Eval代码:在Eval()函数中运行的代码
  • 全局上下文:是最外围的一个执行环境,web浏览器中被认为是window对象。在初始化代码时会,先进入全局上下文中
  • 执行上下文:每当一个函数被调用时就会为该函数创建一个执行上下文,每个函数都有自己的执行上下文
function f1() {
		var f1Context = 'f1 context';
		function f2() {
				var f2Context = 'f2 context';
				function f3() {
						var f3Context = 'f3 context';
						console.log(f3Context);
				}
				f3();
				console.log(f2Context);
		}
		f2();
		console.log(f1Context);
}
f1();
//结果:
//f3 context
//f2 context
//f1 context

全局上下文:拥有f1() f1()的执行上下文:有变量f1Context和f2() f2()的执行上下文:有变量f2Context和f3() f3()的执行上下文:有变量f3Context

ECS:执行环境栈,可以理解为代码执行的土壤,即代码执行的地方 js是单线程,任务都为同步任务的情况下某一时间只能执行一个任务

执行一段代码首先会进入全局上下文中,并将其压入ECS中栈顶 首先执行f1(),为其创建执行上下文,进入到栈顶位置,全局上下文被往下压 f1()中有f2(),再为f2()创建f2()的执行上下文,f2()进入到栈顶位置,f1()被往下压, 依次,最终全局上下文被压入到栈底,f3()的执行上下文在栈顶 f3()执行完后,ECS就会弹出其执行上下文(内部变量随之被销毁),f3()上下文弹出后,f2()上下文来到栈顶,开始执行f2(),依次,最后ECS中只剩下全局上下文,它等到应用程序退出,例如浏览器关闭时销毁。

function foo(i) {
		if(i  == 3) {
				return;
		}
		foo(i+1);
		console.log(i);
}
foo(0);

ECS栈顶为foo(3)的的上下文,直接return弹出后,栈顶变成foo(2)的上下文,执行foo(2),输出2并弹出,执行foo(1),输出1并弹出,执行foo(0),输出0并弹出,关闭浏览器后全局EC弹出,所以结果为2,1,0。

PS.JS数据类型 基本类型:5大类,Number、String、Boolean(true,false)、Undefined(undefined)、Null(null) 引用类型:Object、Array、Function

三、原型和原型链 1、对象(普通对象、函数对象)

  • 所有引用类型(函数Function,数组Array,对象Object)都拥有__proto__属性(隐式原型)
  • 所有函数拥有prototype属性(显式原型)(仅限函数)
  • 原型对象:拥有prototype属性的对象,在定义函数时就被创建

2、构造函数 //创建构造函数

function Word(words){
		this.words = words;
}
Word.prototype = {
		alert(){
				alert(this.words);
		}
}
//创建实例
var w = new Word("hello world");
w.print = function(){
		console.log(this.words);
		console.log(this);  //Person对象
}
w.print();  //hello world
w.alert();  //hello world

print()方法是w实例本身具有的方法,所以w.print()打印hello world;alert()不属于w实例的方法,属于构造函数的方法,w.alert()也会打印hello world,因为实例继承,构造函数原型上的方法。 实例w的隐式原型指向它构造函数的显式原型,指向的意思是恒等于 w.proto === Word.prototype

当调用某种方法或查找某种属性时,首先会在自身调用和查找,如果自身并没有该属性或方法,则会去它的__proto__属性中调用查找,也就是它构造函数的prototype中调用查找。所以很好理解实例继承构造函数的方法和属性: w本身没有alert()方法,所以会去Word()的显式原型Word.prototype中调用alert(),即实例继承构造函数的方法。  

3、原型和原型链**【里面的proto,因为编辑器显示的问题,其实都是__proto__】**

Function.prototype.a = "a";
Object.prototype.b = "b";
function Person(){}
console.log(Person);    //function Person()
let p = new Person();
console.log(p);         //Person {} 对象
console.log(p.a);       //undefined
console.log(p.b);       //b

实例p上面并没有a属性,那么会通过__proto__向上查找,根据: p.proto == Person.prototype,然后Person.prototype上也没有a属性,Person.prototype仍然是一个对象,上面仍然具有__proto__属性,根据: Person.prototype.proto == Function.prototype //false

Person.prototype.proto == Object.prototype //true

Object.prototype.proto == null //再上一级就是null了

此时,Object.prototype.b = "b",所以p.a是undefined,而p.b是"b", 因为没有定义Object.prototype.a,只定义了Function.prototype.a

总结:

1.查找属性,如果本身没有,则会去__proto__中查找,也就是构造函数的显式原型中查找,如果构造函数中也没有该属性,因为构造函数也是对象,也有__proto__,那么会去它的显式原型中查找,一直到null,如果没有则返回undefined 2.p.proto.constructor  == function Person(){} 3.p._proto.proto== Object.prototype 4.p._proto.proto.proto== Object.prototype.proto == null          5.通过__proto__形成原型链而非protrotype