J40 this指向练习

271 阅读8分钟

 1.全局和对象中的this

var num = 10;
var obj = {
	num: 20
};
obj.fn = (function (num) {
	this.num = num * 3;
	num++;
	return function (n) {
		this.num += n;
		num++;
		console.log(num);
	}
})(obj.num);
var fn = obj.fn;
fn(5);
obj.fn(10);
console.log(num, obj.num);

2.this指向计算

(function () {
    var val = 1; 
    var json = {
    	val: 10, 
    	dbl: function () {
    		val *= 2; 
    	}
    };
json.dbl();
alert(json.val + val); //=>10+2 =>"12"
})(); 

3.this计算题

window.val = 1;
var json = {
	val: 10,
	dbl: function () {
		this.val *= 2;
	}
}
json.dbl();
var dbl = json.dbl;
dbl();
json.dbl.call(window);
alert(window.val + json.val);

4.this计算题

var name = 'window';
var Tom = {
	name: "Tom",
	show: function () {
		console.log(this.name);
	},
	wait: function () {
		// this:Tom
		// fun = this.show = Tom.show
		var fun = this.show;
		fun(); //=>fun中的this:window
	}
};
Tom.wait(); //=>window

5.this计算题

 var fullName = 'language';
var obj = {
	fullName: 'javascript',
	prop: {
		getFullName: function () {
			return this.fullName;
		}
	}
};
console.log(obj.prop.getFullName());
//=>this:obj.prop  //=>obj.prop.fullName //=>undefined

var test = obj.prop.getFullName;
console.log(test());
//=>this:window  //=>window.fullName  //=>"language" 

6.this计算题

let obj = {
	// fn等于自执行函数执行的返回值(返回的小函数)
	// fn : function () {console.log(this);}
	fn: (function () {
		return function () {
			console.log(this);
		}
	})()
};
obj.fn(); //=>this:obj
let fn = obj.fn;
fn(); //=>this:window 

7.题目

题目:以下代码的功能是要实现为5个input按钮循环绑定click点击事件,绑定完成后点击1、2、3、4、5五个按钮分别会alert输出0、1、2、3、4五个字符

  • 1.请问如下代码是否能实现?

  • 2.如果不能实现那么现在的效果是什么样的?

  • 3.应该做怎样的修改才能达到我们想要的效果,并说明原理?

1.请问如下代码是否能实现?

不能实现,目前点击任意一个按钮,最后输出的结果都是5(循环最后的结果)

2.如果不能实现那么现在的效果是什么样的?
  • 1.循环元素集合中的每一项,给每一项(按钮元素对象)的CLICK事件行为绑定方法。此时方法并不会执行,只有循环结束(全局变量i=5),触发点击事件行为的时候,才会把对应绑定的方法执行; =>突出函数是在循环之后才会执行的(异步编程)
  • 2.2.当事件绑定的方法执行,会形成新的私有上下文EC(AN),上下文中的代码执行,遇到的变量i并不是当前上下文EC(AN)中的私有变量,此时会基于作用域链,向上级上下文中进行查找,也就是找到的是全局上下文EC(G)中的全局变量i,但是已经知道此时的i变为了循环最后一次的结果5 =>突出私有上下文/全局上下文和作用域链的机制
3.应该做怎样的修改才能达到我们想要的效果,并说明原理?

解决办法1: 在循环绑定的时候,把后续方法执行中所需要用到的当前按钮的索引,以自定义属性的方式存储给当前的元素对象;这样后期方法执行,再需要用到它的索引,直接从自定义属性中获取即可;

for (var i = 0; i < inputs.length; i++) {
// 把每一个按钮的索引赋值给它的index自定义属性
inputs[i].index = i;
inputs[i].onclick = function () {
// this:当前操作的元素,我们从其自定义属性上获取索引值即可
alert(this.index);
	}
} 

解决方案2:之前不能实现的主要原因在于方法执行的时候,遇到的变量i不是自己私有的,是上级作用域(全局)中的,全局的i已经是5了;此时我们可以在每一次循环的时候,形成一个私有的上下文(闭包),把需要的索引存储起来(存储为每一个闭包中的私有变量),点击再次执行对应函数的时候,让遇到的变量i向上级上下文(也就是之前每一次循环形成的私有上下文)中查找,不找全局的即可 =>但是这种办法不好,因为循环多少次,就会产生多少个不销毁的私有上下文,比较浪费性能

for (var i = 0; i < inputs.length; i++) {
	~ function (i) {
// 每一次循环都形成一个闭包(私有上下文),
//我们需要在当前上下文中,把后续需要用到的索引保存起来
		inputs[i].onclick = function () {
			alert(i);
		}
	}(i);
}

for (var i = 0; i < inputs.length; i++) {
	inputs[i].onclick = (function (i) {
		return function () {
			alert(i);
		}
	})(i);
} 

解决方案三: 在ES6新语法规范中,基于LET/CONST创建变量,会把当前所在的代码块(就是大括号,例如:循环体和判断体)变为私有的上下文(块级作用域),我们利用这个机制,也可以轻松的把每一次循环的索引保存起来;原理和闭包类似,只不过不需要我们自己执行自执行函数形成闭包存储i了;

for (let i = 0; i < inputs.length; i++) {
// 每一轮循环都会形成一个全新的私有的块级作用域,
//并且都有一个私有变量i,分别存储每一轮循环的值(索引)
	inputs[i].onclick = function () {
		alert(i);
	}
}

综述:三种解决方案实现的原理不一样,自定义属性方案是性能最好的,推荐更多的使用这种方案;ES6这种方案虽然性能有所消耗,但是实现起来最方便;闭包的解决方案不推荐大家使用,复杂而且非常消耗性能

8.你理解的闭包作用是什么,优缺点?以及项目中的应用?

闭包:函数执行形成的私有上下文,即能保护里面的私有变量不受外界干扰,也能在当前上下文中保存一些信息(前提:形成的上下文不销毁),上下文中的这种保存和保护机制,就是闭包机制

  • 1.真实项目中,团队协作开发(或者封装公共的组件和类库),为了避免全局变量的污染,我们每个人都会把自己的代码放到闭包中,保护起来
  • 2.再某些需求中,我们经常需要形成一个闭包,存储一些值(而且不能销毁),这样来供后面的程序运行使用(例如:惰性函数、柯理化函数、compose函数等JS高阶编程技巧中,就是基于闭包的保存机制实现的)
  • 3.因为闭包会产生不销毁的上下文,这样导致栈/堆内存消耗过大,有时候也会导致内存泄漏等,影响页面的运行性能,所以在真实项目中,要合理应用闭包(不要滥用)

XXX组长:写公共方法

  • 4.utils对象中包含了需要供别人调取使用的方法;此时我们把utils称之为“命名空间”,而对象就是一个空间,空间中包含了当前版块中的内容,或者是把当前版块中的内容按照命名空间进行了分组,每一个分组都是一个单独的个体

  • 5.下面这种设计代码的思想:单例设计模式 (避免了全局变量的污染,也同时实现了不同闭包之间方法的公用性)

    let utils = (function () { function queryElement() { // ... }

    function setStyle() { // ... }

    return { queryElement: queryElement, setStyle: setStyle } })();

    // XXX同学写的代码:天气版块的处理 let wetherModule = (function () { let time = "2020/03/23";

    // 调取其它版块下的方法 utils.queryElement();

    function queryData() { // ... }

    return { queryData: queryData }; })();

    // XXX同学些的代码:新闻页卡版块处理 let newsModule = (function () { let time = "2020/03/22";

    function queryData() { // ... }

    return { queryData: queryData }; })();

9.let和var的区别

  • 1.LET不存在变量提升
  • 2.LET不允许重复声明
  • 3.LET在全局下声明的变量只是全局变量,和全局对象GO没有任何的关系;而VAR在全局下声明的全局变量,也相当于给全局对象GO设置了对应的属性,而且存在映射关系;
  • 4.LET解决了 typeof 检测没有声明过的变量 的时候,结果是undefined而不是报错的 这种暂时性死区问题
  • 5.LET会产生块级私有作用域

10.计算题

console.log(1);
setTimeout(function () {
// setTimeout设置一个定时器,让其1000MS后在执行这个方法
console.log(2);
}, 1000);
console.log(3);	// => 1 3  1秒后输出2 

11.改造下面代码,使之输出0-9

//题目:改造下面代码,使之输出0-9
for (var i = 0; i < 10; i++) {
setTimeout(() => {
	console.log(i);
}, 1000);
}

//此时代码输出的是10个10的分析步骤
for (var i = 0; i < 10; i++) {
// 每一轮循环,都会设置一个定时器,规定1秒后做啥事情;
//时我们不会等,继续执行下一轮循环...  //0轮循环都结束了(全局变量i===10),
//等到已经到1000MS了,才会把对应的方法执行
	setTimeout(() => {
// 方法执行的时候,循环已经结束了
		console.log(i); //=>全局下已经变为循环结束后的10了,所以输出10个10
	}, 1000);
} 

1.方法一:输出使之输出0-9

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

2.方法二:输出使之输出0-9

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

12 下面代码输出的结果是多少,为什么?如何改造一下,就能让其输出 20 10?

var b = 10;
(function b() {
    b = 20;
    console.log(b);
})();
console.log(b);

按照常理,一般自执行函数都是匿名函数;但是我们可以给其设置为实名函数(具名函数),只不过及时设置了函数名,和平时普通的具名函数还是有区别的

  • 1.匿名函数具名化之后,设置的函数名,只能在函数内部的上下文中使用(和外界上下文中的变量没有关系),相当于当前私有上下文中的一个私有变量

    (function func() { console.log(func); //=>当前函数 })(); console.log(func); //=>Uncaught ReferenceError: func is not defined

  • 2.这个设置的函数名,不仅只能在私有上下文中用,而且还不能修改其值

    "use strict"; (function func() { console.log(func); //=>函数 func = 100; //=>非严格模式下修改这个操作不会起到作用,而且在严格模式下会报错 //Uncaught TypeError: Assignment to constant variable. console.log(func); //=>函数 })();

  • 3.我们可以基于重新声明这个变量的方法,修改它的值(相当于我们自己声明的优先级会很高,告诉浏览器,以我们自己声明的为主)

    (function func() { // 变量提升阶段:var func; console.log(func); //=>undefined var func = 100; console.log(func); //=>100 })();

    (function func() { // func设置的函数名字,类似于私有变量,但是本质上不是私有变量 let func = 100; console.log(func); //=>100 })();

    (function func() { // 变量提升阶段:声明+定义过func函数, //此时就把之前给匿名函数设置的函数名给替换了 console.log(func); //=>OK函数 function func() { console.log('OK'); } console.log(func); //=>OK函数 })();

    var b = 10; (function b() { //=>具名化的名字和全局上下文中的变量没有关系 b = 20; console.log(b); //=>匿名函数(修改值没用) })(); console.log(b); //=>10

  • 4.修改方法

    var b = 10; (function b() { var b = 20; console.log(b); //=>20 })(); console.log(b); //=>10