初级前端知识点整合-JS部分(持续更新中)

111 阅读15分钟

1.冒泡排序: 只需要比较n-1趟,所以循环次数为数组长度-1。

function BubbleSort(arr){
    for(var i=0;i<arr.length-1;i++){
        for(var j=0;j<arr.length-1;j++){
            if(arr[j+1]<arr[j]){
                var temp;
                temp = arr[j+1];
                arr[j+1] = arr[j];
                arr[j] = arr[j+1];
            }
        }
    }
    return arr;
}

2.快速排序: 利用二分法和递归实现快速排序。

function quickSort(arr){
    if(arr.length == 0){
        return [];
    }
    //利用Math.floor()方法向下取整找到中间位置
    var cIndex = Math.floor(arr.length / 2);
    //再用splice()方法将数组中间位置的元素取出来
    var c = arr.splice(cIndex,1);
    var l = [],r = [];
    for(var i = 0;i<arr.length;i++){
        if(arr[i]<c){
            l.push(arr[i]);
        }else{
            r.push(arr[i]);
        }
    }
    return quickSort(l).concat(c,quickSort(r));
}

3.JS基本规范: 不要在同一行声明多个变量,正确写法如下:

var a = 1,
    b = 2,
    c = 3;

建议使用对象字面量的写法替代new Array这种写法: var arr = [1,2,3,4]; 尽量不要使用全局变量; For循环和IF语句必须使用大括号; Switch语句必须带有Default分支; for-in 循环中的变量应该使用Var关键字限定作用域,避免作用域污染

4.JS的基本数据类型 undefined、Null、String、Boolean、Number

5.Js中数组的一些操作 <1>map():遍历数组,返回回调返回值组成的新数组

var arr = [1,2,3,4];
var arr = arr.map(function(item){
    return item * item;
})
console.log(arr);  //[1,4,9,16]

var users = [
 {name:"老王",age: 70},
 {name:"大王",age: 50},
 {name:"小王",age: 30}
];

var ages = users.map(function(user){
    return user.age;
})
console.log(ages);   //[70,50,30]

<2>forEach():遍历数组,但无法break,可以用try catch语句中的throw new Error()来停止

var users = [
 {name:"老王",age: 70},
 {name:"大王",age: 50},
 {name:"小王",age: 30}
];
var names = [];
users.forEach(function (item){
    names.push(item.name);
});
console.log(names);    //["老王", "大王", "小王"]

<3>filter():过滤

var arr = [1,2,3,4,5];
var newArr = arr.filter(function(item){
    return item>3;
});
console.log(newArr); //[4,5]

<4>some():有一项返回true,则整体都为true

var arr = [1,2,3,4,5];
var result = arr.some(function(item){
    return item>4;
});
console.log(result);   //true

<5>every():有一项返回false,则整体返回false

var arr = [1,2,3,4,5];
var result = arr.every(function(item){
    return item>6;
});
console.log(result);   //false

<6>join():通过指定连接符生成字符串

var arr = [1,2,3,4,5];
var result = arr.join("-=-");
console.log(result);   //1-=-2-=-3-=-4-=-5

<7>push()/pop():末尾推入和弹出,改变原数组, 返回推入/弹出项,会修改原数组

var arr = [1,2,3,4];
var result1 = arr.push(5);
console.log(result1)  //5
console.log(arr);     //[1,2,3,4,5]

var result2 = arr.pop();
console.log(result2);  //5
console.log(arr);      //[1,2,3,4]

<8>shift()/unshift(): shift()移除数组第一项,返回该值; unshift()添加一个元素进数组第一项,返回新数组长度

var arr = [1,2,3,4];
var result1 = arr.shift();
console.log(result1);   //1
console.log(arr);       //[2,3,4]

var result2 = arr.unshift(1);
console.log(result2);   //4
console.log(arr);       //[1,2,3,4]

<9>sort(fn)/reverse():排序数组和反转数组,会修改原数组

var arr = [1,6,3,5,4,2];
arr.sort(function(a,b){
    return a-b;        //a-b由小到大排序,b-a由大到小排序
});
console.log(arr);   //[1,2,3,4,5,6]

arr.reverse();
console.log(otherResult);  //[6,5,4,3,2,1]

<10>concat():连接数组,不影响原数组浅拷贝。如果参数不是数组,直接当成数组元素添加到数组中,并返回一个新的数组实例。

var arr1 = [1,2,3];
var arr2 = ["Sami","Nick","Michael"];
var result = arr1.concat(arr2);
console.log(arr1);       //[1,2,3]
console.log(arr2);       //["Sami","Nick","Michael"]
console.log(result);     //[1,2,3,"Sami","Nick","Michael"]

<11>slice(start,end):返回截断后的新数组,不改变原数组。第一个参数起始位置,第二个结束位置。截取的数组不包含结束位置上的元素。

var arr = [1,2,3,4,5];
var result = arr.slice(0,2);   
console.log(result);     //[1,2]
console.log(arr);        //[1,2,3,4,5]

<12>splice(start,number,value...):返回删除元素组成的数组,value 为插入项,改变原数组

var arr = [1,2,3,4,5];
var result = arr.splice(1,2);
console.log(arr);    //[1,4,5];
console.log(result); //[2,3];

<13>indexOf(value,fromIndex)、lastIndexOf(value,fromIndex): 查找数组项,返回对应的下标。indexOf从前往后查找,lastIndexOf从后往前查找。

var arr = [1,2,7,4,5,6,7];
var index = arr.indexOf(7);
console.log(index);   //2
var index = arr.indexOf(7,3);
console.log(index);   //6

var index = arr.lastIndexOf(7);
console.log(index);   //6
var index = arr.lastIndexOf(7,3);
console.log(index);   //2

6.事件侦听器通用对象:

var EventUtil = {
    addEvent:function(element,type,handler){
        if(element.addEventListener){
            element.addEventListener(type,handler,false);
        }else if(element.attachEvent){
            element.attachEvent("on" + type,handler);
        }else{
            element["on" + type] = handler;
        }
    },
    removeEvent:function(element,type,handler){
        if(element.removeEventListener){
            element.removeEventListener(type,handler,false);
        }else if(element.detachEvent){
            element.detachEvent("on" + type,handler);
        }else{
            element["on" + type] = null;
        }
    },
    getEvent:function(){
        return event ? event: window.event;
    },
    getTarget:function(event){
        return event.target || event.srcElement;
    },
    preventDefault:function(event){
        if(event.preventDefault){
            event.preventDefault();
        }else{
            event.returnValue = false;
        }
    },
    stopPropagation:function(event){
        if(event.stopPropagation){
            event.stopPropagation();
        }else{
            event.cancelBubble();
        }
    }
}

//调用方法
var btn = document.getElementById("myBtn");
var handler = function(){
    event = EventUtil.getEvent();
    EventUtil.preventDefault();
    alert("Clicked");
};
EventUtil.addEvent(btn,click,handler);

7.JS的内置对象: 数据封装对象:Object、Array、Boolean、Number、String; 其他对象:Math、Date、Function、Error、RegExp、Arguments

8.闭包:指有权访问另一个函数作用域中变量的函数。

function parentFunc(){
    var a = 1;
    function childFunc(){
        console.log(a);
    }
    return childFunc();
}

闭包的特征: <1>函数内再嵌套函数; <2>内部函数可以调用外部函数的参数和变量; <3>参数和变量不会被垃圾回收机制回收。

闭包的好处:能够实现封装和缓存。使用闭包主要是为了封装对象的私有属性和私有方法,闭包可以避免全局变量的污染。 闭包的缺点:闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄漏。

闭包经典问题:

function parentFunc(){
    var arr = [];
    for(var i = 0;i<5;i++){
        arr[i] = function (){
            return i;
        }
    }
    return arr;
}
console.log(parentFunc()[0]());  //5
console.log(parentFunc()[1]());  //5

这里就展现出了几个关键信息,首先分析一下代码:循环中创建了一个匿名函数并将其赋值给arr数组中对应索引的元素,匿名函数作用是返回i值。此时,arr数组中存放的是匿名函数,而匿名函数还没有执行。当调用parentFunc()函数时返回arr数组,再单独执行数组中的元素保存的匿名函数,此时循环已经执行完,所以i值为5。接下来再去调用其它数组元素中的匿名函数也样会获得数值5。 要解决这个闭包所产生的问题,有两种办法: <1>立即执行匿名函数

function parentFunc(){
    var arr = [];
    for(var i = 0;i<5;i++){
        arr[i] = (function (){
            return i;
        })();
    }
    return arr;
}
console.log(parentFunc());  //[0,1,2,3,4]

<2>使用let关键字声明变量:使用let声明变量会形成块级作用域

function parentFunc(){
    var arr = [];
    for(let i = 0;i<5;i++){
        arr[i] = function (){
            return i;
        };
    }
    return arr;
}
console.log(parentFunc()[0]());  //0

9.JS作用域:分为全局作用域和函数作用域 全局作用域,代码在程序中的任何地方都能访问,window对象的内置属性都拥有全局作用域; 函数作用域,在固定的代码片段才能访问。

10.作用域链:作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到window对象即被终止,作用域链向下访问变量是不被允许的。

11.原型和原型链: 每个对象都会在其内部初始化一个属性-prototype(原型)。当我们访问一个对象的属性时,如果这个对象的内部不存在这个属性,就会去prototype中查找这个属性,这个prototype又会有自己的prototype,于是就会像链条一样一直找下去形成原型链。 (因为所有的对象都是由Object对象继承而来,因此最终查找到Object的prototype结束)

12.组件化:利用组件化思想将多个页面都需要用的功能组件封装起来,提高代码复用性,降低耦合性,增强可维护性和可读性。

13.模块化:主要用途是封装对象 模块化的优点:避免全局变量变量污染,命名冲突;提高代码复用率;提高了可维护性。 最常用的模块化封装对象的方法是:构造函数模式+原型模式。 构造函数内写属性,原型中放方法和重写构造函数指针。

function Person(name,age){
    this.name = name;
    this.age = age;
}
Person.prototype = {
    constructor: Person,
    sayName: function(){
        alert(this.name);
    }
}

var person1 = new Person("老王", 70);
var person2 = new Person("小王", 20);

alert(person1.name === person2.name);    //false,构造函数内属性不公用
alert(person1.sayName === person2.sayName);  //true,原型中的方法共用

组合使用构造函数模式和原型模式封装对象的好处在于,每个新建的实例都拥有自己的属性,然后共同享有原型中的方法,不用每次创建新实例都重新创建同样的方法。

14.继承:实现继承的常用方法是原型链+借用构造函数。 原型链实现对原型属性和方法的继承,借用构造函数实现对实例属性的继承。

function SuperType(name){
    this.name = name;
}
SuperType.prototype.sayName = function(){
    alert(this.name);
}

function SubType(age){
    //继承属性
    SuperType.call(this,name);
    this.age = age;
}

//继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
    alert(this.age);
}

var p1 = new SubType("老王", 70);
var p2 = new SubType("小王", 20);
p1.sayName(); //老王
p2.sayName(); //小王
p1.sayAge();  //70
p2.sayAge();  //20

15.Ajax:ajax的核心是XMLHttpRequest(XHR) <1>如何创建一个ajax

//get方法
var xhr = new XMLHttpRequest();
xhr.open("get","example.php",true);  //发送的请求类型、请求的URL、是否异步发送请求
xhr.send(null);

xhr.onreadystatechange= function(){
    if(xhr.readyState === 4){
        if(xhr.status === 200){
            success(xhr.responseText);
        }else{
            console.log(xhr.status);
        }
    }
}

//post方法
var data = new FormData(document.forms[0]);
xhr.open("post","example.php",true);
xhr.send(data);

<2>同步和异步的区别: 同步:用户请求,等待,响应,刷新页面展示内容再操作; 异步:用户请求的同时可继续对页面操作,响应完成不刷新页面展示新内容。 <3> Ajax优点: 异步请求响应快,用户体验好;页面无刷新、数据局部更新;按需取数据,减少了冗余请求和服务器的负担; Ajax缺点: 异步回调问题、this指向问题、路由跳转back问题;对搜索引擎的支持比较弱,对于一些手机还不是很好的支持 <4>post一般用于修改服务器上的资源,对发送的数据没有限制;而get一般用于请求获取数据。

16.事件代理: 事件代理又称之为事件委托,是绑定事件的常用技巧。即把原本需要绑定的事件委托给父元素,让父元素担当事件监听的职务。 事件代理的原理是DOM元素的事件冒泡。 使用事件代理的好处是可以提高性能,节省内存占用,减少事件注册。可以实现当新增子对象时无需再对其绑定。 比如在table上代理td的click事件。

17.this对象: ·this总是指向函数的直接调用者(而非间接调用者); ·如果有new关键字,this指向new出来的新对象 ·在事件中,this指向触发这个事件的对象,但IE中的attachEvent中的this指向window对象。

18.事件模型: ·冒泡型事件:当你使用事件冒泡时,子元素先触发,父元素后触发; ·捕获型事件:当你使用事件捕获时,父元素先触发,子元素后触发; ·DOM事件流:同时支持两种事件模型,冒泡型事件和捕获型事件; ·阻止冒泡:阻止事件冒泡。在w3c中使用stopPropagation()方法,在IE中使用cancelBubble = true; ·阻止捕获:阻止事件的默认行为。在w3c中使用preventDefault()方法,在IE中使用returnValue = false。

19.XML和JSON的区别: ·JSON相对XML,数据体积更小,传递速度更快些; ·JSON与JS的交互更方便,更容易解析处理,更好的数据交互; ·JSON对数据的描述性比XML较差; ·JSON的传输速度远远快于XML。

20.JS定义对象的方法: ·对象字面量: var obj = {}; ·构造函数: var obj = new Object(); ·Object.create: var obj = Object.create(object.prototype);

21.Promise: ·promise用来进行延迟和异步计算 ·promise的四种状态:pending(初始状态)、fulfilled(成功的操作)、rejected(失败的操作)、settled(promise已被fulfilled或者rejected) ·Promise的构造函数:

var promise = new Promise(function(resolve,reject){
    if(...){
        resolve(result);
    }else{
        reject(errMessage);
    }
});

·Promise的then方法:

promise.then(onFulfilled,onRejected);

这两个参数分别对应resolve和reject传过来的结果。 ·引用廖雪峰老师的两个例子给大家理解一下promise(稍作修改):

new Promise(function(resolve,reject){
    console.log("promise start...");
    var timeout = Math.random()*2;   //0-2
    console.log("set timeout to:" + timeout + "seconds");
    setTimeout(function(){
        if(timeout < 1){
            console.log("call resolve()...");
            resolve("200 ok");
        }else{
            console.log("call reject()...");
            reject("timeout in" + timeout + "seconds");
        }
    },timeout * 1000);
}).then(function(resolve){
    console.log("Done:" + resolve);
}).catch(function(reject){
    console.log("Failed:" + reject)
});

可见Promise最大的好处是在异步执行的流程中,把执行代码和处理结果的代码清晰地分离了。

Promise还可以做更多的事情,比如,有若干个异步任务,需要先做任务1,如果成功后再做任务2,任何任务失败则不再继续并执行错误处理函数。 要串行执行这样的异步任务,不用Promise需要写一层一层的嵌套代码。有了Promise,我们只需要简单地写:

job1.then(job2).then(job3).catch(handleError);
function multi(input){
    return new Promise(function(resolve,reject){
        console.log('计算 ' + input + ' x ' + input + '...');
        setTimeout(resolve,500, input * input);
    });
}
function add(input){
    return new Promise(function(resolve,reject){
        console.log('计算 ' + input + ' + ' + input + '...');
        setTimeout(resolve,500,input + input);
    });
}
var p = new Promise(function(resolve,reject){
    console.log("start ....");
    resolve(33);
});
p.then(multi)
 .then(add)
 .then(multi)
 .then(add)
 .then(function(result){
    console.log("结果:" + result);
 });

22.eval()的作用: 把对应的字符串解析成可执行的JS代码并运行; 应该避免使用eval(),不安全且非常耗性能。

23.Null和Undefined的区别: undefined表示声明的变量未赋值,而null表示声明变量的值为空值; 两者相比较时要使用===,因为==无法区分。

24.["1", "2", "3"].map(parseInt) 答案是多少? [1,NaN.NaN]。因为parseInt的参数时(val,radix),radix表示基数(多少进制),而map的参数是(function(currentValue,index,arr),thisIndex)。所以map传了三个参数给parseInt,radix对应index不合法导致解析失败。

25.JSON与字符串的转换: ·字符串转换为JSON

var obj = eval('(' + str + ')');
var obj = str.parseJSON();
var obj = JSON.parse(str);  //最常用

·JSON转换为字符串

var str = obj.toJSONString();
var str = JSON.stringify(obj);

26.attribute和property的区别: attribute是DOM元素再文档中作为html标签拥有的属性; property是DOM元素再JS中作为对象所拥有的属性;

27.如何判断一个对象是否为数组:

function isArray(obj){
    if(typeof obj === 'object'){
        return Object.prototype.toString.call(obj) === '[Object Array]';
    }
    return false;
}

28.event loop(事件循环): ·JS是一门单线程的非阻塞的脚本语言,单线程意味着JS在执行代码的任何时候,都只有一个主线程来处理所有任务。

·事件循环流程图:

event_loop

同步和异步任务分别进入不同的执行场所,同步任务进入主线程,异步任务进入Event table并注册函数; 当指定的事件完成时,Event table会将这个函数移入事件队列Event queue中; 主线程的任务执行完毕为空,会去Event queue读取对应的函数,进入主线程中执行; 上诉的过程不断重复,也就是我们说的事件循环Event loop。

·举个例子说明他们的执行顺序:

console.log("1");
setTimeout(function(){
    console.log("2");
},0);
console.log("3");        //输出结果为:1,3,2

因为setTimeout是异步任务,其他两个console同步任务按顺序执行,所以setTimeout最后输出。

·微任务和宏任务,结合例子说明:

 console.log('1');
 setTimeout(() => {
 	console.log('2')
 }, 1000);
 new Promise((resolve, reject) => {
 	setTimeout(() => {
 		console.log('3');
 	}, 0);
 	console.log('4');
 	resolve();
 	console.log('5');
 }).then(() => {
 	console.log('6');
 });
 console.log('7');     //执行结果为1,4,5,7,6,3,2

先来说明下什么是微任务和宏任务,他们都是异步的任务,且都属于队列,区别在于微任务先于宏任务执行。(有一点歧义,之后再说) 宏任务包含有:setTimeout、setInterval、setImmediate、I/O、UI rendering; 微任务包含有:process.nextTick()、promise.then、MutationObserver; 补充一点 new promise会同步执行。 在执行到new Promise的时候会立马新建一个promise对象并立即执行。所以会输出 1,4,5,7,而then则会在Event Table中注册成回调函数并放在微任务队列中,而两个setTimeout(输出3)和setTimeout(输出2,1s后完成)会被先后注册成回调函数并放在宏任务队列中。

·复杂测试题理解程度(分清宏任务和微任务,画出队列执行顺序理解):

console.log(1)
process.nextTick(() => {
  console.log(8)
  setTimeout(() => {
    console.log(9)
  })
})
setTimeout(() => {
  console.log(2)
  new Promise(() => {
    console.log(11)
  })
})
let promise = new Promise((resolve,reject) => {
  setTimeout(() => {
    console.log(10)
  })
  resolve()
  console.log(4)
})
fn()
console.log(3)
promise.then(() => {
  console.log(12)
})
function fn(){
  console.log(6)
}                  //输出结果:1,4,6,3,8,12,2,11,10,9

按顺序执行,同步任务先执行,再到微任务和宏任务,其内部包含的亦是如此。(不同评论留言)

这些内容来自:juejin.cn/post/684490…

29.附加多一题promise+event loop的题目:贼有意思

new Promise((resolve,reject)=>{
    console.log("promise1")
    resolve()
}).then(()=>{
    console.log("then11")
    new Promise((resolve,reject)=>{
        console.log("promise2")
        resolve()
    }).then(()=>{
        console.log("then21")
    }).then(()=>{
        console.log("then23")
    })
}).then(()=>{
    console.log("then12")
})
new Promise((resolve,reject)=>{
    console.log("promise3")
    resolve()
}).then(()=>{
    console.log("then31")
})            

//输出结果:[promise1,promise3,then11,promise2,then31,then21,then12,then23]

不明白的来评论问我!链式调用插队问题。

30.上瘾了,加多一题async await + event loop + promise

async function async1() {
    console.log("async1 start");
    await  async2();
    console.log("async1 end");
}

async  function async2() {
    console.log( 'async2');
}

console.log("script start");

setTimeout(function () {
    console.log("settimeout");
},0);

async1();

new Promise(function (resolve) {
    console.log("promise1");
    resolve();
}).then(function () {
    console.log("promise2");
});
console.log('script end'); 

async/await仅仅影响的是函数内的执行,而不会影响到函数体外的执行顺序。也就是说async1()并不会阻塞后续程序的执行,await async2()相当于一个Promise,console.log("async1 end");相当于前方Promise的then之后执行的函数。

最终输出结果:[script start,async1 start,async2,promise1,script end,async1 end,promise2,settimeout]