JavaScript面试题继续

1、对于JSON的了解

相关知识点:

JSON是一种数据交换合适,基于文本,优于轻量,用于交换数据。
JSON可以表示数字、布尔值、字符串、null、数组(值得有序序列),以及有这些值(或数组、对象)所组成的对象(字符串与值得映射)。
JSON使用JavaScript语法,但是JSON格式仅仅是一个文本。文本可以被任何编程语言读取及作为数据格式传递。

回答:

JSON是一种基于文本的轻量级的数据交换格式。他可以被任何的编程语言读取和作为数据格式来传递。

在项开发中,我们使用JSON作为前后端数据交换的方式。在前端我们通过将一个符合JSON格式的数据结构序列化为JSON字符串,然后将它传递到后端,后端通过JSON格式的字符串解析后生成对应的数据结构,以此来实现前后端数据的一个传递。

因为JSON的语法是基于js的,因此很容易将JSON和js中的对象弄混,但是我们应该注意的是JSON和js的对象不是一回事,JSON中对象格式更加严格,比如说在JSON中属性值不能为函数,不能出现NaN这样的属性值等,因此大多数的js对象是不符合JSON对象的格式的。

在js中提供了两个函数来实现js数据结构和JSON合适的转换处理,一个是JSON.striongify函数,通过传入一个符合JSON格式的数据结构,将其转换为一个JSON字符串。如果传入的数据结构不符合JSON格式,那么在序列化的时候会对这些值进行对应的特殊处理,使其符合规范。在前端向后端发送数据时,我们可以调用这个函数将数据对象转化为JSON格式的字符串。

另一个函数JSON.parse()函数,这个函数用来将JSON格式的字符串转换为一个js数据结构,如果传入的字符串不是标准的JSON格式的字符串的话,将会抛出错误。当我们从后端结束到JSON格式字符串时,我们可以通过这个方法将其解析为一个js数据结构,因此来进行数据的访问。

2、js延迟加载的方式有哪些?

相关知识点

js延迟加载,也就是等页面加载完成之后在加载JavaScript文件。js延迟加载有助于提高页面加载速度。
一般有以下几种方式:

  • defer属性
  • async属性
  • 动态创建DOM方式
  • 使用setTimeout延迟方法
  • 让js最后加载

回答:

js的加载、解析和执行会阻塞页面的渲染过程,因此我们希望js脚本能够尽可能的延迟加载,提高也免得渲染速度。
我了解的几种方式是:

第一种方式使我们一般采用的试讲js脚本放在文档的底部,老师js脚本尽可能的在最后来加载执行。

第二种方式是给js脚本添加defer属性,这个属性会让脚本的加载与文档的解析同步解析,然后在文档解析完成后在执行这各脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了defer属性的脚本按规范来说最后是顺序执行的,但是在一些浏览器中可能不是这样。

第三种方式是给js脚本添加按async属性,这各属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行js脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个async属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行。

第四种方式是动态创建dom标签的方式,我们可以对文档的加载事件进行监听,当文档加载完成后再动态的创建script标签来引入js脚本。

3、DOM 操作——怎样添加、移除、移动、复制、创建和查找节点?

(1)创建新节点

createDocumentFragment(mode);
createElement(node);
createTextNde(text);

(2)添加、移除、替换、插入

appendChild(node)
removeChild(node)
replaceChild(new,old)
insertBefore(new,old)

(3)查找

getElementById();
getElementsByName();
getElementsByTagName();
getElementsByClassName();
querySelector();
querySelectorAll();

(4)属性操作

getAttribute(key);
setAttribute(key, value);
hasAttribute(key);
removeAttribute(key);

4、 innerHTML 与 outerHTML 的区别?

//对于这样一个html元素:
<div>content<br/></div>
content<br/>//innerHTML:内部html
<div>content<br/></div>//outerHTML:外部HTMl
//innerHTML:内部文本,content
//outerText:内部文本,content

5、.call()和.apply()的区别

它们的作用一模一样,区别在于传入参数的形式的不同
apply接受两个参数,第一个参数指定了函数体内this对象的指向,第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组,apply方法把这个集合中的元素作为参数传递给被调用的函数

call传入的参数数量不固定,跟apply相同的是,第一个参数也是代表函数体内的this指向,从第二个参数开始往后,每个参数被依次传入函数。

6、JavaScript类数组对象的定义

一个拥有 length 属性和若干索引属性的对象就可以被称为类数组对象,类数组对象和数组类似,但是不能调用数组的方法。
常见的类数组对象有 arguments 和 DOM 方法的返回结果,还有一个函数也可以被看作是类数组对象,因为它含有 length属性值,代表可接收的参数个数。
常见的类数组转换为数组的方法有这样几种
1 通过call调用数组的slice方法来实现转换

Array.prototype.slice.call(arrayLike);

2 通过call调用数组的splice方法来实现转换

Array.prototype.splice.call(arrayLike, 0);

3 通过apply调用数组的concat方法实现转化

Array.prototype.concat.apply([], arrayLike);

4 通过 Array.from 方法来实现转换

Array.from(arrayLike);

7 数组和对象有哪些原生方法,列举一下?

数组和字符串的转换方法:toString()、toLocalString()、join() 其中 join() 方法可以指定转换为字符串时的分隔符。

数组尾部操作的方法 pop() 和 push(),push 方法可以传入多个参数。

数组首部操作的方法 shift() 和 unshift() 重排序的方法 reverse() 和 sort(),sort() 方法可以传入一个函数来进行比较,传入前后两个值,如果返回值为正数,则交换两个参数的位置。

数组连接的方法 concat() ,返回的是拼接好的数组,不影响原数组。

数组截取办法 slice(),用于截取数组中的一部分返回,不影响原数组。

数组插入方法 splice(),影响原数组查找特定项的索引的方法,indexOf() 和 lastIndexOf() 迭代方法 every()、some()、filter()、map() 和 forEach() 方法

数组归并方法 reduce() 和 reduceRight() 方法

8、如何判断当前脚本运行在浏览器还是 node 环境中?

this === window ? 'browser' : 'node';
通过判断 Global 对象是否为 window,如果不为 window,当前脚本没有运行在浏览器中。

9、使用 JS 实现获取文件扩展名?

function getName(fname){  
	return fname.slice(((fname.lastIndexOf(".") - 1) >>> 0) + 2);
}
console.log(getName(tree.jpg));//jpg

10、js的防抖和节流

// 函数防抖的实现
function debounce(fn, wait){  
	var timer = null;
  	return function(){    
  	var context = this,      
  	args = arguments;
    	// 如果此时存在定时器的话,则取消之前的定时器重新记时    
    	if(timer) {      
    		clearTimeout(timer);      
    		timer = null;    
    	}
    	// 设置定时器,使事件间隔指定事件后执行    
   	 timer = setTimeout(() => {      
   	 fn.apply(context, args);    
    	}, wait);  
    };
}
// 函数节流的实现;
function throttle(fn, delay) {  
	var preTime = Date.now();
  	return function() {    
  		var context = this,      
  		args = arguments,      
  		nowTime = Date.now();
  	  	// 如果两次时间间隔超过了指定时间,则执行函数。    
    		if (nowTime - preTime >= delay) {      
    			preTime = Date.now();      
    			return fn.apply(context, args);    
    		}  
    	};
   }

11、Unicode 和 UTF-8 之间的关系?

Unicode 是一种字符集合,现在可容纳 100 多万个字符。每个字符对应一个不同的 Unicode 编码,它只规定了符号的二进制代码,却没有规定这个二进制代码在计算机中如何编码传输。

UTF-8 是一种对 Unicode 的编码方式,它是一种变长的编码方式,可以用 1~4 个字节来表示一个字符。

12、 toPrecision 和 toFixed 和 Math.round 的区别?

toPrecision 用于处理精度,精度是从左至右第一个不为 0 的数开始数起。
toFixed 是对小数点后指定位数取整,从小数点开始数起。
Math.round 是将一个数字四舍五入到一个整数。

13、 vue 双向数据绑定原理?

vue 通过使用双向数据绑定,来实现了 View 和 Model 的同步更新。vue 的双向数据绑定主要是通过使用数据劫持和发布订阅者模式来实现的。
首先我们通过 Object.defineProperty() 方法来对 Model 数据各个属性添加访问器属性,以此来实现数据的劫持,因此当 Model 中的数据发生变化的时候,我们可以通过配置的 setter 和 getter 方法来实现对 View 层数据更新的通知。
数据在 html 模板中一共有两种绑定情况,一种是使用 v-model 来对 value 值进行绑定,一种是作为文本绑定,在对模板引擎进行解析的过程中。
如果遇到元素节点,并且属性值包含 v-model 的话,我们就从 Model 中去获取 v-model 所对应的属性的值,并赋值给元素的 value 值。然后给这个元素设置一个监听事件,当 View 中元素的数据发生变化的时候触发该事件,通知 Model 中的对应的属性的值进行更新。
如果遇到了绑定的文本节点,我们使用 Model 中对应的属性的值来替换这个文本。对于文本节点的更新,我们使用了发布订阅者模式,属性作为一个主题,我们为这个节点设置一个订阅者对象,将这个订阅者对象加入这个属性主题的订阅者列表中。当 Model 层数据发生改变的时候,Model 作为发布者向主题发出通知,主题收到通知再向它的所有订阅者推送,订阅者收到通知后更改自己的数据。

14、谈谈你对 webpack 的看法

我当时使用 webpack 的一个最主要原因是为了简化页面依赖的管理,并且通过将其打包为一个文件来降低页面加载时请求的资源数。
我认为 webpack 的主要原理是,它将所有的资源都看成是一个模块,并且把页面逻辑当作一个整体,通过一个给定的入口文件,webpack 从这个文件开始,找到所有的依赖文件,将各个依赖文件模块通过 loader 和 plugins 处理后,然后打包在一起,最后输出一个浏览器可识别的 JS 文件。
Webpack 具有四个核心的概念,分别是 Entry(入口)、Output(输出)、loader 和 Plugins(插件)。
Entry 是 webpack 的入口起点,它指示 webpack 应该从哪个模块开始着手,来作为其构建内部依赖图的开始。
Output 属性告诉 webpack 在哪里输出它所创建的打包文件,也可指定打包文件的名称,默认位置为 ./dist。
loader 可以理解为 webpack 的编译器,它使得 webpack 可以处理一些非 JavaScript 文件。在对 loader 进行配置的时候,test 属性,标志有哪些后缀的文件应该被处理,是一个正则表达式。use 属性,指定 test 类型的文件应该使用哪个 loader 进行预处理。常用的 loader 有 css-loader、style-loader 等。
插件可以用于执行范围更广的任务,包括打包、优化、压缩、搭建服务器等等,要使用一个插件,一般是先使用 npm 包管理器进行安装,然后在配置文件中引入,最后将其实例化后传递给 plugins 数组属性。
使用 webpack 的确能够提供我们对于项目的管理,但是它的缺点就是调试和配置起来太麻烦了。但现在 webpack4.0 的免配置一定程度上解决了这个问题。但是我感觉就是对我来说,就是一个黑盒,很多时候出现了问题,没有办法很好的定位。

15、 函数柯里化的实现

// 函数柯里化指的是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。
	function curry(fn, args) {  
	// 获取函数需要的参数长度  
	let length = fn.length;
  	args = args || [];
  	return function() {   
  		 let subArgs = args.slice(0);
    		// 拼接得到现有的所有参数    				
    		for (let i = 0; i < arguments.length; i++){    
 			 subArgs.push(arguments[i]);    
 		}
   		// 判断参数的长度是否已经满足函数所需参数的长度    
   		if (subArgs.length >= length) {      
   			// 如果满足,执行函数      
   			return fn.apply(this, subArgs);    
   		} else {      
   			// 如果不满足,递归返回科里化的函数,等待参数的传入      
   			return curry.call(this, fn, subArgs);    
   		}  
   	};
   }

// es6 实现
	function curry(fn, ...args){  
		return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);		
	}

16、Object.defineProperty 介绍?

Object.defineProperty 函数一共有三个参数,第一个参数是需要定义属性的对象,第二个参数是需要定义的属性,第三个是该属性描述符。
一个属性的描述符有四个属性,分别是 value 属性的值,writable 属性是否可写,enumerable 属性是否可枚举,configurable 属性是否可配置修改。

17、 URL 和 URI 的区别?

URI: Uniform Resource Identifier 指的是统一资源标识符
URL: Uniform Resource Location 指的是统一资源定位符
URN: Universal Resource Name 指的是统一资源名称
URI 指的是统一资源标识符,用唯一的标识来确定一个资源,它是一种抽象的定义,也就是说,不管使用什么方法来定义,只要能唯一的标识一个资源,就可以称为 URI。
URL 指的是统一资源定位符,URN 指的是统一资源名称。URL 和 URN 是 URI 的子集,URL 可以理解为使用地址来标识资源,URN 可以理解为使用名称来标识资源。

18、为什么使用 setTimeout 实现 setInterval?怎么模拟?

//思路是使用递归函数,不断地去执行setTimeout从而达到setInterval的效果
function mySetInterval(fn,timout){ 
	var timer = {
		flag:true
	}//设置递归函数,模拟定时器执行
	function interval(){
		if(timer.flag){
			fn();
			setTimeout(interval, timeout);
		}
	}
	//启动定时器
	setTimeout(interval, timeout);
	//返回控制器
	 return timer;
}

19、let 和 const 的注意点?

  • 声明的变量只在声明时的代码块内有效
  • 不存在声明提升
  • 存在暂时性死区,如果在变量声明前使用,会报错
  • 不允许重复声明,重复声明会报错

20, 什么是 Proxy ?

Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”,即对编程语言进行编程。

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

发布了16 篇原创文章 · 获赞 9 · 访问量 275

猜你喜欢

转载自blog.csdn.net/webblock/article/details/105338427