JavaScript 面试题(一)

前言: HTML 和 CSS 相关的面试题请阅读HTML + CSS 面试题

JS数据类型有关的之前已经写过了,需要的请阅读JS基本数据类型与引用数据类型
数据类型判断的四种方法

JS作用域和作用域链

作用域: 作用域决定了代码区块中变量和其他资源的可见性,不同作用域下同名变量不会有冲突。

全局作用域
  1. 在页面打开时被创建,页面关闭时被销毁
  2. 编写在 script 标签中的变量和函数,在页面任意位置都能访问
  3. 全局作用域中有全局对象 window,代表一个浏览器窗口,由浏览器创建,可以直接调用
  4. 全局作用域中声明的变量和函数会作为window的对象属性和方法保存
函数作用域
  1. js 采用的是静态作用域,所以函数的作用域在函数定义时就确定了
  2. 相互独立,每调用一次创建一个函数作用域
  3. 函数作用域内可以访问到全局作用域中的变量,函数外无法访问函数内部的局部变量
块级作用域
  1. 块级作用域可通过新增命令letconst声明,所声明的变量在指定块的作用域外无法被访问。块级作用域在如下情况被创建:
    • 在一个函数内部
    • 在一个代码块(由一对花括号包裹)内部
  2. 声明变量不会提升到代码块顶部
  3. 禁止重复声明

在自身作用域中寻找某个变量,若找不到,会到当前作用域的上一级作用域内查找,直到全局作用域,这种层级关系就叫作用域链。

在ES6之前,JavaScript没有块级作用域,只有函数作用域和全局作用域,ES6之后可以通过 let 和 const 体现。

前往 var、let 、const 阅读

推荐文章:JavaScript深入之词法作用域和动态作用域深入理解JavaScript作用域和作用域链

原型和原型链

原型和原型链,需要理解了下面这张图和代码,并且能默画出这张图。

function Foo() {}

let f1 = new Foo();
let f2 = new Foo();

在这里插入图片描述
推荐文章:JavaScript深入之从原型到原型链轻松理解JS 原型原型链

闭包

闭包: 定义在函数内部的函数,并且使用了外部函数内的局部变量

闭包产生的条件

  1. 函数嵌套
  2. 内部函数引用了外部函数内的局部变量

闭包的作用:

  1. 延长变量的生命周期
  2. 创建私有环境

闭包的优缺点:

  • 优点
    1. 执行空间不销毁,变量也没有销毁
    2. 可以读取函数内部的变量
    3. 让这些变量的值始终保存在内存
  • 缺点
    1. 变量一直存储在内存中,内存消耗大,容易造成内存泄露

在某个内部函数的执行上下文创建时,会将父级函数的活动对象加到内部函数的 [[scope]] 中,形成作用域链,所以即使父级函数的执行上下文销毁(即执行上下文栈弹出父级函数的执行上下文),但是因为其活动对象还是实际存储在内存中可被内部函数访问到的,从而实现了闭包。

推荐文章:JavaScript深入之闭包

call、apply、bind 的实现
call

简单来说,call 调用函数方法就是用来改变函数内 this 的指向
举个例子:

function person(){
	console.log(this.name)
}

const obj = {
	name: "aaa",
}

person.call(obj) //  打印的 name 的值是 aaa 这里的 this 指向 obj 对象
person() // undefined 这里的 this 由 window 调用,指向 window 

call 的实现

Function.prototype.newCall = function (obj){
	var obj = obj || window; // 这是为了防止传入的第一个参数为 null 时输出报错
	// 原本this指向person函数,给obj添加一个p属性,赋值为this,person函数在obj内被调用,this指向obj,从而改变person函数内this的指向
	obj.p = this;
	// 调用this方法
	obj.p();
	// 保存传入的其他参数
	var newArguments = [];
	for (var i = 1; i < arguments.length; i++){
		newArguments.push('arguments[' + i + ']');
	}
	var result = eval('obj.p(' + newArguments + ')');
	// 原本obj内没有p这个属性,调用之后删除p这个属性
	delete obj.p;
	return result;
}
apply

apply 与 call 类似,最大的区别就是 apply 接收的第二个参数为数组,而 call 从第二个参数开始接收单个参数

person.call(obj,1,2,3)
person.apply(obj,[1,2,3])

因此 apply 的实现如下:

Function.prototype.newApply = function (obj,arr){
	var obj = obj || window; // 这是为了防止传入的第一个参数为 null 时输出报错
	var result;
	obj.p = this;
	// 如果数组为空,直接返回
	if( !arr ){
		result = obj.p();
	}else{
		// 若数组不为空,则与 call 实现方法类似
		var newArguments = [];
		for (var i = 0; i < arguments.length; i++){
			newArguments.push('arr[' + i + ']');
		}
		result = eval('obj.p(' + newArguments + ')');
	}
	delete obj.p;
	return result;
}
bind

bind 与 call、apply 的区别就是 bind 会返回一个函数,并且不会立即执行。

fn.call(obj,1) // 会立即执行并返回值
fn.bind(obj,1) // 绑定了this的指向但不会立即执行,返回的是一个函数,需要再次调用这个函数才会执行

bind 的实现

Function.prototype.newBind = function (obj,arr){
	// 使用闭包保存this,此时this指向person
	var that = this;
	var arr = Array.prototype.slice.call(arguments,1);
	return function(){
		var arr2 = Array.prototype.slice.call(arguments);
		// 合并两个函数接收的参数
		var arrsum = arr.concat(arr2)
		that.call(obj,arrsum);
	}
}

同时,bind 可以实现 new 操作,但此时的 this 会失效,我们用原生的 bind 尝试一下:

function person(a,b,c){
	console.log(this.name)
	console.log(a,b,c)
}
const obj = {
	name: "aaa",
}

var P = person.bind(obj,1,2);
var p = new P(3); // 此时打印出来的值为 undefined 1 2 3 可以看到 this 失效了

再实现 bind 的 new 操作,此时 bind 的实现变成了这样:

function person(a,b,c){
	console.log(this.name)
	console.log(a,b,c)
}
// 给person的原型添加一个属性
person.prototype.s = "sss"
const obj = {
	name: "aaa",
}
Function.prototype.newBind = function (obj,arr){
	// 使用闭包保存this,此时this指向person
	var that = this;
	var arr = Array.prototype.slice.call(arguments,1);
	return function(){
		var arr2 = Array.prototype.slice.call(arguments);
		// 合并两个函数接收的参数
		var arrsum = arr.concat(arr2)
		that.call(obj,arrsum);
	}
}
// 再使用刚刚写的 newBind 尝试调用原型对象中的 s 属性
var P = person.newBind(obj,1,2);
var p = new P(3); // aaa 1 2 3 新的 newBind 方法没有实现 new 的操作,this 还是指向 obj
console.log(p.s) // undefined 发现此时实例对象与原型对象没有关系

// 重新实现 new 效果以及原型对象的串联
Function.prototype.newBind = function (obj,arr){
	// 使用闭包保存this,此时this指向person
	var that = this,
	 	arr = Array.prototype.slice.call(arguments,1),
		o = function(){},
		newf = function(){
		// console.log( this instanceof newf) // true 发现此时的this是newf的实例,也就是说this是指向new 的实例 p 
		var arr2 = Array.prototype.slice.call(arguments),
		// 合并两个函数接收的参数
			arrsum = arr.concat(arr2)
		// 判断有没有进行new操作,如果有,则把this绑定到new的实例上,否则绑定到obj
		if(this instanceof newf){
			that.call(this,arrsum)
		}else{
			that.call(obj,arrsum);
		}
	}
	// 利用一个空函数把原型对象串联起来
	o.prototype = that.prototype
	newf.prototype = new o;
	return newf;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值