一.ES基础知识点
1.javascript基本类型有几种?引用类型有哪些?用什么方法可以区分他们?
答:js基本类型有6种,分别是String,Number,Boolean,Null,Undefined,Symbol(es6);还有一种比较特殊的基本类型Object;引用类型有Array,Function,Date,Object等等。
基本类型都是按值传递,而引用类型是按引用传递。例如
var a = 10;
var b = a;
b = 5;
console.log(a);//10
console.log(b);//5
上述代码因为a属于基本类型,是按值传递的,所以即使b改变了,a并不会发生变化。
var obj1 = {
name:'zhang san',
age:20
};
var obj2 = obj1;
obj2.age = 10;
console.log(obj2.age);//10
console.log(obj1.age);//10
因为obj1是引用类型,当obj1被创建时,会单独创建一个内存,obj1会指向该内存区域;当obj2=obj1时,此时obj2同样会指向该内存区域,当obj2发生改变时,内存区域发生改变,从而obj1也会发生改变。
基本类型可通过typeof进行判断;但是null值除外,因为typeof(null)会返回Object; typeof函数会返回Function。
引用类型可通过instanceof()函数来判断;比如
var a = new Date();
a instanceof Date;//true
2.proto和prototype的区别?
答:所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展属性(null除外)
-
所有的引用类型(数组、对象、函数),都有一个**proto**属性,属性值是一个普通的对象
-
所有的函数,都有一个prototype属性,属性值也是一个普通的对象
-
所有的引用类型(数组、对象、函数),**proto**属性值指向它的构造函数的prototype属性值。
//要点一,自由扩展性;
var obj = {}; obj.a = 100;
var arr = []; arr.a = 100;
function fn(){
}
fn.a = 100;
//要点二:_proto_
console.log(obj._proto_);
console.log(arr._proto_);
console.log(fn._proto_);
//要点三:函数有prototype
console.log(fn.prototype);
//要点四:引用类型的_proto_属性值指向其构造函数的prototype
console.log(obj._proto_===Object.prototype);//true
3.如何理解原型?
答:javascript是基于原型的语言。
原型得从构造函数说起
this.name = name;
}
fun.prototype.alertName = function(){
alert(this.name);
}
//创建示例
var f = new fun('zhangsan');
f.printName = function(){
console.log(this.name);
}
//测试
f.printName();//zhangsan
f.alertName();//zhangsan
从上面示例可以知道,当视图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的proto(即它的构造函数的prototype)中寻找,因此f.alertName就会找到fun.prototype.alertName.
4.如何理解JS的原型链?
答:以上面的代码块为例,当调用f.alertName()函数时,首先会查找f对象中有无alertName方法,在f对象中没有找到该方法,然后就开始查找f.proto也就是其实例fun.prototype有无alertName,找到之后就引用该方法。
但是如果用f.toString()调用时,同样会先开始找f对象有无toString方法,然后就去f.proto中查找,如果没找到,那就继续去f.proto.proto_里面查找,这种过程因为是以链式结构去查找,所以称为原型链。如果一直都找不到的话,则宣告失败,浏览器会报错。
5.作用域分为几种?
答:分为全局作用域和局部作用域,在es6中添加了块作用域。全局作用域即在最外部定义变量或者用window定义变量来表示。而局部作用域是在函数内用var定义变量;es6中的let和const都是定义块级作用域,不同的是let是可变参数,而const是不可变变量;
6.什么是闭包?闭包有什么作用?
答:闭包就是能够读取其它函数内部变量的函数。最简单的用法是
<pre>function test(){
var name = "Mozilla";
function closure(){
console.log(name);
}
closure();
}
test();
</pre>
在以上代码中test()创建了一个局部变量name和一个名为closure的函数,closure是test函数里面的一个内部函数;当执行test时,同时会执行内部函数closure,就会打印出test作用域中的name的值;还有一个比较经典的例子
<pre>for(var i = 0;i<6;i++){
setTimeout(function(){
console.log(i);//6,6,6,6,6,6
},0)
}
</pre>
在上面代码中打印出6个6;如果想要输出0,1,2,3,4,5,则需要使用闭包来实现(es6中可通过块作用域实现)
<pre>for(var i=0;i<6;i++){
setTimeout(function(i){
console.log(i)
}(i),0)
}
//或者
for(var i=0;i<6;i++){
setTimeout(function(i){
return function(){
console.log(i)
}
}(i))
}
//es6块作用域解决该问题
for(let i =0;i<6;i++){
setTimeout(function(){
console.log(i)
})
}
</pre>
闭包主要有两个作用场景:
-
函数作为返回值
-
函数作为参数传递
<pre>//函数作为返回值
function sum(x){
return function (){
console.log(x)
}
}
var test1 = new sum(5);
var x = 55;
test1();//5
//函数作为参数传递
function sum1(x){
return function(y){
return x+y
}
}
var test2 = new sum1(5);
test2(6);//11
</pre>
7.如何理解任务队列?
答:因为javascript是单线程,所以在线程上的任务都得排队,也就是说只有等到前面一个任务执行完成时,才会执行后面的任务。这样的话会造成一个问题,即当前面一个任务执行时间很长的时候,后面的任务就必须得等到前面的任务执行完之后才会执行,会造成空白页的现象。
为了解决这个问题,javascript设计者就将任务分为2种,一种是同步任务,另外一种是异步任务。同步任务就是在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入“任务队列”的任务,只有“任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。异步任务包括宏任务和微任务。
8.什么是异步任务?异步有哪几种形式?
答:异步是需要等到主线程任务执行完之后才执行的任务。异步有以下四种形式:
1.setTimeout/setInterval;
2.ajax;
3.dom事件绑定
4.<img>图片引入
setTimeout代码示例
<pre>console.log('第一步');
setTimeout(function(){
console.log('第二步');
},0)
console.log('第三步');
//结果输出
//‘第一步’
//‘第三步’
//‘第二步’
</pre>
ajax代码操作示例
<pre>var xhr = new XMLHttpRequest();//创建一个XMLHttpRequest对象实例
xhr.onreadystatechange = function(){
//这里的函数是异步执行
if(xhr.readyState===4){
if(xhr.status===200){
console.log(xhr.responseText);
}
}
}
xhr.open("get","http://www.runoob.com/try/ajax/ajax_info.txt",true);
xhr.send();
</pre>
dom事件绑定示例
<pre><button id="clickFun">点击我</button>
<script>
var clickNode = document.getElementById("clickFun");
clickNode.addEventListener('click',function(){
console.log("点击成功");
})
</script>
</pre>
<img>标签引入示例
<pre>console.log('start')
var img = document.createElement('img')
// 或者 img = new Image()
img.onload = function () {
console.log('loaded')
img.onload = null
}
img.src = '/xxx.png'
console.log('end')
</pre>
9.es6的箭头函数相比于函数有什么优点,哪些场景需要用到?
答:箭头函数和之前的函数相比,节省了一些代码,增加了工作效率;箭头函数主要是用于解决this的指向问题。例如
<pre>function test(){
var arr = [1,2,3];
console.log(this);//这里的this指向{name:'wang'}对象
//函数表达式
arr.map(function(){
console.log(this);//这里的this指向window对象,指向比较混乱
})
//箭头函数
arr.map((item)=>{
console.log(this)//这里的this指向父元素作用域,即{name:'wang'}对象
})
}
test.call({name:'wang'});
</pre>
10.es6中的module有什么作用?
答:ES6中模块化语法更加简洁,如果只是输出一个唯一对象,可以通过
export default {}表示
例如
index.js
<pre>export default{
a:100
}
</pre>
test.js
<pre>import index from './index.js' //假如index.js和test.js在同一文件夹中
console.log(index.a);//100
</pre>
如果想要输出多个对象,就不需要default了,在引入的时候用{}表示,例如:
index.js
<pre>export function fn1(){
console.log('fn1')
}
export function fn2(){
console.log('fn2')
}
</pre>
test.js
<pre>import {fn1} from './index.js'
fn1();//fn1
</pre>
11.ES6 class和普通构造函数的区别?
答:构造函数通常的写法
<pre>//创建一个构造函数Animal
function Animal(type){
this.type = type;
}
//构造函数的原型
Animal.prototype = {
canEat:function(){
....
}
}
//创建一个实例
var theAnimal = new Animal('dog');
theAnimal.canEat();
</pre>
class的写法
<pre>class Animal{
constructor(x.y){
this.x = x;
this.y = y;
}
add(){
return this.x +this.y;
}
}
const m = new Animal(1,2);
console.log(m.add())
</pre>
由以上两个代码可知es6中的类是由constructor来创建一个构造函数,然后就直接添加函数,在这里是不存在原型的。
在继承方面,class的使用无疑会更加简单,例如:
JS构造函数实现继承:
<pre>// 动物
function Animal() {
this.eat = function () {
console.log('animal eat')
}
}
// 狗
function Dog() {
this.bark = function () {
console.log('dog bark')
}
}
Dog.prototype = new Animal()
// 哈士奇
var hashiqi = new Dog()
</pre>
es6 class实现继承
<pre>class Animal {
constructor(name) {
this.name = name
}
eat() {
console.log(${this.name} eat
)
}
}
class Dog extends Animal {
constructor(name) {
super(name)
this.name = name
}
say() {
console.log(${this.name} say
)
}
}
const dog = new Dog('哈士奇')
dog.say()
dog.eat()
</pre>
12.ES6中的set的应用场景是什么?
答:
set主要应用于以下几个场景:
1.数组去重;
<pre>var arr = [1,2,1,1,1,2,3];
var unique = [...new Set(arr)];
console.log(unique)//1,2,3
</pre>
2.两个数组并集
3.两个数组之间的交集
4.两个数组之间的差集
<pre>var a = new Set([1,2,1,3]);
var b = new Set([2,3,4,5,2])
//两个数组并集
let union = new Set([...a,...b]);//[1,2,3,4,5]
//交集
let intersect = new Set([...a].filter(x=>b.has(x)));//2,3
//差集
let different = new Set([...a].filter(x=>!b.has(x)));//1
</pre>
2.浏览器相关的api
2.1 使用原生js写出dom的增删查改
答:
查看:相关api有getElementsByTagName,getElementById,getElementsByClassName,querySelectorAll等等。
<pre>// 通过 id 获取
var div1 = document.getElementById('div1') // 元素
// 通过 tagname 获取
var divList = document.getElementsByTagName('div') // 集合
console.log(divList.length)
console.log(divList[0])
// 通过 class 获取
var containerList = document.getElementsByClassName('container') // 集合
// 通过 CSS 选择器获取
var pList = document.querySelectorAll('p') // 集合
//获取父元素节点
var div1 = document.getElementById('div1');
var parent = div.parentElement;
//获取子元素节点
var div1 = document.getElementById('div1');
var child = div.childNodes;
</pre>
修改:
<pre>var div1 = document.getELemenById('div1');
// 移动已有节点。注意,这里是“移动”,并不是拷贝
var p2 = document.getElementById('p2')
div1.appendChild(p2)
</pre>
增加:
<pre>var div1 = document.getELemenById('div1');
//添加新节点
var p1 = document.createElement('p');
p1.innerHTML = '添加DOM节点';
div1.appendChild(p1);//将p1标签放在id为div1的下方;
</pre>
删除:
<pre>var div1 = document.getElementById('div1');
var child = div1.childNodes;
div1.removeChild(child[0]);
</pre>
2.2什么是事件绑定、事件冒泡?
答:普通的事件绑定写法如下:
<pre>var btn = document.getElmentById('btn1');
btn.addEventListener('click',function(event){
console.log('clicked');
})
</pre>
通用的事件绑定
<pre>function bindEvent(elem, type, fn){
elem.addEventListener(type,fn)
}
var a = document.getElementById('link1')
bindEvent('a','click',function(e){
e.preventDefault()//阻止默认行为
alert('clicked')
})
</pre>
事件冒泡是由最深的节点开始,然后逐步向上传播事件
<pre><body>
<div id="div1">
<p id="p1">激活</p>
<p id="p2">取消</p>
<p id="p3">取消</p>
<p id="p4">取消</p>
</div>
<div id="div2">
<p id="p5">取消</p>
<p id="p6">取消</p>
</div>
</body>
</pre>
对于以上HTML代码结构,点击p1时候进入激活状态,点击其它任何<p>都取消激活状态
<pre>function bindEvent(elem,type,fn){
elem.addEventListener(type,fn)
}
var body = document.body;
var p = document.getElementsByClassName('p')
bindEvent(body,'click',function(e){
//所有p标签的点击事件都会冒泡到body标签上,因此如果不使用stopPropagation的情况下会触发点击事件
alert("触发body点击事件")
})
var p1 = document.getElementById('p1')
bindEvent(p1, 'click', function (e) {
e.stopPropagation() // 阻止冒泡
alert('激活')
})
</pre>
2.3如何使用事件代理?有什么好处?
答:事件代理其实就是事件委托。就拿收快递这个例子打比方,如果有一天公司里面的三人都会收到快递,平常情况下都是一个一个去拿快递,这样的话就会麻烦很多。但是如果把这些快递都放到前台,由前台小姐姐分发快递,这样的话会大大提高效率。
在上面的例子中前台小姐姐实际上就起到了代理的作用,话说回来事件代理也是这样,因为事件存在冒泡机制,也就是事件会逐级向上传递信息,利用这个原理我们就可以做事件代理了。例如有这么一个场景:
<pre><ul id="ul1">
<li><a class='list'>开始</a></li>
<li><a class='list'>暂停</a></li>
<li><a class='list'>取消</a></li>
</ul>
</pre>
如果要获取a中的点击事件,这时候该怎么办?
一般情况下都会这么写
<pre><script>
var aNode = document.getElementsByClassName('list');
for(var i =0;i<aNode.length;i++){
aNode[i].addEventListener(function(e){
console.log(e);
})
}
</script>
</pre>
上面的写法会调用多次dom监听a标签,这样的话性能会浪费很多。如果使用事件代理,即通过事件冒泡的形式监听父元素的点击事件,改进之后的写法
<pre><script>
var ul1 = document.getElementById("ul1");
function bindEvent(elem,type,selector,fn){
if(fn===null){
var fn = selector;
selector = null;
}
elem.addEventListener(type,function(e){
var theEvent= e||window.event;//兼容火狐
var srcElement = theEvent.srcElement;//兼容ie
if(!srcElement){
srcElement = theEvent.target;
}
//代理存在时
if(selector){
if(srcElement.match(selector)){
fn.call(srcElement,theEvent)
}
}else{
fn(theEvent);
}
//无代理
});
}
var list = document.getElementsByClassName('list');
bindEvent(ul1,'click',list ,function(e){
console.log(e);
})
</script>
</pre>
这样写只会监听一个标签事件,会大大减少内存,提高浏览器性能。而且还能使代码变得更加简洁。
2.4手写XMLHttpRequest不借助任何框架
答:
<pre>var xhr = new XMLHttpRequest();//创建XMLHttpRequest对象
xhr.onreadystatechange = function(){
if(xhr.readyState===4&&xhr.status===200){
console.log(xhr.responseText);
}
}
xhr.open('GET',url);
xhr.send();
</pre>
状态码说明:
从上述代码克制,有两处状态码需要说明;分别是xhr.readyState和xhr.status;
xhr.readyState的状态码说明:
0-代理被创建,但尚未调用open()方法。
1-open()方法已经被调用。
2-send()方法已经被调用,并且头部和状态已经获得。
3-下载中,responseText返回部分数据
4-下载操作已完成
xhr.status状态码说明
-
200正常
-
3xx
-
301 永久重定向。如http://xxx.com这个 GET 请求(最后没有/),就会被301到http://xxx.com/(最后是/)
-
302 临时重定向。临时的,不是永久的
-
304 资源找到但是不符合请求条件,不会返回任何主体。如发送 GET 请求时,head 中有If-Modified-Since: xxx(要求返回更新时间是xxx时间之后的资源),如果此时服务器 端资源未更新,则会返回304,即不符合要求
-
404 找不到资源
-
5xx 服务器端出错了
网友评论