20190527-20190602周刊

1. 如何正确判断this的指向

如果用一句话说明this的指向,那么就是:谁调用它,this就指向谁。

按照以下顺序准确判断this的指向:

1. 全局环境中的this

  • 浏览器环境:无论是否在严格模式下,在全局执行环境中(在任何函数体外部)this都指向全局对象window;
  • node环境:无论是否在严格模式下,在全局执行环境中(在任何函体外部),this都是空对象{};

2. 是否是new绑定

  • 如果是new绑定,并且构造函数中没有返回function或者object,那么this指向这个新对象。

如下:

构造函数返回值不是functionobject

function Super(age) {
	this.age = age;
}
let instance = new Super('26');
console.log(instance.age);  //26
复制代码

构造函数返回值是functionobject,这种情况下this指向的是返回的对象

function Super(age) {
	this.age = age;
	let obj = { a: 2 };
	return obj;
}
let instance = new Super('26');
console.log(instance.age);  //undefined
复制代码

你可能想知道为什么会这样?我们看下new的实现原理:

  1. 创建一个新对象
  2. 这个新对象会被执行原型方法
  3. 属性和方法会被加入到this引用的对象中,并执行构造函数中的方法
  4. 如果函数没有返回其他对象,俺么this指向这个新对象,否则this指向构造函数中返回的对象
//new是关键字 这里仅是说明new的过程
function new(func){
	let target = {};
	target.__proto__ = func.prototype;
	let res = func.call(target);
	//排除null的情况
	if(res && typeof(res)=='object' || typeof(res)=='function'){
		return res;
	}
	return target;
}
复制代码

3. 函数是否通过call,apply调用,或者使用了bind绑定,如果是,那么this绑定的就是指定的对象【显示绑定】

function info() {
	console.log(this.age);
}
let person = {
	age: 20,
	info
}
let age = 28;
let myInfo = person.info;
myInfo.call(person);	//20
myInfo.apply(person);	//20
myInfo.bind(person)();	//20
复制代码

这里需要注意一种特殊情况,如果callapply,或者bind传入的第一个参数值是undefined或者null,严格模式下this的值为传入的值null/undefined。非严格模式下,实际应用的默认绑定规则,this指向全局对象(node环境为global,浏览器环境为window

function info() {
	//node环境中:非严格模式 global,严格模式为null
	//浏览器环境中:非严格模式window,严格模式为null
	console.log(this);
	console.log(this.age);
}
var person = {
	age: 20,
	info
}
var age = 28;   //注意如果使用let声明结果?
var myInfo = person.info;

//严格模式抛出错误
//非严格模式,node输出为undefined(因为全局的age不会挂在global上)
//非严格模式,浏览器环境输出28(因为全局的age会挂在window上)
myInfo.call(null);
复制代码

这里需要注意如果是用let声明,非严格模式喜爱浏览器输出undefined 现代 JavaScript 的变量作用域

4. 隐式绑定,函数的调用是在某个对象上触发的,即调用位置上存在上下文对象,典型的隐式调用为:xxx.fn()

function info() {
	console.log(this.age);
}
let person = {
	age: 20,
	info
}
let age = 28;
person.info();  //20,执行的是隐式绑定
复制代码

5. 默认绑定,在不能应用其他绑定规则时使用的默认规则,通尝试独立函数调用

非严格模式:node环境执行全局对象global,浏览器环境,执行全局对象window 严格模式:执行undefined

function info() {
	console.log(this.age);
}
var age = 28;
//严格模式 抛错
//非严格模式 node下输出undefined(因为全局的age不会挂在global上)
//非严格模式 浏览器环境下输出28(因为全局的age不会挂在window上)
//严格模式抛错,因为this此时是undefined
info(); //28
复制代码

注意如果age是通过let声明,输出是undefined,因为通过let声明的不会挂到window对象上

6. 箭头函数的情况

箭头函数没有自己的this,继承外层上下文绑定的this

let obj = {
	age: 20,
	info: function () {
		return () => {
			console.log(this.age);	//this继承的是外层上下文绑定的this
		}
	}
}
let person = { age: 28 };

let info = obj.info();
info();		//20

let info2 = obj.info.call(person);
info2();	//28
复制代码

2. JS中原始类型有哪几种?null是对象吗?原始数据类型和复杂数据类型有什么区别?

目前,JS原始类型有6种,分别为:

  • Boolean
  • String
  • Number
  • Undefined
  • Null
  • Symbol(ES6新增)

复杂数据类型只有1种:Object

null不是对象,尽管typeof null输出的是object,这是一个历史遗留问题,Js的最初版本中使用的是32位系统,为了性能考虑使用低位存储变量的类型信息,000开头代表是对象,null表示为全零,所以将它错误的判断为object

基本数据类型和复杂数据类型的区别:

1. 内存的分配不同
  • 基本数据类型存储在栈中
  • 复杂数据类型存储在堆中,栈中存储的变量,是指向堆中的引用地址
2. 访问机制不同
  • 基本数据类型是按值访问
  • 复杂数据类型按引用访问,Js不允许直接访问在堆内存中的对象,在访问一个对象时,首先得到的是这个对象在堆内存中的地址,然后再按照这个地址去获得这个对象中的值
3.复制变量时不同
  • 基本数据类型:a=b;是将b中保存的原始值的副本赋值给新变量a,ab完全独立,互补影响
  • 复杂数据类型:a=b;将b保存的对象内存的引用地址赋值给新变量a,ab指向了同一个堆内存地址,其中一个值发生了改变,另一个也会改变。
let b = {
	age: 10
}

let a = b;
a.age = 20;
console.log(b);	//{age:20}
复制代码
4. 参数传递的不同(实参/形参)

函数传参都是按值传递(栈中存储的内容):基本数据类型,拷贝的是值,复杂数据类型,拷贝的是引用地址

//基本数据类型
let b = 10;
function change(info) {
	info = 20;
}
//info=b;基本数据类型,拷贝的是值;复杂数据类型,拷贝的是引用地址
change(b);
console.log(b);	//10
复制代码
//复杂数据类型
let b = {
	age: 10
}
function change(info) {
	info.age = 20;
}
//info=b;拷贝的是地址的引用,修改互相影响
change(b)
console.log(b);	//{age:20}
复制代码

3. 对HTML5语义化的理解

HTML5语义化指的是合理正确的使用语义化的标签来创建页面结构,如header,footer,nav,从标签上即可以直观的知道这个标签的作用,而不是滥用div

语义化标签主要有:

  • title:主要用于页面的头部信息介绍
  • header:定义文档的页眉
  • nav:主要用于页面导航
  • main: 规定文档的主要内容,对于文档来说应该是唯一的,它不应包含在文档中重复出现的内容,比如侧栏、导航栏、版权信息、站点标志或搜索表单 参考
  • article:独立的自包含内容
  • h1~h6:定义标题
  • ul:定义无序列表
  • ol:定义有序列表
  • address:定义文档或文章的作者/拥有者的联系信息
  • canvas:绘制图像
  • dialog:定义一个对话框、确认框或者窗口
  • aside:定义其所处内容之外的内容。<aside>的内容可用作文章的侧栏
  • section:定义文档中的节(section、区段),比如章节、页眉、页脚或文档中的其他部分
  • figure:规定独立的流内容(图像、图表、照片、代码等等)。figure元素的内容应该与主内容相关,但如果被删除,则不应该对文档流产生影响
  • details:描述文档或者文档某一部分细节
  • mark:带有记号的文本

4. 如何让( a==1 && a==2 && a==3 )的值为true

1. 利用隐式转换规则

  • ==操作符在左右数据类型不一致时,会先进行隐式转换
  • a==1 && a==2 && a==3的值意味着不可能时基本数据类型,如果anull或者是undefined bool类型,都不可能返回true
  • 因此可以推测a是复杂数据类型,Js中复杂数据类型只有object,回忆下Object转换为原始类型会调用什么方法? 参考
let obj = {
	[Symbol.toPrimitive](hint) {
		console.log(hint);
		return 10;
	},
	valueOf() {
		console.log('valueOf');
		return 20;
	},
	toString() {
		console.log('toString');
		return 'hello';
	}
}

console.log(obj + 'tte'); //default
//如果没有部署[Symbol.toPrimitive]接口,调用顺序为valueOf -> toString
console.log(obj == 'tte'); //defalut
//如果没有部署[Symbol.toPrimitive]接口,调用顺序为valueOf -> toString
console.log(obj * 10); //number
//如果没有部署[Symbol.toPrimitive]接口,调用顺序为valueOf -> toString
console.log(Number(obj)); //number
//如果没有部署[Symbol.toPrimitive]接口,调用顺序为valueOf -> toString
console.log(String(obj)); //string
//如果没有部署[Symbol.toPrimitive]接口,调用顺序为toString -> valueOf
复制代码

那么对于这道题,只要[Symbol.toPrimitive]接口第一次返回的值是1,然后递增,即成立。

let a = {
	[Symbol.toPrimitive]: (function (hint) {
		let i = 1;
		return function () {
			return i++;
		}
	})()
}
console.log(a == 1 && a == 2 && a == 3);
复制代码

调用valueOf接口的情况:

let a = {
	valueOf: (function () {
		let i = 1;
		return function () {
			return i++;
		}
	})()
}
console.log(a == 1 && a == 2 && a == 3);
复制代码

调用toString接口与调用valueOf类似

另外,除了i自增的方法外,还可以利用正则,如下:

let a = {
	reg: /\d/g,
	valueOf() {
		return this.reg.exec(123)[0];
	}
}
console.log(a == 1 && a == 2 && a == 3);
复制代码

正则中的exec方法
简单说 正则表达式——要注意lastIndex属性
正则execmatch的区别

2. 利用数据劫持

使用Object.defineProperty定义的属性,在获取属性时,会调用get方法,利用这个特性我们在window对象上定义a属性,如下:

let i = 1;
Object.defineProperty(window, 'a', {
	get() {
		return i++;
	}
})
console.log(a == 1 && a == 2 && a == 3);
复制代码

ES6新增了Proxy,此处可以利用Proxy去实现,如下:

let a = new Proxy({}, {
	i: 1,
	get() {
		return () => this.i++
	}
})
console.log(a == 1 && a == 2 && a == 3);
复制代码

3. 数组的toString接口默认调用数组的join方法,重写数组的join方法

let a = [1, 2, 3];
a.join = a.shift;

console.log(a == 1 && a == 2 && a == 3);
复制代码

4. 利用with关键字

let i = 0;
with ({
	get a() {
		return ++i;
	}
}) {
	console.log(a == 1 && a == 2 && a == 3);
}
复制代码

5. 防抖(debounce)函数的左右是什么?有哪些应用场景,实现简单的防抖函数

防抖函数的作用:
控制函数在一定时间内的执行次数,防抖意味着N秒内函数只会被执行一次,如果N秒内再次被触发,则重新计算延迟时间。

防抖应用场景:

  1. 搜索框输入查询,如果用户一直在输入中,没有必要不停的调用去请求服务端接口,等用户停止输入的时候,再调用,设置一个合适的时间间隔,有效减轻服务端压力
  2. 表单验证
  3. 按钮提交事件
  4. 浏览器窗口缩放,resize事件等

防抖函数实现:

/**
 * 
 * @param {*} func 要执行的函数
 * @param {*} wait 延迟时间 毫秒
 * @param {*} immediate true 表示开始会立即触发一次  false 表示最后一次一定会触发
 */

function debounce(func, wait, immediate = true) {
	let timeout, context, args;

	//延迟执行函数
	const later = () => setTimeout(() => {
		//延迟函数执行完毕,清空定时器
		timeout = null;
		//延迟执行的情况下,函数会在延迟函数中执行
		//使用到之前缓存的参数和上下文
		if (!immediate) {
			func.apply(context, args);
			context = args = null;
		}
	}, wait);

	let debounced = (...params) => {
		if (!timeout) {
			timeout = later();
			if (immediate) {
				//立即执行
				func.apply(this, params);
			} else {
				//闭包
				context = this;
				args = params;
			}
		} else {
			//连续点击从头开始计算延迟时间
			clearTimeout(timeout);
			timeout = later();
		}
	}
	debounced.cancel = () => {
		clearTimeout(timeout);
		timeout = null;
	}
	return debounced;
}
复制代码

转载于:https://juejin.im/post/5cec95fb5188251e030fe48b

猜你喜欢

转载自blog.csdn.net/weixin_33913377/article/details/91440004