【JavaScript 例题】函数作用域、参数传递、this指向

PS:文字解释为本人理解,少许内容借鉴参考其他博文,或许有很多不全面的地方,至少执行结果是真的。而至于理论知识部分,推荐阅读相关书籍或文章。

👉👉👉传送门:详解JavaScript作用域和作用域链


难度:★☆☆☆☆

例1:

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

// err: a is not defined

例1中,变量 a 无值且未声明,所以报错。


例2:

var a;
function fn() {
	console.log(a);
}
fn();

// undefined

等同

function fn() {
	console.log(a);
}
fn();
var a;	// 变量被提升

// undefined

例2中,声明了全局变量 a,但并未赋值,所以打印出 ‘undefined’。
关于变量提升:所有的声明都会提升到当前作用域的最顶部,而值必须遵循执行顺序。


例3:

function fn() {
	console.log(a);
}
fn();
a = 11;

// err: a is not defined

或者

console.log(a);
a = 55;

// err: a is not defined

例3中,两种情况均报错,因为隐式变量不存在变量声明提升。


例4:

a = 0;
function fn() {
	console.log(a);
}
fn();
console.log(a);

// 0
// 0

等同

var a = 0;
function fn() {
	console.log(a);
}
fn();
console.log(a);

// 0
// 0

例4中,即使没有使用 var 关键字定义变量 a ,但却被隐式声明为全局变量,而函数内并没有声明同名变量,所以它直接拿全局变量打印。


例5:

function fn() {
	a = 11;
	console.log(a);
}
// console.log(a); // 直接报错,a is not defined,中断后面代码的执行
fn();
console.log(a);

// 11
// 11

例5中,函数内并未使用 var 关键字声明,那么当函数被调用之后,a 成了全局变量,所以外部可获取,如果未调用函数,a 就还不是全局变量所以报错。


例6:

var a = 0;
function fn() {
	a = 11;
	console.log(a);
}
console.log(a);
fn();
console.log(a);

// 0
// 11
// 11

例6中,原理和例5一样,函数内并未使用 var 声明变量,所以只要调用了该函数,那么 a 就成了全局变量,并覆盖掉原值。


例7:

function fn() {
	console.log(a);
	var a = 11;
}
fn();
console.log(a);

// undefined
// err: a is not defined

等同

function fn() {
	var a;
	console.log(a);
	a = 11;
}
fn();
console.log(a);

// undefined
// err: a is not defined

例7中,变量 a 的声明被提升,作用域为当前函数,所以是局部变量,而又因为值是在打印后才有的,所以打印时为 ‘undefined’,外部无法获取局部变量,所以报错。


例8:

function fn() {
	var a = 11;
	console.log(a);
}
fn();
console.log(a);

// 11
// err: a is not defined

例8中,a 被关键字 var 声明为局部变量,所以外部无法访问获取 a 值,报错。


例9:

function fn() {
	a = 11;
	console.log(a);
	var a = 0;
}
fn();
console.log(a);

// 11
// err: a is not defined

等同

function fn() {
	var a;	//变量提升
	a = 11;
	console.log(a);
	a = 0;
}
fn();
console.log(a);

// 11
// err: a is not defined

例9中,只要该变量在当前作用域有被声明,无论在当前作用域的哪个地方,都会被提升到顶部,所有当前作用域同名变量,共用一个声明,即全部均为局部变量。【若没有被声明,则所有该变量均为全局变量,可回看 例5、例6 】。而打印值根据执行顺序进行,所以 a = 0; 并未被执行打印。


难度:★★☆☆☆

例10:

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

// undefined

等同

function fn(a) {
	var a;	// 形参作为变量被函数隐式声明【局部变量】
	console.log(a);
}
fn();

// undefined

例10中,形参(变量) a 在函数内【当前作用域】被隐式声明,所以不会报错,而是打印 ‘undefined’。


例11:

var a = 11;
function fn(a) {
	console.log(a);
	a = 43;
}
fn();
console.log(a);

// undefined
// 11

例11中,因为带形参 a 声明函数 fn,所以 a 在函数内部被隐式声明,为局部变量,所以第一次打印 ‘undefined’,第二次则打印全局的值,即 ‘11’;


例12:

var a = 11;
function fn(a) {
	console.log(a);
	a = 43;
}
fn(11);
console.log(a);

// 11
// 11

例12中,形参被隐式声明为局部变量,所以 fn 里的 a = 43; 其实是局部变量,无法影响外部的全局变量值。还有注意细节,调用的函数的时候,传入了值,所以第一次打印传入的值 ‘11’,第二次打印全局变量的值 ‘11’ ,不要被迷惑同时注意细节。


例13:

function fn(a, b) {
	console.log(b);
}
fn(11);	

// undefined

例13中,函数传入的值,对应定义的形参位置。只传入一个值,则默认为第一个形参的值。所以 ‘11’ 是 形参 a 的值,b 没有值传入。而形参 b,只被函数隐式声明,并没有值,所以打印 ‘undefined’。


例14:

function fn(a, b) {
	console.log(b);
	var b = a;
	console.log(b);
}
fn(4, 55);

// 55
// 4

例14中,第一次打印的是形参 b 的值,第二次因为变量(也是形参) b 值被 a 值赋值(覆盖),所以打印出 a 值。


例15:

var a = 0;
function fn(x) {
	console.log(a);
	var a = x;
	console.log(a);
}
fn(5);
console.log(a);

// undefined
// 5
// 0

例15中,因为函数内部有用 var 声明变量,所以函数内所有 a 变量均为局部变量,所以第一次打印 ‘undefined’,因为只声明而没有值;第二次,因为传入之为 5,即 a = x = 5; ,所以打印出 5;第三次,不用说了,外部无法获取内部值,所以打印的是全局变量的值 0。


例16:

var a = 11;
function fn(a, b) {
	b = 44;
	console.log(b);
}
fn(0, 97);
console.log(a);

// 44
// 11

例16中,函数体内赋值变量的优先级大于参数值,所以优先打印 ‘44’。

关于作用域中的访问优先级:【变量提升 < 传参 < 函数体 < 首行赋值】
👉👉👉详情:js函数作用域中的优先级


例17:

var fn = function() {
	console.log(this);
}
fn();

// window

或者

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

// window

例17中,由于没有指定对象调用函数,即该函数不属于任何对象,所以默认 this 指向 window。


例18:

function foo() {
	var a = 1;
	console.log(this);
	function bar() {
		console.log(this);
	}
	bar();
}
foo();

// window
// window

或者

var foo = function() {
	var a = 1;
	console.log(this);
	var bar = function() {
		console.log(this);
	}
	bar();
}
foo();

// window
// window

例18中,原理同例17,函数并未被任何对象调用,未指向任何对象,所以默认 this 指向 window。


例19:

var a = 0;
function fn() {
	var a = 11;
	console.log(this.a);
}
fn();

// 0

例19中,函数并未被任何对象调用,所以默认 this 指向 window,所以 this.a 指向 window.a,即全局 ‘a = 0’。


例20:

var a = 0;
var fn = function() {
	var a = 11;
	console.log(this.a);
	console.log(a)
}
fn();

// 0
// 11

例20中,与例19同理,函数并未被任何对象调用,所以 this.a 等于 window.a = 0,第二个不用说了,它打印的是局部变量a。


例21:

var a = 0;
function fn() {
	a = 15;
	console.log(this.a);
	console.log(a);
}
fn();

// 15
// 15

例21中,与例20同理,但是注意,函数体内变量 a 并未被关键字声明,所以它是全局变量,所以两个都是打印 ‘15’。


例22:

var a = 0;
var fn = function(a) {
	var a = 15;
	console.log(this.a);
	console.log(a)
}
fn(6);

// 0
// 15

例22中,第一次打印同上,第二次打印出 ‘15’。【变量提升 < 传参 < 函数体 < 首行赋值】


例23:

var obj = {
	fn : function() {
		console.log(this);
	}
}
var foo = obj.fn();

var bar = obj.fn;
bar();

// obj
// window

例23中,第一次打印,函数被对象 obj 对象调用:obj.fn(),所以 this 指向当前对象;第二次,var bar = obj.fn; 把函数引用赋值给一个变量,造成 this 丢失。所以 this 指向了全局对象,window。


难度:★★★☆☆

例24:

var a = 0;
var obj = {
	a : 15,
	b : function() {
		console.log(this.a);
		var a = 3;
		console.log(this);
	}
}
var foo = obj.b();
var bar = obj.b;
bar();

// 15
// obj
// 0
// window

例24中,原理同例23,首先看 foo ,即 obj.b();,由于被当前对象 obj 调用,所以 this 指向 obj,所以打印 ‘15, obj’;再看 bar,例23中说了,var bar = obj.b; 把函数引用赋值给一个变量,造成 this 丢失。所以 this 指向了全局对象,window。而至于函数体内的 var a = 3; 为局部变量,完全是为了混淆视听。


例25:

var a = 0;
var obj = {
	a : 14,
	b : function() {
		var c =() => {
			console.log(this);
		}
		var d = function() {
			console.log(this);
		}
		c();
		d();
	}
}
var foo = obj.b();
var bar = obj.b;
b();

// obj
// window
// window
// window

例25中,箭头函数 this 会跟随它的父级函数的 this 的指向。即跟随 obj.b() 的指向。第二部分,this 丢失,所以箭头函数也跟着指向 window。


例26:

var x = 12;
function fnA() {
	console.log(x);
}
function fnB(f) {
	var x = 44;
	f();
}
fnB(fnA);

// 12

例26中,【函数的声明决定其作用域,而不是调用决定作用域】,即函数在哪里创建的,就用哪里的变量,找不到才往父级作用域找。
在这里插入图片描述


例27:

【写个闭包】

var a = 14;
function fun() {
	var a = 22;
	return function() {
		var a = 36;
		console.log(a);
	}
}
var fn = fun();
fn();

// 36

例27中,若是当前声明的函数内有声明变量,则用其内部的变量,而不会跑出去使用外部的变量。当没有 var a = 36; 这条变量时,它才会拿父级的 var a = 22;这个过程称之为【作用域链】
在这里插入图片描述


例28:

var x = 10;
function fn(){
	console.log(x);
}
function show(f){
	var x = 20;
	(function(){
		f();
	})();
}
show(fn);

// 10

例28中,与例27、例26是一样的道理。


例29:

var a = 100;
function fn() {
	var b = 2*a;
	var a = 200;
	var c = a/2;
	function foo(x) {
		console.log(x);
	}
	foo(b);
	foo(c);
}
fn();

// NaN
// 100

例29中,只要见到函数内有 var a ,那想都不用想,把全局变量 var a = 100 无视掉。首先看 b,乘号 * 强制将a的数据类型转换成数值型【隐式转换】,但因 a 只有声明被提升,而没有值,所以 a 为 ‘undefined’,那么此时 var b = 2 * undefined 含非数值类型,直接输出 ‘NaN’。而第二次打印,‘100’ 不用说了,就 var c = 200 / 2


例30:

function fun(n, o) {
	console.log(o);
	return {
		fun : function(m) {
			return fun(m, n);
		}
	};
}
// 第一部分
var a = fun(0);	// undefined
a.fun(1);	// 0
a.fun(2);	// 0
a.fun(3);	// 0

// 第二部分
var b = fun(0).fun(1).fun(2).fun(3);
// undefined, 0, 1, 2

// 第三部分
var c = fun(0).fun(1);	// undefined, 0
c.fun(2);	// 1
c.fun(3);	// 1

第一部分:
在这里插入图片描述
第二部分:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第三部分:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


例31:

+function() {
	console.log(a);
	var a = 5;
	function a() {};
	console.log(a);
	function b() {};
	b = 6;
	console.log(b);
	var c = d = b;
}
console.log(d);
console.log(c);

// f a() {}
// 5
// 6
// 6
// err: c is not defined

作用域访问优先级:【变量提升 < 传参 < 函数体 < 首行赋值】
第一次:由于函数体优先级大于变量提升,所以不是打印 ‘undefined’,而是打印函数体;
第二次:由于此次是“首行赋值”,即打印前遇到该变量且有值,那么优先获取,而不是函数体;
第三次:同第二次打印一样,赋值大于函数体,最大优先级(别理它是否全局变量);
第四次:d = b; 在函数体内没有被 var 声明,所以是全局变量,外部可取;
第五次:由于变量 c 在函数体内被 var 声明,所以是局部变量,外部不可取,直接报错未找到定义。
在这里插入图片描述


难度:★★★★☆

例32:

【360经典试题】

window.val = 1;
var obj = {
	val : 2,
	dbl : function() {
		console.log(this);
		this.val *= 2;
		console.log(val);
		console.log(this.val);
	}
}
var ff = obj.dbl();
var fn = obj.dbl;
fn();

// obj
// 1
// 4
// window
// 2
// 2

第一部分:对象 obj 调用函数 dbl(),所以 this 指向 obj,而 val 因为没有在 dbl() 函数体内声明,所以使用的是全局变量的值,this.val 则是 obj.val 的值。
第二部分:var fn = obj.dbl; 把函数引用赋值给一个变量,造成 this 丢失,所以这里的 this 指向 window。所以函数内部 this.val => window.val => val => 1 这三者是相等的。而又 this.val *= 2; ,所以 window.val = window.val * 2 = 1 * 2 = 2;
在这里插入图片描述
如何改变 var fn; 中 this 的指向?
可通过call()、apply()等方法来改变 this 指针,当然不同情况不同用法
在这里插入图片描述


防坑点总结:

  1. 函数首先在其内部找目标变量,找不到则往父级作用域找,找不到再往父级的父级作用域找,直到找完,找完还没找到,那就报错处理。
  2. 隐式变量不存在变量声明提升。
    在这里插入图片描述
  3. 在声明函数存在形参时,形参会被隐式声明为局部变量。
    在这里插入图片描述
  4. 只要当前作用域存在变量声明,甭管它在当前作用域下方还是上方,是打印前还是打印后,它都会被变量提升至当前函数内顶部。
    在这里插入图片描述
  5. 作用域中的访问优先级:【变量提升 < 传参 < 函数体 < 首行赋值】,同样,当变量与函数名同名,亦遵循此优先级顺序。

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  6. 【函数的声明决定其作用域,而不是调用决定作用域】
  7. this 指向对象,而不是函数名。
  8. 在非严格模式中,无指向的函数内部的 this 指向 window【箭头函数除外】。
  9. 内部自执行匿名函数不属于任何对象,所以 this 指向 window。
    在这里插入图片描述
    在这里插入图片描述
  10. 当把函数引用赋值给一个变量时,造成 this 丢失。
    在这里插入图片描述
    在这里插入图片描述
  11. 最后一点,也是最重要的一点,仔细审题!不可粗心大意!个人练习做题大部分错的并不是不会,而是因粗心而导致给出错误答案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值