[前端基础] JavaScript 基础篇(下)

DOM 和 BOM

DOM 指的是文档对象模型,它指的是把文档当做一个对象来对待,这个对象主要定义了处理网页内容的方法和接口。BOM 指的是浏览器对象模型,它指的是把浏览器当做一个对象来对待,这个对象主要定义了与浏览器进行交互的法和接口。BOM 的核心是 window,而 window 对象具有双重角色,它既是通过 js 访问浏览器窗口的一个接口,又是一个 Global(全局)对象。这意味着在网页中定义的任何对象,变量和函数,都作为全局对象的一个属性或者方法存在。window 对象含有 location 对象、navigator 对象、screen 对象等子对象,并且 DOM 的
最根本的对象 document 对象也是 BOM 的 window 对象的子对象。

垃圾回收机制

必要性:由于字符串、对象和数组没有固定大小,所有当他们的大小已知时,才能对他们进行动态的存储分配。JavaScript 程序每次创建字符串、数组或对象时,解释器都必须分配内存来存储那个实体。只要像这样动态地分配了内存,最终都要释放这些内存以便他们能够被再用,否则,JavaScript 的解释器将会消耗完系统中所有可用的内存,造成系统崩溃。

JavaScript 的解释器可以检测到何时程序不再使用一个对象了,当他确定了一个对象是无用的时候,他就知道不再需要这个对象,可以把它所占用的内存释放掉了。例如:

var a="hello world";
var b="world";
var a=b; // 这时,会释放掉"hello world",

释放内存以便再引用垃圾回收的方法:标记清除、计数引用。

  • 标记清除,这是最常见的垃圾回收方式,当变量进入环境时,就标记这个变量为”进入环境“,从逻辑上讲,永远不能释放进入环境的变量所占的内存,只要执行流程进入相应的环境,就可能用到他们。当离开环境时,就标记为离开环境。垃圾回收器在运行的时候会给存储在内存中的变量都加上标记(所有都加),然后去掉环境变量中的变量,以及被环境变量中的变量所引用的变量(条件性去除标记),删除所有被标记的变量,删除的变量无法在环境变量中被访问所以会被删除,最后垃圾回收器,完成了内存的清除工作,并回收他们所占用的内存。
  • 引用计数法,另一种不太常见的方法就是引用计数法,引用计数法的意思就是每个值没引用的次数,当声明了一个变量,并用一个引用类型的值赋值给改变量,则这个值的引用次数为 1;相反的,如果包含了对这个值引用的变量又取得了另外一个值,则原先的引用值引用次数就减 1,当这个值的引用次数为 0 的时候,说明没有办法再访问这个值了,因此就把所占的内存给回收进来,这样垃圾收集器再次运行的时候,就会释放引用次数为 0 的这些值。用引用计数法会存在内存泄露
function problem() {
	var objA = new Object();
	var objB = new Object();
	objA.someOtherObject = objB;
	objB.anotherObject = objA;
}

在这个例子里面,objA 和 objB 通过各自的属性相互引用,这样的话,两个对象的引用次数都为 2,在采用引用计数的策略中,由于函数执行之后,这两个对象都离开了作用域,函数执行完成之后,因为计数不为 0,这样的相互引用如果大量存在就会导致内存泄露。特别是在 DOM 对象中,也容易存在这种问题:

var element=document.getElementById('')var myObj=new Object();
myObj.element=element;
element.someObject=myObj;
//这样就不会有垃圾回收的过程。

前端模块化

前端模块化就是复杂的文件编程一个一个独立的模块,比如 JS 文件等等,分成独立的模块有利于重用(复用性)和维护(版本迭代),这样会引来模块之间相互依赖的问题,所以有了 CommonJS 规范,AMD,CMD 规范等等,以及用于 JS 打包(编译等处理)的工具 webpack、gulp等。

  • CommonJS:开始于服务器端的模块化,同步定义的模块化,每个模块都是一个单独的作用域,模块输出,modules.exports,模块加载 require()引入模块。
  • AMD:异步模块定义。requireJS 实现了 AMD 规范,主要用于解决下述两个问题:
    1. 多个文件有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器
    2. 加载的时候浏览器会停止页面渲染,加载文件越多,页面失去响应的时间越长。
      语法:requireJS 定义了一个函数 define,它是全局变量,用来定义模块。
//定义模块
define(['dependency'], function(){
	var name = 'Byron';
	function printName(){
	console.log(name);
	}
	return {
		printName: printName
	};
});
//加载模块
require(['myModule'], function (my){
	my.printName();
}

ES6 提出新方案,使用 importexport

关于 AMD 和 CMD

  • AMD 推崇依赖前置,在定义模块的时候就要声明其依赖的模块。而 CMD 推崇 就近依赖,只有在用到某个模块的时候再去 require
  • AMD 和 CMD 对于模块的加载方式都是异步加载,不过它们的区别在于 模块的执行时机,AMD 在依赖模块加载完成后就直接执行依赖模块,依赖模块的执行顺序和我们书写的顺序不一定一致。而 CMD 在依赖模块加载完成后并不执行,只是下载而已,等到所有的依赖模块都加载好后,进入回调函数逻辑,遇到 require 语句 的时候才执行对应的模块,这样模块的执行顺序就和我们书写的顺序保持一致了。
CMDdefine(function(require, exports, module) {
	var a = require("./a");
	a.doSomething();
	// ...
	var b = require("./b"); // 依赖可以就近书写
	b.doSomething();
	// ...
});

事件流

HTML 中与 JavaScript 交互是通过事件驱动来实现的,例如鼠标点击事件 onclick、页面的滚动事件 onscroll 等等,可以向文档或者文档中的元素添加事件侦听器来预订事件。事件流描述的是从页面中接收事件的顺序。

第一种事件模型是最早的 DOM0 级模型,这种模型不会传播,所以没有事件流的概念,但是现在有的浏览器支持以冒泡的方式实现,它可以在网页中直接定义监听函数,也可以通过 js 属性来指定监听函数。这种方式是所有浏览器都兼容的。

第二种事件模型是 IE 事件模型,在该事件模型中,一次事件共有两个过程,事件处理阶段,和事件冒泡阶段。事件处理阶段会首先执行目标元素绑定的监听事件。然后是事件冒泡阶段,冒泡指的是事件从目标元素冒泡到 document,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。这种模型通过 attachEvent 来添加监听函数,可以添加多个监听函数,会按顺序依次执行。

第三种是 DOM2 级事件流,包括下面几个阶段。

  • 事件捕获阶段
  • 处于目标阶段
  • 事件冒泡阶段

addEventListener 是 DOM2 级事件新增的指定事件处理程序的操作,这个方法接收 3 个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是 true,表示在捕获阶段调用事件处理程序;如果是 false,表示在冒泡阶段调用事件处理程序。
IE 只支持事件冒泡。

阻止事件冒泡

  1. cancelBubble(HTML DOM Event 对象属性) :如果事件句柄想阻止事件传播到包容对象,必须把该属性设为 true。
  2. stopPropagation(HTML DOM Event 对象方法):终止事件在传播过程的捕获、目标处理或起泡阶段进一步传播。调用该方法后,该节点上处理该事件的处理程序将被调用,事件不再被分派到其他节点。
  3. preventDefault(HTML DOM Event 对象方法)通知浏览器不要执行与事件关联的默认动作,取消该事件(假如事件是可取消的)而不停止事件的进一步传播。
function stopBubble(e) {
	if(e && e.stopPropagation) {
		e.stopPropagation()  
	} else { 
		window.event.cancelBubble = true
	}
}

事件委托

事件委托指的是,不在事件的发生地(直接 dom)上设置监听函数,而是在其父元素上设置监听函数,通过事件冒泡,父元素可以监听到子元素上事件的触发,通过判断事件发生元素 DOM 的类型,来做出不同的响应。

事件委托本质上是利用了浏览器事件冒泡的机制。因为事件在冒泡过程中会上传到父节点,并且父节点可以通过事件对象获取到目标节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方式称为事件代理。使用事件代理我们可以不必要为每一个子元素都绑定一个监听事件,这样减少了内存上的消耗。并且使用事件代理我们还可以实现事件的动态绑定,比如说新增了一个子节点,我们并不需要单独地为它添加一个监听事件,它所发生的事件会交给父元素中的监听函数来处理。

最经典的就是 ul 和 li 标签的事件监听,比如我们在添加事件时候,采用事件委托机制,不会在 li 标签上直接添加,而是在 ul 父元素上添加。好处:比较合适动态元素的绑定,新添加的子元素也会有监听函数,也可以有事件触发机制。

<ul id="list">
  <li>item 1</li>
  <li>item 2</li>
  <li>item 3</li>
  ......
  <li>item n</li>
</ul>
window.onload = function(){
	var myul = document.querySelector("#list");
	myul.onclick = function(){
		console.log(123)
	}
}

图片的懒加载和预加载

  • 预加载:提前加载图片,当用户需要查看时可直接从本地缓存中渲染。
  • 懒加载:懒加载的主要目的是作为服务器前端的优化,减少请求数或延迟请求数。

两种技术的本质:两者的行为是相反的,一个是提前加载,一个是迟缓甚至不加载。懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力。

mouseover 和 mouseenter 的区别

  • mouseover:当鼠标移入元素或其子元素都会触发事件,所以有一个重复触发冒泡的过程。对应的移除事件是 mouseout
  • mouseenter:当鼠标移除元素本身(不包含元素的子元素)会触发事件,也就是不会冒泡,对应的移除事件是 mouseleave

clientHeight、scrollHeight、offsetHeight 以及 scrollTop、offsetTop、clientTop 的区别

在这里插入图片描述

  • clientHeight:表示的是可视区域的高度,不包含 border 和滚动条
  • offsetHeight:表示可视区域的高度,包含了 border 和滚动条
  • scrollHeight:表示了所有区域的高度,包含了因为滚动被隐藏的部分。
  • clientTop:表示边框 border 的厚度,在未指定的情况下一般为 0
  • scrollTop:滚动后被隐藏的高度,获取对象相对于由 offsetParent 属性指定的父坐标(css定位的元素或 body 元素)距离顶端的高度。

documen.write 和 innerHTML 的区别

  • document.write 的内容会代替整个文档内容,会重写整个页面。
  • innerHTML 的内容只是替代指定元素的内容,只会重写页面中的部分内容。

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

  1. 创建新节点
createDocumentFragment(node);
createElement(node);
createTextNode(text);
  1. 添加、移除、替换、插入
appendChild(node);
removeChild(node);
replaceChild(new,old);
insertBefore(new,old);
  1. 查找
getElementById();
getElementsByName();
getElementsByTagName();
getElementsByClassName();
querySelector();
querySelectorAll();
  1. 属性操作
getAttribute(key);
setAttribute(key,value);
hasAttribute(key);
removeAttribute(key);

JS 对象类型,基本对象类型以及引用对象类型的区别

分为基本对象类型和引用对象类型。

基本数据类型:按值访问,可操作保存在变量中的实际的值。基本类型值指的是简单的数据段。基本数据类型有这六种:undefined、null、string、number、boolean、symbol。

引用类型:当复制保存着对象的某个变量时,操作的是对象的引用,但在为对象添加属性时,操作的是实际的对象。引用类型值指那些可能为多个值构成的对象。引用类型有这几种:Object、Array、RegExp、Date、Function、特殊的基本包装类型(String、
Number、Boolean)以及单体内置对象(Global、Math)。

内部属性 [[Class]]

所有 typeof 返回值为 “object” 的对象(如数组)都包含一个内部属性 [[Class]](我们可以把它看作一个内部的分类,而非传统的面向对象意义上的类)。这个属性无法直接访问,一般通过 Object.prototype.toString(…) 来查看。例如:

Object.prototype.toString.call( [1,2,3] );
// "[object Array]"
Object.prototype.toString.call( /regex-literal/i );
// "[object ]"

JS 内置对象

js 中的内置对象主要指的是在程序执行前存在全局作用域里的由 js 定义的一些全局值属性、函数和用来实例化其他对象的构造函数对象。一般我们经常用到的如全局变量值 NaN、undefined,全局函数如 parseInt()、parseFloat() 用来实例化对象的构造函数如 Date、Object 等,还有提供数学计算的单体内置对象如 Math 对象。

JavaScript 类数组对象的定义

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

  • 通过 call 调用数组的 slice 方法来实现转换
Array.prototype.slice.call(arrayLike);
  • 通过 call 调用数组的 splice 方法来实现转换
Array.prototype.splice.call(arrayLike, 0);
  • 通过 apply 调用数组的 concat 方法来实现转换
Array.prototype.concat.apply([], arrayLike);
  • 通过 Array.from 方法来实现转换
Array.from(arrayLike);

undefined 与 undeclared 区别

已在作用域中声明但还没有赋值的变量,是 undefined 的。相反,还没有在作用域中声明过的变量,是 undeclared 的。对于 undeclared 变量的引用,浏览器会报引用错误,如ReferenceError: b is not defined 。但是我们可以使用 typeof 的安全防范机制来避免报错,因为对于 undeclared(或者 not defined )变量,typeof 会返回 “undefined”。

null 和 undefined 区别

首先 Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。undefined 代表的含义是未定义,null 代表的含义是空对象。一般变量声明了但还没有定义的时候会返回 undefined,null 主要用于赋值给一些可能会返回对象的变量,作为初始化。

undefined 在 js 中不是一个保留字,这意味着我们可以使用undefined 来作为一个变量名,这样的做法是非常危险的,它会影响我们对 undefined 值的判断。但是我们可以通过一些方法获得安全的 undefined 值,比如说 void 0。当我们对两种类型使用 typeof 进行判断的时候,Null 类型化会返回 “object”,这是一个历史遗留的问题。当我们使用双等号对两种类型的值进行比较时会返回 true,使用三个等号时会返回 false。

如何获取安全的 undefined 值?

因为 undefined 是一个标识符,所以可以被当作变量来使用和赋值,但是这样会影响 undefined 的正常判断。表达式 void ___ 没有返回值,因此返回结果是 undefined。void 并不改变表达式的结果,只是让表达式不返回值。按惯例我们用 a = void 0

JS 监听对象属性

在 ES5 中可以通过 Object.defineProperty 来实现已有属性的监听

Object.defineProperty(user,'name',{
	set: function(key,value){
	},
	get: function(key){
	}
})

缺点:如果 id 不在 user 对象中,则不能监听 id 的变化在 ES6 中可以通过 Proxy 来实现

var user = new Proxy({}, {
	set: function(target,key,value,receiver){
	},
	get: function(obj, prop) {
        return prop in obj ? obj[prop] : 37;
    }
})

这样即使有属性在 user 中不存在,通过 user.id 来定义也同样可以这样监听这个属性的变化。

实现一个私有变量,用 getName 方法可以访问,不能直接访问

  1. 通过 defineProperty 来实现
obj={
name:yuxiaoliang, getName:function(){
return this.name
}
}
object.defineProperty(obj,"name",{
	enumerable: false
});
  1. 通过函数的创建形式
function product(){
	var name='yuxiaoliang';
	this.getName=function(){
		return name;
	}
}
var obj=new product()

==和===、以及 Object.is 的区别

两等号判等,会在比较时进行类型转换。三等号判等(判断严格),比较时不进行隐式类型转换,(类型不同则会返回 false)。

  • == 主要存在:强制转换成 number,ull == undefined
" "==0 //true
"0"==0 //true
" " !="0" //true
123=="123" //true
null==undefined //true
  • Object.is 特例:
Object.is(+0, -0) // false
+0 == -0 // true
Object.is(NaN, NaN) // true
NaN == NaN // false

setTimeout、setInterval 和 requestAnimationFrame 之间的区别

与 setTimeout 和 setInterval 不同,requestAnimationFrame 不需要设置时间间隔,大多数电脑显示器的刷新频率是 60Hz,大概相当于每秒钟重绘 60 次。大多数浏览器都会对重绘操作加以限制,不超过显示器的重绘频率,因为即使超过那个频率用户体验也不会有提升。因此,最平滑动画的最佳循环间隔是 1000ms/60,约等于 16.6ms。RAF 采用的是系统时间间隔,不会因为前面的任务,不会影响 RAF,但是如果前面的任务多的话,会响应 setTimeout 和 setInterval 真正运行时的时间间隔。

特点:

  • requestAnimationFrame 会把每一帧中的所有 DOM 操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率。
  • 在隐藏或不可见的元素中,requestAnimationFrame 将不会进行重绘或回流,这当然就意味着更少的 CPU、GPU 和内存使用量。
  • requestAnimationFrame 是由浏览器专门为动画提供的 API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU 开销。

控制一次加载一张图片,加载完后再加载下一张

<script type="text/javascript">
var obj=new Image();
obj.src="http://www.phpernote.com/uploadfiles/editor/201107240502201179.jpg";
obj.onload=function(){
alert('图片的宽度为:'+obj.width+';图片的高度为:'+obj.height);
document.getElementById("mypic").innnerHTML="<img src='"+this.src+"' />";
}
</script>
<div id="mypic">onloading……</div>
<script type="text/javascript">
var obj=new Image();
obj.src="http://www.phpernote.com/uploadfiles/editor/201107240502201179.jpg";
obj.onreadystatechange=function(){
	if(this.readyState=="complete"){
		alert('图片的宽度为:'+obj.width+';图片的高度为:'+obj.height);
		document.getElementById("mypic").innnerHTML="<img src='"+this.src+"' />";
	}
}
</script>
<div id="mypic">onloading……</div>

JS 判断类型

typeof()instanceofObject.prototype.toString.call()。对于Array,Object 等 typeof 只能判断是 object。

如何判断一个数组

  1. instanceof
let a = [];
a instanceof Array; //true
let b = {};
b instanceof Array; //false
  1. constrcutor
let a = [1,3,4];
a.constructor === Array;//true
  1. Object.prototype.toString.call()
let a = [];
Object.prototype.toString.call(a) === '[object Array]'; //true
  1. Array.isArray()
let a = [1,2,3]
Array.isArray(a);//true

不同数据类型的值的比较

参考 https://zhuanlan.zhihu.com/p/34272091

其他值到字符串的转换规则

  • Null 和 Undefined 类型 ,null 转换为 “null”,undefined 转换为 “undefined”,
  • Boolean 类型,true 转换为 “true”,false 转换为 “false”。
  • Number 类型的值直接转换,不过那些极小和极大的数字会使用指数形式。
  • Symbol 类型的值直接转换,但是只允许显式强制类型转换,使用隐式强制类型转换会产生错误。
  • 对普通对象来说,除非自行定义 toString() 方法,否则会调用 toString()(Object.prototype.toString())来返回内部属性 [[Class]] 的值,如"[object Object]"。如果对象有自己的 toString() 方法,字符串化时就会调用该方法并使用其返回值。

其他值到数字值的转换规则

  • Undefined 类型的值转换为 NaN。
  • Null 类型的值转换为 0。
  • Boolean 类型的值,true 转换为 1,false 转换为 0。
  • String 类型的值转换如同使用 Number() 函数进行转换,如果包含非数字值则转换为NaN,空字符串为 0。
  • Symbol 类型的值不能转换为数字,会报错。
  • 对象(包括数组)会首先被转换为相应的基本类型值,如果返回的是非数字的基本类型值,则再遵循以上规则将其强制转换为数字。为了将值转换为相应的基本类型值,抽象操作 ToPrimitive 会首先(通过内部操作 DefaultValue)检查该值是否有 valueOf() 方法。如果有并且返回基本类型值,就使用该值进行强制类型转换。如果没有就使用 toString() 的返回值(如果存在)来进行强制类型转换。如果 valueOf() 和 toString() 均不返回基本类型值,会产生 TypeError 错误。

其他值到布尔类型的值的转换规则

• undefined
• null
• false
• +0、-0 和 NaN
• “”
假值的布尔强制类型转换结果为 false。从逻辑上说,假值列表以外的都应该是真值。

{} 和 [] 的 valueOf 和 toString 结果

  • {} 的 valueOf 结果为 {} ,toString 的结果为 “[object Object]”
  • [] 的 valueOf 结果为 [] ,toString 的结果为 “”

~ 操作符

~ 返回 2 的补码,并且 ~ 会将数字转换为 32 位整数,因此我们可以使用 ~ 来进行取整操作。~x 大致等同于 -(x+1)。

解析字符串中的数字和将字符串强制类型转换为数字的返回结果都是数字,它们之间的区别是什么?

解析允许字符串(如 parseInt() )中含有非数字字符,解析按从左到右的顺序,如果遇到非数字字符就停止。而转换(如 Number ())不允许出现非数字字符,否则会失败并返回 NaN。

+ 操作符什么时候用于字符串的拼接?

如果某个操作数是字符串或者能够通过以下步骤转换为字符串的话,+ 将进行拼接操作。

如果其中一个操作数是对象(包括数组),则首先对其调用 ToPrimitive 抽象操作,该抽象操作再调用 [[DefaultValue]],以数字作为上下文。如果不能转换为字符串,则会将其转换为数字类型来进行计算。简单来说就是,如果 + 的其中一个操作数是字符串(或者通过以上步骤最终得到字符串),则执行字符串拼接,否则执行数字加法。那么对于除了加法的运算符来说,只要其中一方是数字,那么另一方就会被转为数字。

什么情况下会发生布尔值的隐式强制类型转换?

  • if (…) 语句中的条件判断表达式。
  • for ( … ; … ; … ) 语句中的条件判断表达式(第二个)。
  • while (…) 和 do…while(…) 循环中的条件判断表达式。
  • ? : 中的条件判断表达式。
  • 逻辑运算符 ||(逻辑或)和 &&(逻辑与)左边的操作数(作为条件判断表达式)

== 操作符的强制类型转换规则

  • 字符串和数字之间的相等比较,将字符串转换为数字之后再进行比较。
  • 其他类型和布尔类型之间的相等比较,先将布尔值转换为数字后,再应用其他规则进行比较。
  • null 和 undefined 之间的相等比较,结果为真。其他值和它们进行比较都返回假值。
  • 对象和非对象之间的相等比较,对象先调用 ToPrimitive 抽象操作后,再进行比较。
  • 如果一个操作值为 NaN ,则相等比较返回 false( NaN 本身也不等于 NaN )。
  • 如果两个操作值都是对象,则比较它们是不是指向同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回 true,否则,返回 false

什么是假值对象?

浏览器在某些特定情况下,在常规 JavaScript 语法基础上自己创建了一些外来值,这些就是“假值对象”。假值对象看起来和普通对象并无二致(都有属性,等等),但将它们强制类型转换为布尔值时结果为 false 最常见的例子是 document.all,它是一个类数组对象,包含了页面上的所有元素,由 DOM(而不是 JavaScript 引擎)提供给 JavaScript 程序使用。

|| 和 && 操作符的返回值

|| 和 && 首先会对第一个操作数执行条件判断,如果其不是布尔值就先进行 ToBoolean 强制类型转换,然后再执行条件判断。对于 || 来说,如果条件判断结果为 true 就返回第一个操作数的值,如果为 false 就返回第二个操作数的值。&& 则相反,如果条件判断结果为 true 就返回第二个操作数的值,如果为 false 就返回第一个操作数的值。

|| 和 && 返回它们其中一个操作数的值,而非条件判断的结果。

JS 中继承实现的几种方式

  1. 原型链继承,将父类的实例作为子类的原型,他的特点是实例是子类的实例也是父类的实例,父类新增的原型方法/属性,子类都能够访问,并且原型链继承简单易于实现,缺点是来自原型对象的所有属性被所有实例共享,无法实现多继承,无法向父类构造函数传参。
  2. 构造继承,使用父类的构造函数来增强子类实例,即复制父类的实例属性给子类,构造继承可以向父类传递参数,可以实现多继承,通过 call 多个父类对象。但是构造继承只能继承父类的实例属性和方法,不能继承原型属性和方法,无法实现函数服用,每个子类都有父类实例函数的副本,影响性能
  3. 实例继承,为父类实例添加新特性,作为子类实例返回,实例继承的特点是不限制调用方法,不管是 new 子类()还是子类()返回的对象具有相同的效果,缺点是实例是父类的实例,不是子类的实例,不支持多继承
  4. 拷贝继承:特点:支持多继承,缺点:效率较低,内存占用高(因为要拷贝父类的属性)无法获取父类不可枚举的方法(不可枚举方法,不能使用 for in 访问到)
  5. 组合继承:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
  6. 寄生组合继承:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺

symbol

Symbol 是 ES6 的新增属性,代表用给定名称作为唯一标识,这种类型的值可以这样创建,let id=symbol(“id”)

Symbl 确保唯一,即使采用相同的名称,也会产生不同的值,我们创建一个字段,仅为知道对应 symbol 的人能访问,使用 symbol 很有用,symbol 并不是 100%隐藏,有内置方法 Object.getOwnPropertySymbols(obj)可以获得所有的 symbol。也有一个方法 Reflect.ownKeys(obj)返回对象所有的键,包括 symbol。所以并不是真正隐藏。但大多数库内置方法和语法结构遵循通用约定他们是隐藏的。

Symbol 值不能够被强制类型转换为数字(显式和隐式都会产生错误),但可以被强制类型转换为布尔值(显式和隐式结果都是 true )。

ES6 箭头函数

箭头函数与普通函数的区别在于:

  1. 箭头函数没有 this,所以需要通过查找作用域链来确定 this 的值,这就意味着如果箭头函数被非箭头函数包含,this 绑定的就是最近一层非箭头函数的 this
  2. 箭头函数没有自己的 arguments 对象,但是可以访问外围函数的 arguments 对象
  3. 不能通过 new 关键字调用,同样也没有 new.target 值和原特性

获得对象上的属性

从 ES5 开始,有三种方法可以列出对象的属性:

  • for(let k in obj):该方法依次访问一个对象及其原型链中所有可枚举的类型
  • object.keys:返回一个数组,包括所有可枚举的属性名称
  • object.getOwnPropertyNames:返回一个数组包含不可枚举的属性

JS 中整数的安全范围

安全整数指的是,在这个范围内的整数转化为二进制存储的时候不会出现精度丢失,能够被“安全”呈现的最大整数是 2^53 - 1,即 9007199254740991,在 ES6 中被定义为 Number.MAX_SAFE_INTEGER。最小整数是-9007199254740991,在 ES6 中被定义为
Number.MIN_SAFE_INTEGER。

如果某次计算的结果得到了一个超过 JavaScript 数值范围的值,那么这个值会被自动转换为特殊的 Infinity 值。如果某次计算返回了正或负的 Infinity 值,那么该值将无法参与下一次的计算。判断一个数是不是有穷的,可以使用 isFinite 函数来判断。

typeof NaN 结果

NaN 意指“不是一个数字”(not a number),NaN 是一个“警戒值”(sentinel value,有特殊用途的常规值),用于指出数字类型中的错误情况,即“执行数学运算没有成功,这是失败后返回的结果”。

typeof NaN; // "number"

NaN 是一个特殊值,它和自身不相等,是唯一一个非自反(自反,reflexive,即 x === x 不成立)的值。而 NaN != NaN 为 true。

isNaN 和 Number.isNaN 区别

函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断。

函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为NaN ,这种方法对于 NaN 的判断更为准确。

Array 构造函数只有一个参数值时的表现

Array 构造函数只带一个数字参数的时候,该参数会被作为数组的预设长度(length),而非只充当数组中的一个元素。这样创建出来的只是一个空数组,只不过它的 length 属性被设置成了指定的值。构造函数 Array(…) 不要求必须带 new 关键字。不带时,它会被自动补上。

同步和异步的区别

同步指的是当一个进程在执行某个请求的时候,如果这个请求需要等待一段时间才能返回,那么这个进程会一直等待下去,直到消息返回为止再继续向下执行。

异步指的是当一个进程在执行某个请求的时候,如果这个请求需要等待一段时间才能返回,这个时候进程会继续往下执行,不会阻塞等待消息的返回,当消息返回时系统再通知进程进行处理。

什么是 Polyfill?

Polyfill 指的是用于实现浏览器并不支持的原生 API 的代码。比如说 querySelectorAll 是很多现代浏览器都支持的原生 Web API,但是有些古老的浏览器并不支持,那么假设有人写了一段代码来实现这个功能使这些浏览器也支持了这个功能,那么这就可以成为一个 Polyfill。

URL 和 URI 的区别

  • URI: Uniform Resource Identifier 指的是统一资源标识符
  • URL: Uniform Resource Location 指的是统一资源定位符
  • URN: Universal Resource Name 指的是统一资源名称

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

escape,encodeURI,encodeURIComponent 区别?

  • encodeURI 是对整个 URI 进行转义,将 URI 中的非法字符转换为合法字符,所以对于一些在URI 中有特殊意义的字符不会进行转义。
  • encodeURIComponent 是对 URI 的组成部分进行转义,所以一些特殊字符也会得到转义。
  • escape 和 encodeURI 的作用相同,不过它们对于 unicode 编码为 0xff 之外字符的时候会有区别,escape 是直接在字符的 unicode 编码前加上 %u,而 encodeURI 首先会将字符转换为 UTF-8 的格式,再在每个字节前加上 %。

toPrecision 和 toFixed 和 Math.round 的区别

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

CSP

CSP 指的是内容安全策略,它的本质是建立一个白名单,告诉浏览器哪些外部资源可以加载和执行。我们只需要配置规则,如何拦截由浏览器自己来实现。通常有两种方式来开启 CSP,一种是设置 HTTP 首部中的 Content-Security-Policy,一种是设置 meta 标签的方式 <meta http-equiv="Content-Security-Policy">

什么是点击劫持?如何防范点击劫持?

点击劫持是一种视觉欺骗的攻击手段,攻击者将需要攻击的网站通过 iframe 嵌套的方式嵌入自己的网页中,并将 iframe 设置为透明,在页面中透出一个按钮诱导用户点击。我们可以在 http 相应头中设置 X-FRAME-OPTIONS 来防御用 iframe 嵌套的点击劫持攻击。通过不同的值,可以规定页面在特定的一些情况才能作为 iframe 来使用。

Map、Set

Map

  • Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
  • WeakMap 结构与 Map 结构类似,也是用于生成键值对的集合。但是WeakMap 只接受对象作为键名( null 除外),不接受其他类型的值作为键名。而且 WeakMap 的键名所指向的对象,不计入垃圾回收机制。
const myMap = new Map();

const keyString = 'a string';
const keyObj = {};
const keyFunc = function() {};

// 添加键
myMap.set(keyString, "和键'a string'关联的值");
myMap.set(keyObj, "和键 keyObj 关联的值");
myMap.set(keyFunc, "和键 keyFunc 关联的值");

console.log(myMap.size); // 3

// 读取值
console.log(myMap.get(keyString)); // "和键'a string'关联的值"
console.log(myMap.get(keyObj)); // "和键 keyObj 关联的值"
console.log(myMap.get(keyFunc)); // "和键 keyFunc 关联的值"

console.log(myMap.get('a string')); // "和键'a string'关联的值",因为 keyString === 'a string'
console.log(myMap.get({})); // undefined,因为 keyObj !== {}
console.log(myMap.get(function() {})); // undefined,因为 keyFunc !== function () {}

Map 的迭代

for (const [key, value] of myMap) {
  console.log(`${key} = ${value}`);
}
// 0 = zero
// 1 = one

for (const key of myMap.keys()) {
  console.log(key);
}
// 0
// 1

for (const value of myMap.values()) {
  console.log(value);
}
// zero
// one

for (const [key, value] of myMap.entries()) {
  console.log(`${key} = ${value}`);
}
// 0 = zero
// 1 = one
myMap.forEach((value, key) => {
  console.log(`${key} = ${value}`);
});
// 0 = zero
// 1 = one

Set

  • ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
  • WeakSet 结构与 Set 类似,也是不重复的值的集合。但是 WeakSet 的成员只能是对象,而不能是其他类型的值。WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用
let mySet = new Set();

mySet.add(1); // Set [ 1 ]
mySet.add(5); // Set [ 1, 5 ]
mySet.add(5); // Set [ 1, 5 ]
mySet.add("some text"); // Set [ 1, 5, "some text" ]
let o = {a: 1, b: 2};
mySet.add(o); // Set(4) {1, 5, 'some text', {…}}

mySet.add({a: 1, b: 2}); // o 指向的是不同的对象,Set(4) {1, 5, 'some text', {…}, {…}}

mySet.has(1); // true
mySet.has(3); // false

mySet.size; // 5

mySet.delete(5);  // true,从 set 中移除 5
mySet.has(5);     // false, 5 已经被移除

mySet.size; // 4,刚刚移除一个值

Set 迭代方法

// 迭代整个 set
// 按顺序输出:1, "some text", {"a": 1, "b": 2}, {"a": 1, "b": 2}
for (let item of mySet) console.log(item);

// 按顺序输出:1, "some text", {"a": 1, "b": 2}, {"a": 1, "b": 2}
for (let item of mySet.keys()) console.log(item);

// 按顺序输出:1, "some text", {"a": 1, "b": 2}, {"a": 1, "b": 2}
for (let item of mySet.values()) console.log(item);

// 按顺序输出:1, "some text", {"a": 1, "b": 2}, {"a": 1, "b": 2}
//(键与值相等)
for (let [key, value] of mySet.entries()) console.log(key);

// 使用 Array.from 转换 Set 为 Array
var myArr = Array.from(mySet); // [1, "some text", {"a": 1, "b": 2}, {"a": 1, "b": 2}]

// 如果在 HTML 文档中工作,也可以:
mySet.add(document.body);
mySet.has(document.querySelector("body")); // true

// Set 和 Array 互换
mySet2 = new Set([1, 2, 3, 4]);
mySet2.size;               // 4
[...mySet2];               // [1,2,3,4]

// 可以通过如下代码模拟求交集
let intersection = new Set([...set1].filter(x => set2.has(x)));

// 可以通过如下代码模拟求差集
let difference = new Set([...set1].filter(x => !set2.has(x)));

// 用 forEach 迭代
mySet.forEach(function(value) {
  console.log(value);
});
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值