JavaScript篇
Javascript很多考题都出自红宝书(JavaScript高级程序设计)
-
JS 有哪些数据类型?
- 基本数据类型:
String Boolean Number Undefined Null
(本质上null就是一个占位用的对象)ES6新增了Symbol
(创建后独一无二且不可变的数据类型) - 引用数据类型:
Object
(狭义的对象
,Array
,Function
)
- 基本数据类型:
-
是否可以使用
typeof bar === 'object'
来检测bar
是不是object
类型,有何风险?- 有风险。
typeof
只能检测出String,Number,Boolean,Undefined,Object,Function
这六种类型. typeof null
返回的也是Object
,因为null
本质上就是一个占位的对象.另一方面,数组Array
也不能用typeof
检测数据类型,因为同样会返回Object
.
- 有风险。
-
认清
Array
的方法:console.log( bar instanceof Array) // 如果数组,返回true
console.log( Array.isArray(bar)) //ES5方法
console.log( Object.prototype.toString.call(bar) === '[object Array]')
- 终极判断数据类型的方法:
//可以判断的类型有 Number, String, Boolean, Undefined, Null,Object,Function,Array console.log( Object.prototype.toString.call(arr).slice(8, -1)); console.log( Object.prototype.toString.call(arr).slice(8, -1) === 'Array'])
-
什么是
window
对象? 什么是document
对象?window
对象是指浏览器打开的窗口。document
对象是Documentd
对象(HTML
文档对象)的一个只读引用,window
对象的一个属性。
-
undefined
和null
的区别undefined
代表定义未赋值null
表示定义并且赋值了,只是值为null
-
介绍js有哪些内置对象?
- 数据封装类对象:
Object、Array、Boolean、Number
和String
- 其他对象:
Function、Arguments、Math、Date、RegExp、Error
- 数据封装类对象:
-
内存溢出与内存泄露
- 内存溢出
- 一种程序运行出现的错误
- 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误
- 内存泄露
- 占用的内存没有及时释放
- 内存泄露积累多了就容易导致内存溢出
- 常见的内存泄露:
- 意外的全局变量
- 没有及时清理的计时器或回调函数
- 闭包
- 内存溢出
-
对象的创建模式?
Object
构造函数模式/* 一个人: name:"Tom", age: 12 */ var p = new Object() //先创建空Object对象 p = {} //此时内部数据是不确定的 p.name = 'Tom'// 再动态添加属性/方法 p.age = 12 p.setName = function (name) { this.name = name } //测试 console.log(p.name, p.age)//Tom 12 p.setName('Bob') console.log(p.name, p.age)//Bob 12
- 对象字面量模式
var p = { name: 'Tom', age: 12, setName: function (name) { this.name = name } } //测试 console.log(p.name, p.age) p.setName('JACK') console.log(p.name, p.age)
- 工厂函数:通过工厂函数动态创建对象并返回(内置对象)
function createPerson(name, age) { //返回一个对象的函数===>工厂函数 var obj = { name: name, age: age, setName: function (name) { this.name = name } } return obj } // 创建2个人 var p1 = createPerson('Tom', 12) var p2 = createPerson('Bob', 13)
- 构造函数模式
function Person(name, age) { this.name = name; this.age = age; this.setName = function(name){this.name=name;}; } new Person('tom', 12);
- 构造函数+原型的组合模式
function Person(name, age) { this.name = name; this.age = age; } Person.prototype.setName = function(name){this.name=name;}; new Person('tom', 12);
-
JavaScript原型,原型链 ? 有什么特点?
- 函数的
prototype
属性(显示原型属性): 在定义函数时自动添加的, 默认值是一个空Object
实例对象(独独Object不满足) - 所有函数都是Function的实例对象(包含Function)
console.log(Function.__proto__===Function.prototype)//true
- Object的原型对象是原型链尽头
console.log(Object.prototype.__proto__) // null
- 所有实例对象都有一个特别的属性:
__proto__
(隐式原型属性)在创建实例对象时被自动添加, 并赋值为构造函数的prototype值 - 原型链:
- 访问一个对象的属性时,
- 先在自身属性中查找,找到返回
- 如果没有, 再沿着
__proto__
这条链向上查找, 找到返回 - 如果最终没找到, 返回
undefined
- 别名: 隐式原型链
- 作用: 查找对象的属性(方法)
- 这样通过
__proto__
属性就形成了一个链的结构---->原型链 - 当查找对象内部的属性/方法时,
js
引擎自动沿着这个原型链查找
- 函数的
-
Javascript如何实现继承?
- 1 构造继承 2 原型继承 3 实例继承 4 拷贝继承
//原型prototype机制或apply和call方法去实现较简单,建议使用构造函数与原型混合方式。 function Parent(){ this.name = 'wang'; } function Child(){ this.age = 28; } Child.prototype = new Parent();//继承了Parent,通过原型 var demo = new Child(); alert(demo.age); alert(demo.name);//得到被继承的属性
-
上下文作用域
全局函数无法查看局部函数的内部细节,但局部函数可以查看其上层的函数细节,直至全局细节。
当需要从局部函数查找某一属性或方法时,如果当前作用域没有找到,就会上溯到上层作用域查找,
直至全局函数,这种组织形式就是作用域链
。
-
谈谈
This(上下文)
对象的理解。- 我们每次调用函数时,解析器都会将一个上下文对象作为隐含的参数传递进函数。使用
this
来引用上下文对象,根据函数的调用形式不同,this
的值也不同。 this
的不同的情况:- 以函数的形式调用时,
this
是window
- 以方法的形式调用时,
this
就是调用方法的对象 - 以构造函数的形式调用时,
this
就是新创建的对象 - 使用
call
和apply
调用时,this
时指定的那个对象 - 在事件中,
this
指向触发这个事件的对象,特殊的是,IE
中的attachEvent
中的this
总是指向全局对象Window
;
- 以函数的形式调用时,
- 我们每次调用函数时,解析器都会将一个上下文对象作为隐含的参数传递进函数。使用
-
call
和apply
的区别?- 这两个方法都是函数对象的方法需要通过函数对象来调用
- 通过两个方法可以直接调用函数,并且可以通过第一个实参来指定函数中
this
- 不同的是
call
是直接传递函数的实参而apply
需要将实参封装到一个数组中传递
-
Javascript
中,有一个函数,执行时对象查找时,永远不会去查找原型,这个函数是?hasOwnProperty
-
什么是立即执行函数?使用立即执行函数的目的是什么?
- 概念:1. 声明一个匿名函数 2. 马上调用这个匿名函数
- 为什么要用括号把函数包起来:为了兼容
JS
的语法 - 作用: 隐藏内部实现 不污染外部命名空间(创建一个独立的作用域,这个作用域里面的变量,外面访问不到(即避免「变量污染」))
- 以这个面试题为例,用立即执行函数给每个 li 创造一个独立作用域即可
var liList = ul.getElementsByTagName('li') for(var i=0; i<6; i++){ function(j){ liList[j].onclick = function(){ alert(j) // 0、1、2、3、4、5 } }(i) } //在立即执行函数执行的时候,i 的值被赋值给 j,此后 j 的值一直不变。 //i 的值从 0 变化到 5,对应 6 个立即执行函数,这 6 个立即执行函数里面的j「分别」 //是 0、1、2、3、4、5。以上,就是立即执行函数的基本概念。
-
JSON
的了解?
JSON
(JavaScript Object Notation) 是一种轻量级的数据交换格式。 它是基于JavaScript的一个子集。数据格式简单, 易于读写, 占用带宽小.如:{"age":"12", "name":"back"}
JSON字符串转换为JSON对象:
var obj =eval('('+ str +')');
var obj = str.parseJSON();
var obj = JSON.parse(str);
JSON对象转换为JSON字符串:
var last=obj.toJSONString();
var last=JSON.stringify(obj);
- NaN是什么?它是什么类型?如何检测一个变量是不是NaN?
- 答案:
NaN(not a Number)
,但是实际上它是Number
类型,typeof NaN
会返回number
.这个东西比较厉害,因为 NaN === NaN //false
- 你会发现它自己都不等于它自己,因此判断变量是否是它,不能使用
===
,可以使用isNaN()
方法
- 答案:
//检查变量是否是NaN
isNaN(bar);
Object.is(bar,NaN); //ES6方法,这个方法会修正JS中的一些小bug
/* Object.is()方法,要求严格相等,且Object.is(NaN,NaN)会返回true */
这两个方法结合起来用的作用在于判断变量 bar 是一个 NaN非数值,但不是NaN本身这个数
/*补充一个*/
var a = b = 3;
//实际上等同于
var a=b;
b=3;
-
js
延迟加载的方式有哪些?defer
(只支持IE
)和async
、创建script
,插入到DOM
中,加载完毕后callBack
(用得最多)、按需异步载入js
-
如何将浮点数点左边的数每三位添加一个逗号,如
12000000.11
转化为『12,000,000.11』
?(386485473.88).toLocaleString('en-US') // "386,485,473.88"
//方法2: var separator=(num)=>{ if(!num){ return '0.00'; }; let str = parseFloat(num).toFixed(2); return str && str .toString() .replace(/(\d)(?=(\d{3})+\.)/g, function($0, $1) { return $1 + ","; }); } separator(386485473.88) //"386,485,473.88"
-
列举ES6新特性
- 函数默认值、解构赋值 、模板字符串、
let/const
、新增函数库(Number String Array Obiect Math
)、箭头函数
- 函数默认值、解构赋值 、模板字符串、
-
let
、const
、var
的区别?let
:- 无变量提升、有块级作用域、不能重复声明
const
:- 无变量提升、有块级作用域、禁止重复声明
- 禁止重复赋值、必须赋初始值
var
存在变量提升,可以重复声明,赋值
-
箭头函数
this
的指向- 在箭头函数中,函数体内部没有自己的 this,默认在其内部调用 this 的时候,会自动查找其父级上下文的 this 对象(如果父级同样是箭头函数,则会按照作用域链继续向上查找)
-
手写
es6
继承 -
promise的状态,链式调用,同步异步流程,唯一性
-
js
的for
跟for in
循环它们之间的区别?- 遍历数组时的异同:
for
循环 数组下标的typeof
类型:number
,
-for in
循环数组下标的typeof
类型:string
- 遍历对象时的异同:
for
循环 无法用于循环对象,获取不到obj.length
;for in
循环遍历对象的属性时,原型链上的所有属性都将被访问,- 解决方案:使用
hasOwnProperty
方法过滤或Object.keys
会返回自身可枚举属性组成的数组
- 遍历数组时的异同:
-
js
对数组去重,列出你的思路(两种以上)//第一种:原生方法去重,借助一个空数组实现去重,便于理解底层原理(unique函数带有参数) function unique(arr) { let a = []; //Array.prototype.forEach(function(item, index){}) : 遍历数组 arr.forEach((item,index)=>{ a.indexOf(item)===-1?a.push(item):''; }) return a; }; console.log(unique([1,3,3,5,7,1,1,3])); //[1,3,5,7]; //第二种: /*第二种同上(unique函数,但是不带参数) 拓展:需要注意的一点就是此函数可以传多个数组,但是要看arguments[index] 决定执行哪个数组,默认是执行第一个*/ function unique2() { let a = []; //Array.from(v) : 将伪数组对象或可遍历对象转换为真数组 Array.from(arguments[1]).forEach((item,index)=>{ a.indexOf(item)===-1?a.push(item):''; }) return a; } console.log(unique2([1,2,2,5,1,3,4,3],[1,1,5,55,4]);//[ 1, 5, 55, 4 ] //原理 function unique2() { let a = []; //Array.prototype 可以换成[] Array.prototype.forEach.call(arguments[0],(item,index)=>{ a.indexOf(item)===-1?a.push(item):''; }) return a; } console.log(unique2([1,2,2,5,1,3,4,3],[1,1,5,55,4]);//[[ 1, 2, 5, 3, 4 ]] //第三种方法: Array.from方法可以将Set结构转为数组 //new Set + Array.from function unique3(arr) { return Array.from(new Set(arr)); } console.log(unique3([1,2,2,5,1,3,4,3])); //第四种方法:最简单 let arr = [1,2,5,5,1,3,4,3,7,9,7]; let uniq = [...new Set(arr)]; console.log(uniq); //第五种方法: 基于数组原型链的去重方法 Array.prototype.uniq = function () { let a = []; this.forEach((item,index)=>{ a.indexOf(item)===-1?a.push(item):''; }) return a; } console.log([3,1,4,1,1,3].uniq());
-
什么是闭包?
- 包含被引用变量(函数)的对象(通过
Chrome
工具查看)
- 包含被引用变量(函数)的对象(通过
-
请将下列b函数进行修改,保证每次调用a都能+1(考闭包):
function b(){ var a=1; }; function b(){ var a=1; return ()=>{ a++; return a; } }; let c = b(); c(); //2 c(); //3 c(); //4
-
删除数组最后一个元素,改变原数组的方法?
myArr.pop()
myArr.slice(myArr.length-1)
myArr.splice(myArr.length,1)
-
列举常用的5个字符串操作方法
string.slice()
- 可以从一个字符串中截取指定的内容,并将截取到内容返回,不会影响原变量
string.substring()
- 和slice()基本一致,不同的是它不能接受负值作为参数,如果设置一个负值,则会自动修正为0,
substring()中如果第二个参数小于第一个,自动调整位置
- 和slice()基本一致,不同的是它不能接受负值作为参数,如果设置一个负值,则会自动修正为0,
string.substr()
- 和slice()基本一致,不同的是它第二个参数不是索引,而是截取的数量
string.trim()
去除前后空格string.toUpperCase()
将字符串转换为大写并返回string.toLowerCase()
将字符串转换为小写并返回
-
列举常用的5个数组操作方法
push()
pop()
shift()
unshift()
splice()
sort()
-
事件冒泡以及事件捕获。
-
如何实现数组的随机排序?
/*第一种方法*/
var arr = [1,2,3,4,5,6,7,8,9,10];
function randSort1(arr){
for(var i = 0,len = arr.length;i < len; i++ ){
var rand = parseInt(Math.random()*len);
var temp = arr[rand];
arr[rand] = arr[i];
arr[i] = temp;
}
return arr;
}
console.log(randSort1(arr));
/*第二种方法*/
var arr = [1,2,3,4,5,6,7,8,9,10];
arr.sort(function(){
return Math.random() - 0.5;
})
console.log(arr);
/*第三种方法*/
var arr = [1,2,3,4,5,6,7,8,9,10];
function randSort2(arr){
var mixedArray = [];
while(arr.length > 0){
var randomIndex = parseInt(Math.random()*arr.length);
mixedArray.push(arr[randomIndex]);
arr.splice(randomIndex, 1);
}
return mixedArray;
}
console.log(randSort2(arr));
- 排序方法
/*冒泡排序*/ function bubbleSort (myArr) { var len = myArr.length; var i,j,stop; for (i = 0; i < len; i++) { for (j = 0; stop = len-i, j < stop; j++) { if (myArr[j] > myArr[j+1]) { [myArr[j],myArr[j+1]] = [myArr[j+1],myArr[j]];//交换元素 } } } return myArr; } var res = bubbleSort([3,2,4,5,1]); console.log(res);//[1,2,3,4,5] /*选择排序*/ function selectSort (myArr) { var len = myArr.length, min; var i,j; for (i = 0; i < len; i++) { min = i; for (j=i+1; j < len;j++) { if (myArr[j]<myArr[min]) { min = j; } } //如果i不是最小的,则互换 if (i!= min) { [myArr[i],myArr[min]] = [myArr[min],myArr[i]]; } } return myArr; } var res = selectSort([3,2,4,5,1]); console.log(res);//[1,2,3,4,5] /*插入排序*/ function insertionSort(arr) { for(let i = 1; i < arr.length; i++) { for(let j = 0; j < i; j++) { if(arr[i] < arr[j]) { //在j位置新增一个元素arr[i] arr.splice(j, 0, arr[i]); //再把原来arr[i]所在的删除 arr.splice(i+1, 1); break } console.log(arr); // 至今不明白为什么控制台输出是[2,3,4,5,1] } } } var arr = [3,2,4,5,1]; insertionSort(arr); console.log(arr); //[1,2,3,4,5] /*归并排序*/ function mergeSort(arr) { var merge = function(leftArr, rightArr) { var resultArr = [] while(leftArr.length && rightArr.length) { resultArr.push(leftArr[0] <= rightArr[0] ? leftArr.shift() : rightArr.shift()); } return resultArr.concat(leftArr).concat(rightArr) } if(arr.length < 2) return arr; let mid = parseInt(arr.length/2) //取数组的中位下标,也可以用 arr.length >> 1 return merge(mergeSort(arr.slice(0, mid)), mergeSort(arr.slice(mid))); } var res = mergeSort([3,2,4,5,1]); console.log(res);//[1,2,3,4,5] /*快速排序*/ function quickSort(arr) { if (arr.length <= 1) { return arr //递归的出口 } var left = [], right = [], current = arr.splice(0,1); //此时数组少了一个数(第一个) for (let i =0; i<arr.length; i++) { if (arr[i] < current) { left.push(arr[i]); // 放左边 }else { right.push(arr[i]); //放右边 } } return quickSort(left).concat(current,quickSort(right)); } /*测试*/ let res = quickSort([4,7,21,6,9,3,1]); console.log(res);//[1,3,4,6,7,9,21]
- DOM操作——怎样添加、移除、移动、复制、创建和查找节点?
这个面试的时候问了怎么用js动态控制插入节点
(1)创建新节点
createDocumentFragment() //创建一个DOM片段
createElement() //创建一个具体的元素
createTextNode() //创建一个文本节点
(2)添加、移除、替换、插入
appendChild()
removeChild()
replaceChild()
insertBefore() //在已有的子节点前插入一个新的子节点
(3)查找
getElementsByTagName() //通过标签名称
getElementsByName() //通过元素的Name属性的值(IE容错能力较强,会得到一个数组,其中包括id等于name值的)
getElementById() //通过元素Id,唯一性
Promise
怎么使用?
-ES6
的Promise
是一个构造函数,用来生成promise
实例。- 作用:
- 解决回调地狱(回调函数的层层嵌套, 编码是不断向右扩展, 阅读性很差)
- 能以同步编码的方式实现异步调用
- 使用
Promise
:- 创建
promise
对象let promise = new Promise((resolve, reject) => { //初始化promise状态为 pending //执行异步操作 if(异步操作成功) { resolve(value);//修改promise的状态为fullfilled } else { reject(errMsg);//修改promise的状态为rejected } })
- 调用
promise
的then()
promise.then(function( result => console.log(result), errorMsg => alert(errorMsg) )); ---- --- --- ----- ------------------------- promise.then(()=>{},()=>{}).then()... //使用promise封装处理ajax请求 let request = new XMLHttpRequest(); request.onreadystatechange = function () { } request.responseType = 'json'; request.open("GET", url); request.send();
- 创建
- 作用:
async/await
语法了解吗?目的是什么
概念:真正意义上去解决异步回调的问题,同步流程表达异步操作(本质是
Generator
的语法糖)
* 不需要像Generator去调用next方法,遇到await等待,当前的异步操作完成就往下执行
* 返回的总是Promise对象,可以用then方法进行下一步操作
* async取代Generator函数的星号*,await取代Generator的yield
* 语意上更为明确,使用简单,经临床验证,暂时没有任何副作用
- 如何用正则实现 string.trim() //去除字符串前后的空格
function trim(string){ return string.replace(/^\s+|\s+$/g, '')}
- 写一个通用的事件侦听器函数。
// event(事件)工具集,来源:github.com/markyun
markyun.Event = {
// 页面加载完成后
readyEvent : function(fn) {
if (fn==null) {
fn=document;
}
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = fn;
} else {
window.onload = function() {
oldonload();
fn();
};
}
},
// 视能力分别使用dom0||dom2||IE方式 来绑定事件
// 参数: 操作的元素,事件名称 ,事件处理程序
addEvent : function(element, type, handler) {
if (element.addEventListener) {
//事件类型、需要执行的函数、是否捕捉
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent('on' + type, function() {
handler.call(element);
});
} else {
element['on' + type] = handler;
}
},
// 移除事件
removeEvent : function(element, type, handler) {
if (element.removeEventListener) {
element.removeEventListener(type, handler, false);
} else if (element.datachEvent) {
element.detachEvent('on' + type, handler);
} else {
element['on' + type] = null;
}
},
// 阻止事件 (主要是事件冒泡,因为IE不支持事件捕获)
stopPropagation : function(ev) {
if (ev.stopPropagation) {
ev.stopPropagation();
} else {
ev.cancelBubble = true;
}
},
// 取消事件的默认行为
preventDefault : function(event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},
// 获取事件目标
getTarget : function(event) {
return event.target || event.srcElement;
},
// 获取event对象的引用,取到事件的所有信息,确保随时能使用event;
getEvent : function(e) {
var ev = e || window.event;
if (!ev) {
var c = this.getEvent.caller;
while (c) {
ev = c.arguments[0];
if (ev && Event == ev.constructor) {
break;
}
c = c.caller;
}
}
return ev;
}
};
- 刁钻代码题
a.x = a = {}
问 a.x 是多少?undefined
var a = {n:1};
var b = a;
a.x = a = {n:2};
-
(a ==1 && a== 2 && a==3)
可能为true
吗?a = { value: 0, toString(){ a.value += 1 return a.value } }
-
["1", "2", "3"].map(parseInt)
答案是多少?详细解析parseInt()
函数能解析一个字符串,并返回一个整数,需要两个参数 (val
,radix
),其中radix
表示要解析的数字的基数。【该值介于2
~36
之间,并且字符串中的数字不能大于radix
才能正确返回数字结果值】;- 但此处
map
传了3
个 (element
,index
,array
),我们重写parseInt
函数测试一下是否符合上面的规则。function parseInt(str, radix) { return str+'-'+radix; }; var a=["1", "2", "3"]; a.map(parseInt); // ["1-0", "2-1", "3-2"] 不能大于radix /* 因为二进制里面,没有数字3,导致出现超范围的radix赋值和不合法的进制解析, 才会返回NaN所以["1", "2", "3"].map(parseInt) 答案也就是:[1, NaN, NaN]*/
-
以下两个函数是否等价
function foo1()
{
return {
bar: "hello"
};
}
function foo2()
{
return
{
bar: "hello"
};
}
console.log(foo1()); // {bar : "hellp"}
console.log(foo2()); // undefined
答案:不等价!!
注意return 后面大括号的位置,第二个函数js会默认return 后面返回的东西(是空),等价于
return undefined
{xxx} //后面当然,当然是写了也白写
-
事件是?IE与火狐的事件机制有什么区别? 如何阻止冒泡?
1. 我们在网页中的某个操作(有的操作对应多个事件)。例如:当我们点击一个按钮就会产生一个事件。是可以被JavaScript
侦测到的行为。
2. 事件处理机制:IE
是事件冒泡、Firefox
同时支持两种事件模型,也就是:捕获型事件和冒泡型事件;
3.ev.stopPropagation();
(旧ie
的方法ev.cancelBubble = true;
) -
如何实现深拷贝?
- 理想方案:(但不能处理函数数据)
function clone(object){ let _obj=JSON.parse(JSON.stringify(obj))}
- 保险做法
function clone(obj){ if(!obj&& typeof obj!== 'object'){ return; } var newObj=obj.constructor===Object?{}:[]; for(var key in obj){ newObj[key] =(obj[key]&&typeof obj[key]==='object')?clone(obj[key]):obj[key]; } return newObj; }
- 理想方案:(但不能处理函数数据)
-
Ajax
是什么? 如何创建一个Ajax
?Ajax
的全称:Asynchronous Javascript And XML
。Ajax
是一种用于创建快速动态网页的技术, 异步的JavaScript
和XML
所谓异步,在这里简单地解释就是:向服务器发送请求的时候,我们不必等待结果,而是可以同时做其他的事情,等到有了结果它自己会根据设定进行后续操作,与此同时,页面是不会发生整页刷新的,提高了用户体验。
- 创建
(1)创建XMLHttpRequest
对象,也就是创建一个异步调用对象
(2)创建一个新的HTTP
请求,并指定该HTTP
请求的方法、URL
及验证信息
(3)设置响应HTTP
请求状态变化的函数
(4)发送HTTP
请求
(5)获取异步调用返回的数据
(6)使用JavaScript
和DOM
实现局部刷新
-
Ajax
的优缺点- 优点:
- 减轻服务器的负担,
Ajax
一般只从服务器获取只需要的数据。 - 无需刷新整个页面, 减少用户等待时间。
- 更好的客户体验,可以将一些服务器的工作转移到客户端完成,节约网络资源,提高用户体验。
- 基于标准化的对象,不需要安装特定的插件, 浏览器都能支持
Ajax
- 彻底将页面与数据分离。
- 减轻服务器的负担,
- 缺点:
- 没有浏览历史, 不能回退
- 存在跨域请求问题
- 对搜索引擎支持比较弱
- 使用动态页面更新使得用户难于将某个特定的状态保存到收藏夹中,不过这些都有相关方法解决
- 优点:
-
Ajax
解决浏览器缓存问题?
- 在
ajax
发送请求前加上anyAjaxObj.setRequestHeader("If-Modified-Since","0")
。 - 在
ajax
发送请求前加上anyAjaxObj.setRequestHeader("Cache-Control","no-cache")
。 - 在
URL
后面加上一个随机数:"fresh=" + Math.random();
。 - 在
URL
后面加上时间戳:"nowtime=" + new Date().getTime();
。 - 如果是使用
jQuery
,直接这样就可以了$.ajaxSetup({cache:false})
。这样页面的所有ajax
都会执行这条语句就是不需要保存缓存记录。
- 同步和异步的区别?
同步的概念应该是来自于OS中关于同步的概念:不同进程为协同完成某项工作而在先后次序上调整(通过阻塞,唤醒等方式).同步强调的是顺序性.谁先谁后.异步则不存在这种顺序性.
-
同步:浏览器访问服务器请求,用户看得到页面刷新,重新发请求,等请求完,页面刷新,新内容出现,用户看到新内容,进行下一步操作。
-
异步:浏览器访问服务器请求,用户正常操作,浏览器后端进行请求。等请求完,页面不刷新,新内容也会出现,用户看到新内容。
-
如何解决跨域问题?
jsonp
、iframe
、window.name
、window.postMessage
、服务器上设置代理页面 -
怎么理解不同源
- 协议名不同
- 域名不同
- 端口号不同
-
详细说说jsonp
基本思想
- 网页通过添加一个
<script src=‘’>
元素,向服务器请求JSON
数据(<script>
的src
属性获得js
代码, 不受同源政策限制)- 服务器收到请求后,将数据放在一个指定名字的回调函数里传回来
- 用
JSONP
获取的不是JSON
数据,而是可以直接运行的JavaScript
语句网页不能跨域的ajax
请求,<script>
可以跨域<script src='xxxxx'>
到本地就执行
- 动态生成
<script>
标签, 然后通过标签的src
属性发送GET
类型的跨域请求, 服务器端返回一段js
脚本, 这段js
脚本的内容为一个函数调用,实参为需要返回的响应数据(json
);- 客户端这边需要提前定义好对应的函数, 当
<script>
请求成功并接收到数据时, 会自动调用此函数, 在函数中 我们就可以处理响应数据了
-
你对重绘、重排的理解?
- 首先网页数次渲染生成时,这个可称为重排;
- 修改
DOM
、样式表、用户事件或行为(鼠标悬停、页面滚动、输入框键入文字、改变窗口大小等等)这些都会导致页面重新渲染,那么重新渲染,就需要重新生成布局和重新绘制节点,前者叫做"重排",后者"重绘"; - 减少或集中对页面的操作,即多次操作集中在一起执行;
- 总之可以简单总结为:重绘不一定会重排,但重排必然为会重绘。
- 更详细的可以看阮老师分析
-
项目上线前,能做哪些优化?
- 图片预加载,
css
样式表放在顶部且link链式引入,javascript
放在底部body
结束标签前; - 使用
dns-prefetch
对项目中用到的域名进行DNS
预解析,减少DNS
查询,如:<link rel="dns-prefetch" href="//github.com"/>
; - 减少http请求次数:图片静态资源使用
CDN
托管;API
接口数据设置缓存,CSS Sprites/SVG Sprites
,JS
、CSS
源码压缩、图片大小控制合适,使用iconfont
(字体图标)或SVG
,它们比图片更小更清晰,网页Gzip
压缩; - 减少
DOM
操作次数,优化javascrip
t性能; - 减少
DOM
元素数量,合理利用:after、:before
等伪类; - 避免重定向、图片懒加载;
- 前后端分离开发,资源按需加载,最好能做到首屏直出(即服务端渲染);
- 避免使用
CSS Expression
(css
表达式)又称Dynamic properties
(动态属性) - 多域名分发划分内容到不同域名,解决浏览器域名请求并发数问题,同时也解决了请求默认携带的
cookie
问题; - 尽量减少
iframe
使用,它会阻塞主页面的渲染; - 对所有资源压缩
JavaScript
、CSS
、字体
、图片
等,甚至html
;
- 图片预加载,