02.JS的进阶——原型与原型链与作用域与变量提升

本文深入探讨JavaScript中的原型链概念,包括原型、原型链、显示原型和隐式原型,以及实例对象之间的关系。同时,介绍了执行上下文的创建过程,包括全局执行上下文和函数执行上下文,以及它们如何影响变量和函数的查找规则。此外,还讨论了作用域和作用域链,以及在面试中常见的原型和执行上下文相关问题。
摘要由CSDN通过智能技术生成

 

1.原型prototype

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title></title>
	</head>
	<body>
		<!--(一个互指的关系)
			1.函数的prototype属性(图)
			* 每个函数都有个prototype属性,它默认指向一个object空对象(即:原型对象,反正就是空壳方法)
			* 原型对象中有一个属性constructor,它指向函数对象
			2.给原型对象添加属性(一般都是方法)
			* 作用:函数的所有  实例对象  自动拥有原型中的属性(方法)
		-->
		<script type="text/javascript">
			/*每个函数都有一个prototype属性,他会默认指向一个object空对象(即:原型对象,反正就是空壳方法)*/
			console.log(Date.prototype,typeof Date.prototype)//显示object
			function Fun(){//快捷修改名字,alt+shift+r
				
			}
			console.log(Fun.prototype)//默认指向一个object空对象(没有我们的属性)
			
			
			//原型对象中有个constructor,它指向函数对象
			//举例:函数中的prototype.constructor指向函数data
			console.log(Date.prototype.constructor===Date)
			console.log(Fun.prototype.constructor===Fun)
			
			//给原型对象添加属性(一般是方法)===>实例对象可以访问
			Fun.prototype.test = function(){
				console.log('test()')
			}
			
			
			
			
		</script>
	</body>
</html>

2.显示原型链和隐式原型链

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<!--
			1. 每个函数function都有一个prototype,既显示原型(属性)
			2. 每个实例对象都有一个__proto__,可称为隐式原型(属性)
			3. 对象的隐式原型的值为其对应构造函数的显示原型的值
			4. 内存结构(图)
			5. 总结:
				* 函数的prototype属性:在定义函数时自动添加的,默认值是一个空的object对象
				* 对象的__proto__属性:创建对象时自动添加的,默认值为构造函数的prototype属性值
				* 程序员能直接操作显示原型,但不能直接操作隐式原型(ES6之前)
			
			
			
			
			
			
		-->
		<script type="text/javascript">
			//一、函数定义构造函数
			function Fu(){
				
			}
			//1. 每个函数function都有一个prototype,既显示原型
			console.log(Fn.prototype)//prototype,什么时候才能加    函数定义的时候就有了
			//2. 每个实例对象都有一个__proto__,可称为隐式原型
			
			//二、创建实例对象
			var fn = new Fn()//内部语句:(fn)this.__proto__ = Fn.prototype
			console.log(fn.__proto__)// __proto__  fn对象 的时候就有了
			//3. 对象的隐式原型的值为其对应构造函数的显示原型的值
			console.log(Fn.prototype===fn.__proto__))//显示为true
			
			//四、4. 给原型添加方法
			Fn.prototype.test = function(){
				console.log('这是在原型中添加的test()方法')
			}
			//五、调用 给原型添加方法
			fn.test();
			
			//小型套娃环节
		</script>
		
		
		
		
		<!--总结:prototype和__proto__为引用变量,存放的都是地址值,并且值为一样的,所以共同指向原型
			. 意思为:找到,访问
			
			
			
		-->
	</body>
</html>

3.原型链

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>原型链</title>
	</head>
	<body>
		<!--
			1. 原型链(图解)
			*访问一个对象的属性时
				 * 先在自身属性中查找,找到返回
				 * 如果没有,再沿着__proto__这条链向上查找,找到返回
				 * 如果最终没有找到,返回undefined
			 * 别名:隐式原型链
			 * 作用:查找对象的属性(方法)
			 2. 构造函数/原型/实体对象的关系(图解)
			 3. 构造函数/原型/实体对象的关系2(图解)
			
			
			
			
			
			
			
			
			
			
		-->
		
		<script type="text/javascript">
			//创建构造函数
			function Fn(){
				this.test1 = function(){
					console.log('test1()')
				}
			}
			
			
			Fn.prototype.test2 = function(){
				console.log('test2()')
			}
			
			var fn = new Fn()
			fn.test1()
			fn.test2()
			console.log(fn.toString())
		</script>
		
		
		
	</body>
</html>

4.原型链的属性问题

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<!--
			1. 读取对象的属性值时;会自动到原型链中查找
			2. 设置对象的属性值时;不会查找原型链,如果当前对象没有此属性,直接添加属性并没有设置其值
			3. 方法一般定义在原型中,属性一般通构造函数定义在对象本身上
		-->
	</body>
	<!--<script type="text/javascript">
		function Person(name,age){
			this.name = name;
			this.age = age;
		}
		Person.prototype.setName = function(name){
			this.name = name;
		}
		person.prototype.sex = '男';
		
		var p1 = new Person('Tom',12)
		p1.setName('jack')
		console.log(p1.name,p1.age,p1.sex)
		p1.sex='女'
		console.log(p1.name,p1.age,p1.sex)
		
		var p2 = new Person('Bob',23)
		console.log(p2.name,p2.age,p2.sex)
		
	</script>
	-->
	<script type="text/javascript">
		
		
		function Fn(){
			
		}
		Fn.prototype.a = 'xxx'
		var fn1 = new Fn()
		console.log(fn1.a,fn1)
		//原型上的属性 实例对象自动可见
		
		var fn2 = new Fn()
		fn2.a = 'yyy'
		console.log(fn1.a,fn2.a,fn2)// xxx(查找原型链上的属性) yyy(设置属性)
		
		function Person(name , age){
			this.name = name
			this.age = age
		}
		//在原型链上面设置一个方法
		Person.prototype.setName = function(name){
			this.name = name
		}
		//创建对象实例
		var p1 = new Person('Tom', 12)
		p1.setName('Bob')
		var p2 = new Person('jack', 12)
		p2.setName('cat')
		console.log(p2)
		//属于同一个实例对象所以隐式原型都一样
		console.log(p1.__proto__===p2.__proto__)//true
		
		
		
	</script>
	
	
	
</html>

5.原型链的补充

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<!--实现继承(继承实际的方法)主要是依靠原型链来实现-->
	
		
		<!--
		原型链的基本思路:
		
			1.利用原型让一个引用类型继承另一个引用类型的属性和方法
			
			每个构造函数都有一个原型对象(var nan = new Person())
			原型对象都包含一个指向构造函数想指针(constructor),
			而实例对象都包含一个指向原型对象的内部指针(__proto__)。
			如果让原型对象等于另一个类型的实例,
			此时的原型对象将包含一个指向另一个原型的指针(__proto__),
			另一个原型也包含着一个指向另一个构造函数的指针(constructor)。
			假如另一个原型又是另一个类型的实例……这就构成了实例与原型的链条。
			
			
			
			
		-->
		
		
		
		<script type="text/javascript">
			/*
			 1. 函数显示原型指向的对象是默认是空的实例对象(但object不满足)
			 * */
			console.log(Fn.prototype instanceof Object)//true
			console.log(Object.prototype instanceof Object)//false
			console.log(Function.prototype instanceof Object)//ture
			
			/*
			 2.所有的函数都是Function的实例(包含Function)
			 * */
			console.log(Function.__proto__===Function.prototype)
			
			/*
			 3. Object的原型对象是原型链的尽头
			 * */
			console.log(Object.prototype.__proto__)//null
			
			
			
			/*清楚举例说明*/
			
			
//			function animal(){
//				this.type = "animal";
//			}
//			animal.prototype.getType = function(){
//				return this.type;
//			}
//			 
//			function dog(){
//				this.name = "dog";
//			}
//			dog.prototype = new animal();
//			 
//			dog.prototype.getName = function(){
//				return this.name;
//			}
//			var xiaohuang = new dog();
 
//原型链关系
		
//			xiaohuang.__proto__ === dog.prototype
//			dog.prototype.__proto__ === animal.prototype
//			animal.prototype.__proto__ === Object.prototype
//			Object.prototype.__proto__ === null
			
			
			
			
			
		</script>
	</body>
</html>

6.探索instanceof

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<!--
			1.instanceof是如何判断的?
			* 表达式:A instanceof B
			* 如果函数B的显示原型对象在A对象的原型链上,返回true,否则返回false
			2.Function是通过new自己产生的实例
			
			
		-->
	</body>
	<script type="text/javascript">
		/*
		 1.案例
		 * */
		function Foo(){}
		var f1 = new Foo()
		console.log(f1 instanceof Foo)//true
		console.log(f1 instanceof Object)//true
		
		/*
		 2. 案例
		 * */
		console.log(Object instanceof Function)//true
		console.log(Object instanceof Object)//true
		console.log(Function instanceof Function)//true
		console.log(Function instanceof Object)//true
		
		function foo(){}
		console.log(Object instanceof Foo)//false
		
		
		
		
		
		
		
	</script>
</html>

7.面试常问

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<!--
			测试题1
			
		-->
		
		<script type="text/javascript">
			function A(){
				
			}
			A.prototype.n = 1
			
			var b = new A()
			
			A.prototype = {
				n: 2,
				m: 3
			}
			
			var c = new A()
			console.log(b.n,b.m,c.n,c.m)//1,undefine,2 3
			
			
			/*测试题2*/
			function F(){}
			Object.prototype.prototype.a = function(){}{
				console.log('a()')
			}
			Function.prototype.b = function(){
				console.log('b()')
			}
			var f = new F()
			f.a()//a
			f.b()//null
			F.a()//a
			F.b()//b
			
			
			
			
			
		</script>
		
		
		
	</body>
</html>

8.变量提升与函数执行(待补充)

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<!--
			1. 变量声明提升(变量声明提前执行 但是没有赋值)
				* 通过var定义(声明)的变量,在定义语句之前就访问到
				* 值:undefined
			2. 函数声明提升
				* 通过function声明的函数,在之前就可以直接调用
				* 值:函数定义(对象)
			3.  问题:变量提升和函数提升如何产生的?
			
			
			
		-->
		<script type="text/javascript">
			/*
			 面试题:输出underfind
			 为什么:在函数体执行之前,先这样声明了var a; 但是没有定义赋值
			 所以输出的时候按照规则先输出了上面的var a
			 * */
			var a = 3
			function fn(){
				//var a ; 默认
				console.log(a)//输出underfind
				var a = 4
			}
			fn()
			
			//第一种情况:变量提升
		
			console.log(b)//输出:undefined 变量提升
			
			var b = 3
			
			//第二种情况:函数提升
			fn2()//可调用  函数提升
			fn3()//不能  变量提升
			
			function fn2(){
				console.log("fn()")//输出 :fn()
			}
			
			//第三种情况:一个用变量声明的函数(变量提升)
			var fn3 = function(){
				console.log('fn3()')
			}
			
			
			
			
			
			
			
			
		</script>
	</body>
</html>

9.执行上下文

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<!--
			1. 代码分类(位置)
				* 全局代码
				* 函数(局部)代码
				* 
			2. 全局执行上下文
				* 在执行全局代码前将window确定为全局执行上下文
				* 对全局数据进行预处理(马上即将要执行 做准备工作)
					* var定义的全局变量==>underfind ,添加window的属性
					* function声明的全局函数==>赋值(fun),添加window的方法
					* this==>赋值(window)
				*开始执行全局代码
				* 
			3. 函数执行上下文
				* 在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象
				* 对局部数据进行预处理
					* 形参变量==>赋值(实参)==>添加为执行上下文的属性
					* argument==>赋值(实参列表),添加为执行上下文的属性
					* var定义的局部变量==>underfind,添加执行上下文的属性
					* function声明的函数==>赋值(fun),添加为执行上下文的方法
					* this==>赋值(调用函数的对象)
				* 开始执行函数体的代码
			
			
			
			总结:总的来说是一个预处理 让栈给他分配内存 进行预处理的一个操作,这样就会理解变量提升和函数提升了
			
			
			
		-->
		<script type="text/javascript">
			console.log(a1,window.a1)
			window.a2()
			console.log(this)
			
			var a1 = 3//35直接跳到39 说明中间的执行过了
			function a2(){
				console.log("a2()")
			}
			console.log(a1)
			
			console.log("2333333333333333333333333333333333");
			//函数执行上下文
			function fn(a1){
				console.log(a1)//2
				console.log(a2)//underfind
				a3()//a3()
				console.log(this)//window
				console.log(arguments)//arguments[2]伪数组(2,3)
				
				var a2 = 3
				function a3(){
					console.log('a3()')
				}
			}
			fn(2,3)
			
			
			
			
			
			
			
			
			
			
			
			
			
		</script>
		
		
		
	</body>
</html>

10.执行上下文栈

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<!--
		栈:先进后出
			1. 在代码执行执行前 ,JS引擎就会创建一个栈来存储管理所有的执行上下文对象
			2. 在全局执行上下文(window)确定后,将其添加到栈中(压栈)
			3. 在函数执行上下文创建后,将其添加到栈中(压栈)
			4. 在当前函数执行完后,将栈顶的对象移除(出栈)
			5. 当所有的代码执行完后,栈中只剩下window
			
			
			
			
			
		-->
		<script type="text/javascript">
			
			var a = 10
			var bar = function(x){
				var b = 5
				foo(x + b)
			}
			var foo = function(y){
				var c = 5
				console.log( a + c + y)//
			}
			bar(10)
			bar(10)
			//一共有5次
			
			
			
			//调用的时候执行上下文
			//1.window执行上下文 2.32行 3.26行
			//预处理进行执行上下文:n+1
			
			
			
			
			
			
			
			
			
		</script>
		
	
	</body>
</html>

11.执行上下文2

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<!--
			1. 依次输出什么
			2. 整个过程中产生了几个执行上下文 5个
			
			
		-->
		<script type="text/javascript">
			console.log('global begin' + i)//global begin underfind
			var i = 1
			foo(1);
			function foo(i){
				if(i==4){
					return;
				}//递归内部都有结束调用的条件
				console.log('foo() begin:'+i);//foo() begin:1 2 3
				foo(i + 1);//递归调用:在函数内部自己调用自己
				console.log('foo() end:' + i);//foo() end:3 2 1
			}
			console.log('global end:' + i);
			
			
			
			
			
			
			
			
			
		</script>
		
		
		
		
	</body>
</html>

12.面试题

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<script type="text/javascript">
			//测试题1:先执行变量提升 再执行函数提升
			
			function a(){}
			var a;
			console.log(typeof a)//输出:function
			
			
			//测试题2:
			//(!(在)不在)
			if (!(b in window)) {//b在window有没有
				var b = 1;
			}
			console.log(b)//underfind
			
			
			
			//测试题3:
			var c = 1
			function c(c){//里面代码 没机会执行 上下文预处理
				console.log(c)//输出:报错
			}
			c(2)
			//和函数名有关系 var 声明了
			//所以c不是一个函数
			
		</script>
	</body>
</html>

13.作用域

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<!--
			1. 理解
				* 就是一块‘地盘’,一个代码所在的区域
				* 它是静态的(相对于上下文对象),在编写代码时就确定了
			2. 分类
				* 全局作用域
				* 函数作用域
				* 没有块作用域(ES6有了)
			3. 作用
				* 隔离变量,不同作用域下的同名变量不会有冲突
		-->
		<script type="text/javascript">
		/*
		 没有块作用域
		 if(true){
		 	var c = 3
		 }
		 console.log(c)
		 */
			
			var a = 10, b = 20;
			
			function fn(x){
				var a = 100,c= 300;
				console.log('fn()',a,b,c,x)//100 20 300 10
				
				function bar(x){
					var a =1000,
					d = 400
					console.log('bar()',a,b,c,d,x)//1000 20 300 400 100 //1000 20 300 400 200
				}
				bar(100)
				bar(200)
			}
			fn(10)
			
		</script>
		
		
		
		
	</body>
</html>

14.作用域与上下文

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<!--
			1. 区别1
				* 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
				* 全局执行上下文环境是在全局作用域确定之后,js代码马上执行创建
				* 函数执行上下文环境是在调用函数时,函数体代码执行之前创建
			2. 区别2
				* 作用域是静态的,只要函数定义好了就一直存在,且不会再变回
				* 执行上下文是动态的,调用函数时创建,函数调用结束时就会自动释放
			3.联系3
				* 执行上下文环境(对象)是从属于所在的作用域
				* 全局上下文环境==>全局作用域
				* 函数上下文环境==>对应的函数使用域
		-->
		<script type="text/javascript">
			
			var a = 10, b = 20;
			
			function fn(x){
				var a = 100,c= 300;
				console.log('fn()',a,b,c,x)//100 20 300 10
				
				function bar(x){
					var a =1000,
					d = 400
					console.log('bar()',a,b,c,d,x)//1000 20 300 400 100 //1000 20 300 400 200
				}
				bar(100)
				bar(200)
			}
			fn(10)
			
		</script>
		
		
		
		
		
		
	</body>
</html>

15.作用域链

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		?
		<!--
			1. 理解
				* 多个上下级关系的作用域形成的链,它的方向是从下到上(从内到外)
				* 查找变量时就是沿着作用域链来查找的
			2. 查找一个变量的查找规则
				* 在当前作用域下的执行上下文查找对应的属性,如果有直接返回,否则进入2
				* 在上一级作用域的执行上下文中查找对应的属性,如果有直接返回,否则进入3
				* 再次执行2的相同操作,直到全局作用域。如果还找不到就抛出找不到的异常
			
			
		-->
		<script type="text/javascript">
			var a = 1
			function fn1(){
				
				var b = 2
				function fn2(){
					var c =3
					console.log(a)
					console.log(b)
					console.log(c)
					console.log(d)
					
				}
				fn2()
			}
			fn1()
			
			
			
			
			
			
			
			
			
		</script>
		
		
		
	</body>
</html>

16.作用域面试题

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<script type="text/javascript">
			var x = 10;
			function fn(){
				console.log(x)//10
			}
			function show(f){
				var x = 20;
				f();
			}
			show(fn);
			
			
			
			
		</script>
	</body>
</html>

17.作用域面试题2

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<script type="text/javascript">
			var fn = function(){
				console.log(fn)//输出整个函数
			}
			fn()
			
			var obj = {
				fn2:function(){
					console.log(this.fn2)//这样才能找到
					console.log(fn2)//报错找不到对象
				}
			}
			obj.fn2()
			
			
		</script>
	</body>
</html>

18.引入

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>引入</title>
	</head>
	<body>
		<button>测试1</button>
		<button>测试2</button>
		<button>测试3</button>
	
	<!--
		需求:点击某个按钮,提示点击的是第n个按钮
	-->
		
		<script type="text/javascript">
			
	      /*var btns = document.getElementsByTagName('button');
			//添加历遍和监听
			for (var i=0,length=btns.length;i<length;i++)
			var btn = btns[i]
			btn.onclick = function(){
				alert("第"+(i+1)+"个");//不可行
			}*/
			
		/*	var btns =document.getElementsByTagName('button')
			for (var i = 0; i <btns.length ; i++) {//length=btns.length;i<length效率提升:面试会问
				var btn = btns[i]
				btn.onclick = function(){
					alert('第'+(i+1)+'个');
					
				}
			}错误:都是第四个
			*/
			
			//第二种:
			/*var btns = document.getElementsByTagName('button')
			for (var i = 0; i < btns.length; i++) {
				var btn = btns[i]
				//得出下标
				btn.index = i
				btn.onclick =function(){
					alert('第' + (this.index+i)+ '个')
				}
			}*/
			//第三种:
			/*var btns = document.getElementsByTagName('button')
			
			for (var i = 0; i < btns.length; i++) {
				(function (i){
					var btn = btns[i]
					btn.onclick = function(){
						alert('第' + (this.index+i)+ '个')
					}
				})(i)
			}*/
			
			
			
			
			
			//第三种:自己练习:闭包
			var btns = document.getElementsByTagName('button')
			for (var i = 0; i < btns.length; i++) {
			(function(i){
					var btn = btns[i]
					btn.onclick= function(){
						alert('第'+(i+1)+'个')
					}
			})(i)	
		}
			
			
			
		</script>
		
		
	</body>
</html>

 


总结

有待补充完善

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值