javascript基本知识集合

一.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.什么是闭包?闭包有什么作用?

答:闭包就是能够读取其它函数内部变量的函数。最简单的用法是

function test(){
var name = "Mozilla";
function closure(){
console.log(name);
}
closure();
}
test();

在以上代码中test()创建了一个局部变量name和一个名为closure的函数,closure是test函数里面的一个内部函数;当执行test时,同时会执行内部函数closure,就会打印出test作用域中的name的值;还有一个比较经典的例子

for(var i = 0;i<6;i++){
setTimeout(function(){
console.log(i);//6,6,6,6,6,6
},0)
}

在上面代码中打印出6个6;如果想要输出0,1,2,3,4,5,则需要使用闭包来实现(es6中可通过块作用域实现)

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)
})
}

闭包主要有两个作用场景:

  • 函数作为返回值

  • 函数作为参数传递

//函数作为返回值
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

7.如何理解任务队列?

答:因为javascript是单线程,所以在线程上的任务都得排队,也就是说只有等到前面一个任务执行完成时,才会执行后面的任务。这样的话会造成一个问题,即当前面一个任务执行时间很长的时候,后面的任务就必须得等到前面的任务执行完之后才会执行,会造成空白页的现象。

为了解决这个问题,javascript设计者就将任务分为2种,一种是同步任务,另外一种是异步任务。同步任务就是在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入“任务队列”的任务,只有“任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。异步任务包括宏任务微任务

8.什么是异步任务?异步有哪几种形式?

答:异步是需要等到主线程任务执行完之后才执行的任务。异步有以下四种形式:

1.setTimeout/setInterval;

2.ajax;

3.dom事件绑定

4.图片引入

setTimeout代码示例

console.log('第一步');
setTimeout(function(){
console.log('第二步');
},0)
console.log('第三步');

//结果输出
//‘第一步’
//‘第三步’
//‘第二步’

ajax代码操作示例

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();

dom事件绑定示例



标签引入示例

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')

9.es6的箭头函数相比于函数有什么优点,哪些场景需要用到?

答:箭头函数和之前的函数相比,节省了一些代码,增加了工作效率;箭头函数主要是用于解决this的指向问题。例如

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'});

10.es6中的module有什么作用?

答:ES6中模块化语法更加简洁,如果只是输出一个唯一对象,可以通过

export default {}表示

例如

index.js

export default{
a:100
}

test.js

import index from './index.js' //假如index.js和test.js在同一文件夹中
console.log(index.a);//100

如果想要输出多个对象,就不需要default了,在引入的时候用{}表示,例如:

index.js

export function fn1(){
console.log('fn1')
}
export function fn2(){
console.log('fn2')
}

test.js

import {fn1} from './index.js'
fn1();//fn1

11.ES6 class和普通构造函数的区别?

答:构造函数通常的写法

//创建一个构造函数Animal
function Animal(type){
this.type = type;
}
//构造函数的原型
Animal.prototype = {
canEat:function(){
....
}
}
//创建一个实例
var theAnimal = new Animal('dog');
theAnimal.canEat();

class的写法

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())

由以上两个代码可知es6中的类是由constructor来创建一个构造函数,然后就直接添加函数,在这里是不存在原型的。

在继承方面,class的使用无疑会更加简单,例如:

JS构造函数实现继承:

// 动物
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()

es6 class实现继承

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()

12.ES6中的set的应用场景是什么?

答:

set主要应用于以下几个场景:

1.数组去重;

var arr = [1,2,1,1,1,2,3];
var unique = [...new Set(arr)];
console.log(unique)//1,2,3

2.两个数组并集

3.两个数组之间的交集

4.两个数组之间的差集

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

2.浏览器相关的api

2.1 使用原生js写出dom的增删查改

答:

查看:相关api有getElementsByTagName,getElementById,getElementsByClassName,querySelectorAll等等。

// 通过 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;

修改:

var div1 = document.getELemenById('div1');
// 移动已有节点。注意,这里是“移动”,并不是拷贝
var p2 = document.getElementById('p2')
div1.appendChild(p2)

增加:

var div1 = document.getELemenById('div1');
//添加新节点
var p1 = document.createElement('p');
p1.innerHTML = '添加DOM节点';
div1.appendChild(p1);//将p1标签放在id为div1的下方;

删除:

var div1 = document.getElementById('div1');
var child = div1.childNodes;
div1.removeChild(child[0]);

2.2什么是事件绑定、事件冒泡?

答:普通的事件绑定写法如下:

var btn = document.getElmentById('btn1');
btn.addEventListener('click',function(event){
console.log('clicked');
})

通用的事件绑定

function bindEvent(elem, type, fn){
elem.addEventListener(type,fn)
}
var a = document.getElementById('link1')
bindEvent('a','click',function(e){
e.preventDefault()//阻止默认行为
alert('clicked')
})

事件冒泡是由最深的节点开始,然后逐步向上传播事件



激活


取消


取消


取消




取消


取消




对于以上HTML代码结构,点击p1时候进入激活状态,点击其它任何

都取消激活状态

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('激活')
})

2.3如何使用事件代理?有什么好处?

答:事件代理其实就是事件委托。就拿收快递这个例子打比方,如果有一天公司里面的三人都会收到快递,平常情况下都是一个一个去拿快递,这样的话就会麻烦很多。但是如果把这些快递都放到前台,由前台小姐姐分发快递,这样的话会大大提高效率。

在上面的例子中前台小姐姐实际上就起到了代理的作用,话说回来事件代理也是这样,因为事件存在冒泡机制,也就是事件会逐级向上传递信息,利用这个原理我们就可以做事件代理了。例如有这么一个场景:


如果要获取a中的点击事件,这时候该怎么办?

一般情况下都会这么写


上面的写法会调用多次dom监听a标签,这样的话性能会浪费很多。如果使用事件代理,即通过事件冒泡的形式监听父元素的点击事件,改进之后的写法


这样写只会监听一个标签事件,会大大减少内存,提高浏览器性能。而且还能使代码变得更加简洁。

2.4手写XMLHttpRequest不借助任何框架

答:

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();

状态码说明:

从上述代码克制,有两处状态码需要说明;分别是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 服务器端出错了

你可能感兴趣的:(javascript基本知识集合)