JS校招笔试面试编程题
一、排序
1、快速排序
快排基本思想:
(1)从数列中取出一个数作为基准数
(2)分区过程,将比这个数大的数全放在它的右边,小于或等于它的数全放在左边
(3)再对左右区间重复第二步,直到各区间只有一个数
function quickSort(arr) {
if(arr.length <= 1) return arr;
const middleIndex = Math.floor(arr.length / 2);
const middle = arr.splice(middleIndex,1)[0];
const left = [], right = [];
arr.foreach(item => {
if(item < middle) {
left.push(item);
}else {
right.push(item);
}
});
return quickSort(left).concat(middle,quickSort(right));
}
2、冒泡排序
function bubbleSort(arr) {
for(var i = 0; i<arr.length - 1; i++) {
for(var j = 0; j<arr.length - 1 - i; j++) {
if(arr[j] > arr[j + 1]) {
var temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
3、插入排序
插入排序基本思想:
(1)从第一个元素开始,该元素可以认为已被排序
(2)取出下一个元素,在已排序的元素序列中从后向前扫描
(3)如果该元素(已排序)大于新元素,将该元素移到下一位置
(4)重复步骤(3),直到找到已排序的元素小于或等于新元素的位置
(5)将新元素插入到下一位置中
(6)重复步骤(2)~ (5)
function insertSort(arr) {
var preIndex, current;
for(var i = 1; i < arr.length; i++) {
preIndex = i - 1;
current = arr[i];
while( preIndex >= 0 && current < preIndex) {
arr[preIndex + 1] = arr[preIndex]'
preIndex--;
}
arr[preIndex + 1] = current;
}
return arr;
}
4、选择排序
选择排序思想:
首先在非排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后再从剩余未排序元素中寻找最小(大)元素,然后房嫂已排序序列的末尾,重复直至排序完成。
function selectSort(arr) {
var minIndex,temp;
for(var i = 0; i < arr.length; i++) {
minIndex = i;
for(var j = i + 1; j < arr.length; j++) {
if(arr[j] < arr[minIndex]) {
minIndex = j;
}
}
temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
return arr;
}
5、希尔排序
思想:(先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序)
选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;
按增量序列个数 k,对序列进行 k 趟排序;
每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
function shellSort(arr) {
var len = arr.length,temp,gap = 1;
while (gap < len / 5) { // 动态定义间隔序列
gap = gap * 5 + 1;
}
for (gap; gap > 0; gap = Math.floor(gap / 5)) {
for (var i = gap; i < len; i++) {
temp = arr[i];
for (var j = i - gap; j >= 0 && arr[j] > temp; j -= gap) {
arr[j + gap] = arr[j];
}
arr[j + gap] = temp;
}
}
return arr;
}
6、归并排序
归并排序思想:
(1) 把长度为n的输入序列分成两个长度为n/2的子序列
(2)对这两个子序列分别采用归并排序
(3) 将两个排序好的子序列合并成一个最终的排序序列
function mergeSort(arr) {
if(arr.length < 2) return arr;
var middle = Math.floor(arr.length / 2),
left = arr.slice(0, middle);
right = arr.slice(middle);
return merge(mergeSort(left), mergeSort(right));
}
function merge(left, right) {
var result = [];
while (left.length && right.length) {
left[0] < right[0] ? result.push(left.shift()) : result.push(right.shift());
}
return result.concat(left, right);
}
7、二分查找(折半查找)
(1)递归方法
function binarySearch(data, dest, start, end) {
var start = start || 0,
end = end || data.length - 1,
middle = Math.floor((start + end) / 2);
if(data[middle] == dest) return middle;
if(dest < data[middle]) {
return binarySearch(data, dest, 0, middle - 1);
}else {
return binarySearch(data, dest, middle + 1, end);
}
return false;
}
(2)非递归方法
function binarySearch(data, dest) {
var i = 0;
while(i < data.length - 1) {
var middle = Math.floor((data.length + 1) / 2);
if(data[middle] == dest) return middle;
if(dest > data[middle]) {
i = middle + 1;
}else {
i = middle - 1;
}
}
return false;
}
二、数
(一)自然数中出现过多少次数字几问题
使用js,返回1到999所有自然数中一共出现过多少次“1”,如1到21一共出现过13次“1”
思路:将1到999所有的自然数转换为类似001,012,311这样的长度的字符串,然后利用for循环求得1的个数即可
function numStrProcess(num,length) {
var numStr = num.toString();
if(numStr.length >= length) {
return numStr;
}
for(var i = 0; i < length - numStr.length; i++) {
numStr = "0" + numStr;
}
return numStr;
}
var result = 0;
for(var j = 1; j <= 999; j++) {
var numStr = numStrProcess(j, 3);
for(var k = 0; k < numStr.length; k++) {
if(numStr[k] == 1) {
result += 1;
}
}
}
console.log(result);
(二)回文数判断
判断一个整数是否是回文数
特殊情况:
(1)0-9的数字都可以成为回文数
(2)不等于0且尾数是0的数字都不是回文数
(3)负数都不是回文数
js回文数的四种判断方法
(三)判断一组数字是否连续
思路:判断相邻数字之间是否相差1,相差1则连续并将其加入同一数组。
function contiArr(arr) {
var result = [],temp = [];
arr.sort(function(a, b) {
return a - b;
}).concat(Infinity).reduce(function(a, b) {
temp.push(a);
if(b - a > 1) {
result.push(temp);
temp = [];
}
return b;
});
return result;
}
(四)不借助临时变量,进行两个整数的交换
//不借助临时变量,进行两个整数的交换(可用ES6)
//方法一:使用加减法
var a = 6;
var b = 2;
b = a - b; //b = 4
a = a - b; //a = 2
b = a + b; //b = 6
console.log(a,b);
//方法二:异或运算(^)
//将两个数转化为二进制数,如果对应位相等则为0,不相等则为1
let a = 6, b = 2;
a = a ^ b = 0110 ^ 0010 = 0100; // a = 4
b = a ^ b = 0100 ^ 0010 = 0110; // b = 6
a = a ^ b = 0100 ^ 0110 = 0010; // a = 2
//方法三:ES6解构赋值
//通过解构赋值, 可以将属性/值从对象/数组中取出,赋值给其他变量。
let a = 6, b = 2;
[a,b] = [b,a];
console.log(a,b); // 2 6
(五)实现九九乘法表
for(var i = 1; i <= 9; i++) {
for(var j = 1; j <= i; j++) {
var desc = j + "*" + i + "=" + j*i + "";
document.write(desc);
}
document.write("");
}
三、数组
(一)数组扁平化与数组去重(重点)
1、数组扁平化(多维数组=>一维数组)
(1)reduce
遍历每一项,若值为数组则递归遍历,否则concat
function flatten(arr) {
return arr.reduce((result, item) => {
return result.concat(Array.isArray(item) ? flatten(item) : item;
}, []);
}
注意:reduce接收一个函数作累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
reduce包含两个参数:回调函数和传给total的初始值。
arr.reduce((total, item) => {
return total + item;
}, 0);
(2) toString + split
function flatten(arr) {
return arr.toString().split(',').map(function(item) {
return Number(item)
})
}
注意:split分割后形成的数组的每一项值为字符串,所需用一个map方法遍历数组将其每一项转换为数值型。
(3)join + split
function flatten(arr) {
return arr.join(',').split(',').map(function(item) {
return parseInt(item)
})
}
(4)递归
递归遍历每一项,若为数组继续遍历,否则concat
function flatten(arr) {
var res;
arr.map(item => {
if(Array.isArray(item)) {
res = res.concat(flatten(item));
}else {
res.push(item);
}
});
return res;
}
(5)扩展运算符
ES6的扩展运算符能将二维数组变为一维数组
function flatten(arr) {
while(arr.some(item => Array.isArray(item)) {
arr = [].concat(...arr);
}
return arr;
}
2、去除数组中的重复元素
(1)使用空对象比较
function deRepeat() {
var obj = {};
var newArr = [], index = 0;
for(var i = 0; i < arr.length; i++) {
if(obj[arr[i] == undefined) {
obj[arr[i]] = 1;
newArr[index++] = arr[i];
}else if(obj[arr[i]] == 1){
continue;
}
}
return arr;
}
(2)不添加新数组的比较
function deRepeat(arr) {
for(var i = 0; i < arr.length; i++) {
for(var j = i + 1; j < arr.length; j++) {
if(arr[i] == arr[j]) {
arr.splice(j, 1);
j--;
}
}
}
return arr;
}
(3)使用indexOf()
//法一
function deRepeat(arr) {
var arr = [], newArr = [];
for(var i = 0; i < arr.length; i++) {
if(newArr.indexOf(arr[i]) == -1) {
newArr.push(arr[i]);
}
}
return newArr;
}
//法二
function deRepeat(arr) {
arr.sort();
var newArr[arr[0]];
for(var i = 1; i < arr.length; i++) {
if(arr[i] !== newArr[newArr.length - 1]) {
newArr.push(arr[i]);
}
}
}
(4)filter() + indexOf()
arr.filter((item, index) => {
return index == arr.indexOf(item)
})
3、编写一个程序将数组扁平化去除其中重复部分数据,最终得到一个升序且不重复的数组
法一:
Array.from(new Set(arr.flat(Infinity))).sort((a,b) => {
return a - b
})
法二:
Array.from(new Set(arr.toString().split(','))).sort((a,b) => {
return a - b
})
法三:
[...new Set(arr.toString().split(','))].sort((a,b) => {
return a - b
})
法四:
[...new Set(arr.flat(Infinity))].sort((a,b) => {
return a - b
})
(二)深拷贝与浅拷贝(重点)
1、区别
- 浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。
- 深拷贝会另外创造一个一模一样的对象,新对象与原对象不共享内存,修改新对象不会改到原对象。
2、实现深拷贝
2.1 递归实现拷贝
2.2 Object.create()
2.3 JSON.parse(JSON.stringify())
2.4 jQuery提供的$.extend
2.5 函数库lodash提供的cloneDeep()
3、实现浅拷贝
3.1 Object.assign()
3.2 Array.prototype.concat()
3.3 Array.prototype.slice()
(三)任意位置插入元素不改变数组
function insert(arr, item, index) {
var newArr = [];
for(var i = 0;i < arr.length; i++){
newArr.push(arr[i]);
}
newArr.splice(index, 0, item);
return newArr;
}
(四)实现随机选取10–100之间的10个数字,存入一个数组,并排序。
var array = [];
function getRandom(start,end){
var transition = end - start + 1; //加1是为了能取到100
var result = Math.floor(Math.random() * transition + start); //[0,90]+10
return result;
}
for(var i = 0; i < 10; i++){
array.push(getRandom(10,100));
}
array.sort(function(a,b){
return a > b;
})
console.log(array);
(五)数组中的两个数相加等于目标数,返回他们在数组中的位置。
function func(arr, target) {
var obj = {};
for(var i = 0; i < arr.length; i++) {
var item = arr[i];
if(obj[item] === undefined) {
var x = target - item;
obj[x] = i;
}else {
return [obj[item],i];
}
}
}
四、字符串
(一)将字符串转换为驼峰格式
function cssStyle2DomStyle(sName) {
let arr = sName.split('-');
arr[0] != "" || arr.splice(0,1);
for(let i = 1;i < arr.length;i++){
arr[i] = arr[i].charAt(0).toUpperCase() + arr[i].slice(1);
}
return arr.join("");
}
(二)字符串反转
法一:split(),reverse(),join()
var str = "";
var result = str.split('').reverse().join('');
console.log(result);
法二:charAt()
var str = "";
var result = "";
for(var i = str.length - 1; i >= 0; i--) {
result = str.charAt(i);
}
console.log(result);
(三)清除字符串的空格
(1)replace正则匹配方法
去除字符串内所有的空格:str = str.replace(/\s*/g,"");
去除字符串内两头的空格:str = str.replace(/^\s*|\s*$/g,"");
去除字符串内左侧的空格:str = str.replace(/^\s*/,"");
去除字符串内右侧的空格:str = str.replace(/(\s*$)/g,"");
(2).trim()
str.trim() 只能去除字符串两端的空格,不能去除中间的空格
str.trimLeft() 只能去除字符串最左边的空格
str.trimRight() 只能去除字符串最右边的空格
(3) JQ方法:
.
t
r
i
m
(
s
t
r
)
方法
.trim(str)方法
.trim(str)方法 .trim() 函数用于去除字符串两端的空白字符,无法去除中间的空格。
(四)纯数字字符串添加千分符
function toThousands(str){
var result;
var arr = [],newArr = [];
var counter = 0;
arr = str.split('');
for(var i = arr.length - 1;i >= 0; i--){
counter++;
newArr.unshift(arr[i]);
if(!(counter % 3) && i != 0){
newArr.unshift(',');
}
}
result = newArr.join('').toString();
return result;
}
(五)统计一个字符串中出现最多的字符,并统计出现次数
思路:
(1)利用charAt()遍历字符串,将每个字符都存储进对象中,若对象没有该属性就赋值为1,有就+1。
(2)遍历对象得到最大值和出现最多次的字符,考虑存在出现次数一样多的字符,所以maxKey为数组。遍历过程中,把字符串中的每个字符作为对象的属性存储在对象中,对应的属性值是该字符出现的次数。
function getMaxObj(str) {
if(str.length == 1) return str;
var obj = {};
for(var i = 0; i < str.length; i++) {
if(!obj[str.charAt(i)]) obj[str.charAt(i)] = 1;
else obj[str.charAt(i)] += 1;
}
var maxObj = {
maxKey: [], //出现最多的字符
maxValue: 0 //出现最多的次数
};
for(var j in obj) {
if(obj[j] > maxObj.maxValue) {
maxObj.maxValue = obj[j];
maxObj.maxKey = j;
}else if(obj[j] === maxObj.maxValue) {
maxObj.maxKey.push(j);
}
}
return maxObj;
}
(六)获取URL中的参数
(七)模板占位符替换功能的实现
实现一个render(template,context)方法,将template中的占位符用context填充。
let template = '我叫{{name}},今年{{age}}岁。'
let context = {
name:'zfr',
age:21
}
function render(template,context){
Object.keys(context).forEach(key => {
template = template.replace(new RegExp(`{{${key}}}`,'g'),context[key])
})
return template
}
五、函数
(一)函数柯里化
(二)防抖与节流(重点)
(三)闭包(重点)
闭包的理解、优点、缺点以及使用场景。
(四)用setTimeout实现setInterval
function newInterval(func, millisecond, count) {
function interval() {
if(typeof count === 'undefined' || count --> 0) {
setTimeout(interval,millisecond);
try {
func();
}catch(e) {
count = 0;
throw e.toString();
}
}
}
setTimeout(interval,millisecond);
}
(五)封装一个使localStorage具有过期机制的函数
yunchong_zhao:封装设置过期时间的localStorage函数
(六)this指向问题
1、普通函数调用,指向windows
windows.value = 100;
function getValue() {
console.log(this.value);
}
getValue(); //输出为100,此时this指向windows
2、对象的方法调用,指向对象
var obj = {
value: 100,
getValue: function() {
console.log(this.value); //输出为100,此时this指向obj
}
}
3、构造函数方法调用,指向构造函数实例出来的对象
function main(val) {
this.value = val;
}
main.prototype.getValue = function() {
console.log(this.value);
}
var fun = new main(100);
fun.getValue();
fun.value; //输出为3,此时this指向main的实例对象fun
4、call、apply、bind可以自定义this指向第一个参数
function getValue() {
console.log(this.value);
}
var obj = {
value: 100
}
getValue.call(obj); //输出为100,此时this指向了obj对象
六、对象与继承
(一)创建子类Child,使用原型和构造函数的方式继承父类People的方法,并调用say函数说出姓名和年龄。
(二)原型和原型链(重点)
1、如何理解JS中的原型链
(1)每个构造函数都有一个原型对象
(2)每个原型对象都有一个指向构造函数的指针
(3)每个实例函数都有一个指向原型对象的指针
(4)查找方式是一层一层查找,直至顶层。
Object.prototype
2、JS中执行对象查找时,永远不会去查找原型的函数?
Object.hasOwnProperty(proName):是用来判断一个对象是否有你给出名称的属性。
注意:此方法无法检查该对象的原型链中是否具有该属性,该属性必须是对象本身的一个成员。
(三)继承方法
1.原型链继承
2.构造函数继承
3.实例继承
4.组合继承
5.拷贝继承
6.寄生组合继承
(四)new操作符
(1)创建一个空对象,并且this变量引用该对象,同时还继承了该函数的原型。
var obj = {}
(2)属性和方法被加入到this引用的对象中。
obj.proto = Function.prototype
(3)新创建的对象由this所引用,并且最后隐式的返回this。
Function.call(obj)
- 手写实现new操作
function _new(fn, ...arg) {
const obj = Object.create(fn,prototype);
const ret = fn.apply(obj, arg);
return ret instanceof Object ? ret : obj;
}
(五)intanceof 操作符的实现原理及实现
- instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。
function instanceOfDemo(left, right) {
// 获取对象的原型
let proto = Object.getPrototypeOf(left);
// 获取构造函数的prototype对象
let prototype = right.prototype;
// 判断构造函数的prototype对象是否在对象的原型链上
while(true) {
if (!proto) return false;
if (proto === prototype) return true;
// 如果没有找到,就继续从其原型上找,Object.getPrototypeOf方法用来获取指定对象的原型
proto = Object.getPrototypeOf(proto);
}
}
七、栈与队列
(一)双栈实现队列
实现思路一:
准备两个栈用于实现队列:inStack和outStack
当有新元素入队时:将其压入inStack中
当需要出队时:
当outStack为空时:
将inStack中的元素逐一弹出并压入outStack中
将outStack的栈顶元素弹出
当outStack不为空时:
直接将outStack的栈顶元素弹出
var inslack=[];//入队数组
var outslack=[];//出队数组
var i; //计数器
var length=5; //假定入队元素总个数为5
console.log("入队");
for(i=0;i<=length-1;i++)
{
inslack.push(i);
console.log(`inslack`);
} //进行入队操作
for(i=length-1;i>=0;i--)
{
outslack.push(inslack(i));
inslack.pop();
console.log(`outslack`);
} //将inslack数组中的数组放入outslack(从栈顶将数组元素转移)
console.log("出队");
for(i=length-1;i>=0;i--)
{
outslack.pop();
console.log(`outslack`);
} //用outslack模拟从栈顶出队
实现思路二:
栈:先进后出,队列:先进先出
如果转化:
1.将内容先push进一个栈inStack,
2.判断outStack是否为空,空:将栈inStack中的元素pop(删除并返回数组的最后一个元素)并push进outStack,非空:直接出栈
3.出栈时,先push进inStack先从outStack出来,即:实现了先进先出
var inStack = [],
outStack = [];
function push(node) {
inStack.push(node);
}
function pop() {
if (!outStack.length) {
while (inStack.length) {
outStack.push(inStack.pop());
}
}
return outStack.pop();
}
扩展:
用两个队列实现一个栈的功能?
入栈:将元素进队列A
出栈:判断队列A中元素的个数是否为1,如果等于1,则出队列,否则将队列A中的元素 以此出队列并放入队列B,直到队列A中的元素留下一个,然后队列A出队列,再把 队列B中的元素出队列以此放入队列A中。
八、树
前序遍历、中序遍历、后序遍历
- 分别按照二叉树先序,中序和后序打印所有的节点。
/*
* function TreeNode(x) {
* this.val = x;
* this.left = null;
* this.right = null;
* }
*/
/**
*
* @param root TreeNode类 the root of binary tree
* @return int整型二维数组
*/
function threeOrders( root ) {
// write code here
if(!root) return [];
var result = [];
var temp = [];
//前序遍历 根->左->右
function preOrder(root){
if(root == null){
return;
}
temp.push(root.val);
preOrder(root.left);
preOrder(root.right);
}
//中序遍历 左->根->右
function inOrder(root){
if(root == null){
return;
}
inOrder(root.left);
temp.push(root.val);
inOrder(root.right);
}
//后序遍历 左->右->根
function postOrder(root){
if(root == null){
return;
}
postOrder(root.left);
postOrder(root.right);
temp.push(root.val);
}
preOrder(root);
result.push(temp);
temp = [];
middleOrder(root);
result.push(temp);
temp = [];
postOrder(root);
result.push(temp);
return result;
}
module.exports = {
threeOrders : threeOrders
};
简书puxiaotaoc:JS二叉树遍历(前序、中序、后序、深度优先、广度优先)
九、链表
十、apply call bind
call方法
语法:
call(thisObj, Object1, Object2)
//Function.call(obj,[param1[,param2[,…[,paramN]]]])
定义:调用一个对象的一个方法,以另一个对象替换当前对象。
说明:call方法可以用来代替另一个对象调用一个方法。call方法可将一个函数的对象上下文从初始的上下文改变为由thisObj指定的新对象。如果没有提供thisObj参数,则Global对象被用作thisObj。
apply方法
语法:
apply(thisObj, [argArray])
//Function.apply(obj,args)
定义:应用某一个对象的一个方法,用另一个对象替换当前对象。
说明:如果argArray不是一个有效的数组或者不是arguments对象,那么将导致一个TypeError。如果没有提供argArray和thisObj任何一个参数,那么Global对象将被用作thisObj,并且无法被传递任何参数。
yangrenmu:JavaScript中call、apply、bind的简单实现
apply怎么实现bind
//《javascript设计模式与开发实践》中第二章关于实现bind方法的代码
Function.prototype.bind = function (obj) {
var self = this;//保存调用的函数。
return function(){
return self.apply(obj,arguments);
}
};
var obj = {
name:'seven'
};
var func = function () {
console.log(this.name);
}.bind(obj);
func();
十一、Promise(重点)
(一)有道云笔记FZth001总结的Promise面试题
(二)构造一个Promise实例
构造一个Promise实例需要给Promise构造函数传入一个函数。传入的函数需要有两个形参,两个形参都是function类型的参数,分别是resolve和reject。Promise上还有then方法,then方法就是用来指定Promise对象的状态改变时确定执行的操作,resolve时执行第一个函数(onFulfilled),reject时执行第二个函数(onRejected)。当状态为resolve时便不能再变为reject,反之同理。
function Promise(executor) {
let self = this;
self.status = 'pending'; //等待态
self.value = undefined; //当前成功的值
self.reason = undefined; //失败的值
function resolve(value) { //成功的方法
if(self.status === 'pending') {
self.status = 'resolved';
self.value = value;
}
}
function reject(reason) { //失败的原因
if(self.status === 'pending') {
self.status = 'rejected';
self.reason = reason;
}
}
executor(resolve, reject);
}
Promise.prototype.then = function(onFulfilled, onRejected) {
let self = this;
if(self.status === 'resolved') {
onFulfilled(self.value);
}
if(self.status === 'rejected') {
onRejected(self.reason);
}
}
modules.exports = Promise;
(三)promise async await
promise async await是用来把异步请求改成同步。
promise:promise其实只是一个对象,用来执行异步操作通过new Promise()方法可以创建Promise对象。
例:
new Promise((resolve, reject) => {
// ...
});
async:为了使用await而使用,放在其函数前
await:放在async函数内部,表示等待promise返回结果后再继续执行,await虽然等待的是promise对象但不必写.then(…)。
例:
async function demo() {
let result = await Promise.resolve(12345);
}
demo();
(四)JS实现一个带并发限制的异步调度器
- js实现⼀个带并发限制的⼀部调度器,保证同时运⾏的任务最多有两个。
class Scheduler {
// 标记当前正在运⾏中的任务
queue = new Set();
add (promiseCreator) {
if (this.queue.size >= 2) {
const queueArr = [];
this.queue.forEach(item => queueArr.push(item));
// 当正在运⾏中的task有⼀个结束了之后,再重新运⾏这个新插⼊的task
return Promise.race(queueArr).finally(() => this.add(promiseCreator));
} else {
// 调度器当前任务数不⾜两个,直接返回该promise,并将该任务的promise存⼊
const promise = promiseCreator();
this.queue.add(promise);
promise.finally(() => {
this.queue.delete(promise);
});
return promise;
}
}
}
十二、AJAX(重点)
(一)特点、技术组成、核心原理、优点、缺点
1、特点
局部刷新、提高用户的体验度,数据从服务器商加载
2、技术组成
不是新技术,而是之前技术的整合
Ajax: Asynchronous Javascript And Xml;
包括的技术:JavaScript、XML、CSS、XMLHttpRequest
异步:发送请求以后,不等结果,由回调函数处理。
JavaScript:向服务器发送请求,获得返回结果,更新页面
XML: 用来封装数据
3、核心原理
XMLHttpRequst对象:通过该对象向服务器发送请求。
它是异步请求的技术,所有现代浏览器都支持(Chrome、IE5+)
1)创建XMLHttpReuest对象
非IE浏览器(Mozilla/Safari):
var xhr=new XMLHttpRequest();
IE:
xhr=new ActiveXObject(“Msxml2.XMLHTTP”);
低版本IE:
xhr=new ActiveXObject(“Microsfot.XMLHTTP”);
2)XMLHttpRequest对象的属性与方法
a)方法:
open(“GET/POST”,URL,true/false):用来向服务器建立连接
有三个参数:
参数1:提交方式,post或get
参数2:请求的URL
参数3:表示同步或异步请求,true:表示异步请求
false: 表示同步请求
send(data):发送请求
参数:提交的内容。
POST方式:data就是提交的参数,send(username=root&password=abc123);
GET方式:send(null)
b)属性:
onreadystatechange:设置状态改变时的回调函数,回调函数用来获取服务器数据。
onreadystatechange=function(){
}
readyState:服务器状态响应
状态码:
0:未初始化,还没调用send()方法
1:正在加载,已调用send()方法,正在发送请求
2:加载完成,send()方法执行完成,已经接收到全部响应的内容
3:请求进行中,正在解析响应内容
4:请求完成,响应内容解析完成,可在客户端调用。
responseText:服务器返回的数据(文本格式)
responseXML:服务器返回的数据(XML格式)
4、优点
(1)通过异步模式,提升用户体验。
(2)优化了浏览器和服务器之间的传输,减少不必要的数据往返,减少带宽占用。
(3)Ajax在客户端运行,承担了一部分本来由服务器承担的工作,减少了大用户量下的服务器负载。
5、缺点
(1)Ajax不支持浏览器back按钮。
(2)安全问题,Ajax暴露了与服务器交互的细节。
(3)对搜索引擎的支持比较弱。
(4)破坏了程序的异常机制。
(5)不容易调试。
(二)创建过程
(1)创建XMLHttpRequest对象,也就是创建一个异步调用对象
(2)创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息。
(3)设置响应HTTP请求状态变化的函数。
(4)发送HTTP请求。
(5)获取异步调用返回的数据。
(6)使用JS和DOM实现局部刷新。
(三)原生JS实现AJAX(请求原理解析)
1、创建异步对象XMLHttpRequest
var xhr;
if(XMLHttpRequest) {
xhr = new XMLHttpRequest();
}else {
xhr = new ActiveXObject("Microsoft.XMLHttp");
}
2、设置请求参数
(请求方式,请求页面的相对路径,是否异步)
2.1 准备请求
xhr.open(method,url,async);
//method: get/post
//url:请求地址
//async:true 异步/false 同步
2.2 发送请求
xhr.send();
2.3 get
xhr.open("GET",url,true);
xhr.send(null);
2.4 post
xhr.open("POST",url,true);
xhr.setRequestHeader("Content-Type","application/x-www-formurlencoded;charset=UTF-8"); //规定表头
xhr.send("name="+username+"&age="+userAge); //参数
3、处理响应
3.1 获取异步对象的readyState属性,该属性存有服务器响应的状态信息。每当readyState改变时,onreadystatechange函数就会被执行。
3.2 判断响应报文的状态,若为200则服务器正常运行并返回相应数据。
3.3 读取响应数据,可通过responseText属性来取回服务器返回的数据
xhr.onreadystatechange = function() {
if(xhr.readyState === 4 && xhr.status == 200) {
console.log("响应成功", xhr.responseText);
}
}
(四)AJAX错误调试
JQuery使我们在开发AJAX应用程序时提高了效率,减少了许多兼容性问题,我们在AJAX项目中,遇到ajax异步获取数据出错怎么办,我们可以通过捕捉error事件来获取出错信息。
发送error可能有下面两种情况引起的,或其他程序问题。
(1)data: “{}”,data为空也一定要传"{}“;不然返回的是xml格式的,并提示parsererror。
(2)parsererror的异常和Header的类型也有关系。即编码Header('Content-Type:text/html;charset=UTF-8”);
十三、DOM
(一)判断一个元素是否隐藏
function isHidden(element) {
var style = window.getComputedStyle(element);
return (style.display === 'none');
}
(二)遍历A节点的父节点下的所有子节点
var B = document.getElementById("A").parentNode.children;
console.log(B);
(三)判断浏览器是否支持某个属性或方法
(1)判断浏览器是否支持HTML标签属性
// HTML属性 in DOM对象 :判断是否支持这个属性,支持返回true,不支持返回false
if('placeholder' in document.createElement('input')){
console.log('浏览器支持placeholder这个标签属性')
}
(2)判断浏览器是否支持js属性或方法
// typeof 属性或方法 等于 undefined ,则表示不支持
if(typeof addEventListener === 'undefined'){
console.log('不支持')
}else{
console.log('支持')
}
(3)JS特性检测
function elementSupportsAttribute(elementName, attribute) {
if (!document.createElement) return false;
let temp = document.createElement(elementName);
return ( attribute in temp );
}
如果此函数返回false说明elementName中不包含attribute属性。
参数:
elementName: 字符串类型,如:‘div’
attribute: 字符串类型, 如:‘width’
//通过此方法也可检测是在移动端还是PC端:
//定义一个变量,存放是否存在"ontouchend"事件
const hasTouch = "ontouchend" in document ? true : false;//如果存在说明是移动端结果为true,如果不存在说明是在PC端结果为false;
if(hasTouch){
//移动端代码逻辑
}else{
//PC端代码逻辑
}
(4)JavaScript判断浏览器对CSS3属性是否支持的多种方法
//法一:javascript比较常用下面这个代码:
var support_css3 = (function() {
var div = document.createElement('div'),
vendors = 'Ms O Moz Webkit'.split(' '),
len = vendors.length;
return function(prop) {
if ( prop in div.style ) return true;
prop = prop.replace(/^[a-z]/, function(val) {
return val.toUpperCase();
});
while(len--) {
if ( vendors[len] + prop in div.style ) {
return true;
}
}
return false;
};
})();
//法二:JavaScript方法2:不支持ie6
function isPropertySupported(property)
{
return property in document.body.style;
}
法三:CSS.supports
十四、其他
(一)时间选择器获取当前时间
//获取日期 YYYY-MM-DD
var today = new Date();
var y = today.getFullYear();//获取年份
var m = (today.getMonth() + 1) < 10 ? ('0' + (today.getMonth() + 1)) : (today.getMonth() + 1);//获取月份
var d = today.getDate() < 10 ? ('0' + today.getDate()) : today.getDate();//获取天
today = y + "-" + m + "-" + d; //获取当前时间
alert(today);
(二)Proxy
- 在 Vue3.0 中通过 Proxy 来替换原本的 Object.defineProperty 来实现数据响应式。
- Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作。代表需要添加代理的对象,handler 用来自定义对象中的操作,比如 可以用来自定义 set 或者 get 函数。
let p = new Proxy(target, handler)
- 通过 Proxy 来实现一个数据响应式
- 如果需要实现一个 Vue 中的响应式, 需要在 get 中收集依赖,在 set 派发更新,之所以 Vue3.0 要使用 Proxy 替换原本的 API 原因在于 Proxy 无需一层层递归为每个属性添加代理,一次即可完成以上操作,性能上更好,并且原本的实现有一些数据更新不能监听到,但是 Proxy 可以完美监听到任何方式的数据改变,唯一缺陷就是浏览器的兼容性不好。
- 简单Demo:
let onWatch = (obj, setBind, getLogger) => {
let handler = {
get (target, property, receiver) {
getLogger(target, property);
return Reflect.get(target, property, receiver);
},
set (target, property, value, receiver) {
setBind(value, property);
return Reflect.set(target, property, value);
}
}
return new Proxy(obj, handler);
}
let obj = { a: 1 };
let p = onWatch (
obj,
(v, property) => {
console.log(`监听到属性${property}改变为${v}`);
},
(target, property) => {
console.log(`'${property}'-${target[property]}`);
}
)
p.a = 2 //监听到属性a改变
p.a // 'a' = 2
(三)手写Event类(观察者模式)
部分为秋招笔试面试中小公司遇到过的编程题或者类似题