1、介绍原型/原型链/构造函数/实例/继承
构造函数:用new 操作后面的函数,即便是空函数,结果生成一个实例原型:声明一个函数时,就自动给该函数增加一个propotype 属性,指向原型对象(初始化一个空对象),为了实现函数复用原型链:实例上的__proto_ 属性指向构造函数的原型对象,最顶端是 Object.prototype,然后再往上就是 null,Object.prototype.__proto__ = null函数也有__proto__ 属性,指向 Function.prototype,函数是构造函数 Function 的实例构造器:原型对象的constructor 属性,默认指向声明的函数
2、如何实现 new 运算符
let new2 = function(func) {
//创建一个空对象 o,并且继承构造函数的原型对象
let o = Object.create(func.prototype);
//执行构造函数,并且上下文 this 指向 o 对象
let k = func.call(o);
//如果构造函数返回的是对象就返回该对象,否则返回 o 对象
if(typeof k === 'object') {
return k
}else {
return o
}
}
3、有几种方式可以实现继承
//借助构造函数实现继承:缺点是父构造函数的原型链继承不了,若要全部继承除非将所有属性和方法定义在构造函数中
function Parent1 () {
this.name = 'parent1';
}
function Child1 () {
//这么做的好处是定义在父构造函数中的引用类型的属性,对于子构造函数的每个实例来说是独立的
//并且在子构造函数实例化时,可以给父构造函数传参
Parent.call(this);
this.type = 'child1';
}
//借助原型链实现继承:缺点是继承的引用类型属性是共享的,子构造函数的实例更改会影响其他实例上的这个属性,比如 play 属性
function Parent2 () {
this.name = 'parent2';
this.play = [1, 2, 3];
}
function Child2 () {
this.type = 'Child2';
}
Child2.prototype = new Parent2();
//组合方式:缺点是会执行两次父构造函数
function Child3 () {
//执行第一次
Parent2.call(this);
this.type = 'Child3';
}
Child3.prototype = new Parent2(); //执行第二次
//组合优化1,不需要再将定义在父构造函数中的属性和方法再继承一次,只需要继承原型链上的
Child3.prototype = Parent2.prototype;
//缺点是无法区分一个实例是子函构造函数实例化的还是父构造函数实例化的
let s1 = new Child3();
//s1.constructor指向了 Parent2,而不是 Child3,因为 Child3 原型对象的属性 constructor 继承了 Parent2 原型对象上的
//如果你强行执行 Child3.prototype.constructor = Child3 的话,也会将 Parent2.prototype.constructor 改成 Child3
//组合优化2,通过 Object.create() 创建一个中间对象,将两个原型对象区别开来,并且继承父构造函数的原型链
Child3.prototype = Object.create(Parent2.prototype);
Child3.prototype.constructor = Child3;
//即 Child3.prototype.__proto__ === Parent2.prototype 为 true
//如此 Child3.prototype 和 Parent2.prototype 被隔离开,是两个对象,不会相互影响
//这种方式为理想继承方式
4、arguments
可变参/不定参,为一个类数组,有 length 属性,可以通过循环找到每个参数,可以用 arguments[index] 来找到具体的参数,但是不是真正的数组,如果要转换成真正的数组(意味着可以使用数组方法),可以这么做:
//以 forEach 为例:
Array.prototype.forEach.call(arguments, (item) => {console.log(item);});
[].forEach.call(arguments, () => {});
Array.from(arguments).forEach(() => {}); //直接将 arguments 转换成了数组
当我们给参数命名,是为了增加可读性。
5、数据类型判断
判断基本类型(除null),使用 typeof 运算符:
let num = 1;
typeof num ==='number'; //为 true
//还可以判断是否为 function
typeof func === 'function';
//还可以判断是否为 object
typeof obj === 'object';
判断null 或者 undefined,直接跟 null 或者 undefined 作比较,num === null
判断数组:
1)arr instanceof Array //返回 true 表示 arr 是数组类型,前提是必须保证由同一个 Array 构造函数生成的实例,如果是另一个 iframe 的话,则不为 true,则即便是数组类型,也得不到正确的结果2)arr.constructor === Array //问题跟 instanceof 相同,且这个属性是可以被改写的3)Array.isArray(arr) //一般使用这个方式,不存在上面的问题4)Object.prototype.toString.call(arr) //必须使用 Object 原型对象上的 toString 方法,而不能使用 Array 重写的 toString 方法,这个方式可以区分函数/数组/对象/null,也没有上面的问题,且兼容性比第三种更好(第三就是这种方式的语法糖,只不过只能判断是否是数组类型,直接使用这种方式可以判断更多类型),这么使用:
Object.prototype.toString.call(arr) === '[object Function]' || '[object Array]' || '[object null]' || '[object Object]'
PS:instranceof 的作用是判断一个对象是否是另一个对象的实例,其问题除了上面的以外,还有就是只要是原型链上的对象,返回都为 true,因此我们使用 constructor 来判断更精准,但是 constructor 属性是可以被改写的,因此使用第二道面试题中理想继承方式,就可以使用 constructor 来精准判断一个对象是否是另一个对象的实例。
6、作用域链、闭包、作用域
作用域即执行环境,每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。在web 浏览器中,全局执行环境被认为是 window 对象。且每个函数都有自己的执行环境,在进入函数时入栈,在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。
当代码在一个环境中执行时,会创建变量对象的一个作用域链,是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终是当前执行的代码所在环境的变量对象,最后端,始终是全局执行环境的变量对象。
闭包指的是有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。也就是一个闭包的作用域链上至少包含三个作用域,并且只有闭包才有自由变量(即不是本地定义的,也不是全局变量)
//使用闭包要作内存管理
let func = (function() { return function closure() {} })();
//会将 closure 函数和它的作用域链赋值给 func 全局变量,外部函数的作用域会一直保存在内存中,直到 closure 函数不存在
//为了优化性能,在调用完 func 后应该给 func 变量作解除引用的操作,即
func = null; //让值脱离环境,让垃圾收集器下次运行时将值回收。
7、Ajax的原生写法
function ajax1() {
//创建一个 XHR 对象
let oAjax = window.XMLHttpRequest ? (new XMLHttpRequest()) : (new window.ActiveXobject('Microsoft.XMLHTTP'));
//返回一个函数,这是函数柯里化操作,不用每次调用 ajax 都判断浏览器环境
//但是会占用更多的内存,因为总是会保存外部函数的作用域
return function(url, fnSucc, fnFaild) {
//只要 XHR 对象的 readyState 属性的值发生改变,就触发这个事件
oAjax.onreadystatechange = function() {
// readyState属性是 0-4 的值,当为 4 时,表示已经接收到全部响应数据,并可以在客户端使用
if(oAjax.readyState === 4) {
//响应的 HTTP 状态
let s = oAjax.status;
if(s === 200 || s === 206 || s === 304) {
//将响应主体被返回的文本作为参数传给这个函数,并执行这个函数
if(fnSucc) fnSucc(oAjax.responseText);
}else {
if(fnFaild) fnFaild(oAjax.status);
}
}
};
//启动一个请求,准备发送
oAjax.open('GET', url, true);
//发送请求
oAjax.send(null);
}
}
8、对象深拷贝、浅拷贝
对象浅拷贝是共用一个引用,因此更改新拷贝的对象时,也会更改原来的对象
对象深拷贝是两个引用,有以下几种方式实现深拷贝:
//使用 Object.assign,只能实现第一层属性的深拷贝
let clone = Object.assign({},obj)
//使用 slice,如果数组中有引用类型的元素的话,只能实现第一层的深拷贝
let clone = arr.slice(0);
//使用 concat,同 slice
let clone = [].concat(arr);
//使用 JSON 对象,无法实现属性值为 function 和 undefined 的拷贝,并且拷贝从原型链继承的值也会有问题,比如 constructor 的值变成了 Object
function deepClone(obj) {
let _obj = JSON.stringify(obj);
let clone = JSON.parse(_obj);
return clone;
}
//使用递归,在不使用库的情况下,这种方式可以实现真正的深层度的拷贝
function deepClone(obj) {
let clone = Array.isArray(obj) ? [] : {};
if(obj && typeof obj === 'object') {
for(let key in obj) {
if(obj.hasOwnProperty(key) {
if(obj[key] && typeof obj[key] === 'object') {
clone[key] = deepClone(obj[key]);
}else {
clone[key] = obj[key];
}
}
}
}
return clone;
}
//通过 JQuery 的 extend 方法
//使用 lodash 函数库
for-in 和 for-of 的区别:for-in 和 for-of 的区别1、for-in 遍历的总是对象的下标,因此如果给数组增加属性,那么这个属性(key)也会遍历出来,更适合遍历对象,遍历顺序可能不是按照内部顺序,通常配合 hadOwnProperty() 方法一起使用;2、for-of 就是迭代器,遍历的是数组元素的值,不会遍历增加的属性,是按照内部顺序遍历的,并且还可以遍历 Map 和 Set 数据结构。如果没有在内部使用let 那么默认为 var
在使用console.log 这类方法输出对象时会显示内存的最新状态,在同一个 tick 中,即便更改对象是在 console.log 之后,那么输出的对象也是被更改过的对象,因此,如果想输出某个时刻的对象值时,应该进行深拷贝进行输出。
9、图片懒加载、预加载
图片懒加载是为了降低一次性的HTTP 请求数量,当图片很多时,或者同时在线人数较多时,图片懒加载可以起到很好的性能优化的作用。
实现步骤:
1)设置自定义属性 data-src 来存储图片资源;
2)页面初始化或者在滚动时判断图片是否出现在可视区域;
3)在可视区域的话,将自定义属性 data-src 的值赋值给 src 属性。
class LazyLoad {
constructor(tag) {
this.boundTop = 0;
this.clientHeight = 0;
this.timer = null;
this.aImg = Array.from(document.querySelectorAll(tag));
this.init();
}
isShow(el) {
this.boundTop = el.getBoundingClientRect().top;
this.clientHeight = document.documentElement.clientHeight;
return this.boundTop <= this.clientHeight - 100;
}
canLoad() {
for(let i=0;i<this.aImg.length;i++) {
let img = this.aImg[i]
if(this.isShow(img)) {
img.src = img.dataset.src
//使用 for 循环是为了在改动数组后来操作 i,使得正确遍历每个元素
this.aImg.splice(i, 1)
i--
}
}
}
addEvent() {
window.addEventListener('scroll', () => {
if(!this.aImg.length && this.timer) return;
this.timer = setTimeout(() => {
this.canLoad();
this.timer = null;
}, 200);
});
}
init() {
this.canLoad();
this.addEvent();
}
}
let lazy = new LazyLoad('img');
图片预加载:在需要显示图片之前,就加载完毕,当需要显示图片时,就从缓存中取图片,在图片不是特别多的时候,可以使用预加载。
有多种实现方式,以下为较喜欢的一种:
<script type="text/javascript">
//使用 Image 对象来实现
let imgs = [];
function preload() {
for(let i=0; i<arguments.length;i++) {
//因为有这个对象,所以不用像生成其他对象那样来创建新对象
imgs[i] = new Image();
//这时就会下载远程图片到本地
imgs[i].src = arguments[i];
}
}
preload(
"http://img4.imgtn.bdimg.com/it/u=951914923,777131061&fm=26&gp=0.jpg",
"http://domain.tld/gallery/image-002.jpg",
"http://domain.tld/gallery/image-003.jpg"
);
//将图片都缓存到了 imgs 数组中,当需要显示它时,就从里面取
</script>
10、实现页面加载进度条
<body>
<div style="position:fixed;left:0;top:0;z-index:99;width:0%;height:3px;background:#24e1b6;" id="bar"></div>
<img src="./img/1.png" />
<img src="./img/2.png" />
<img src="./img/3.png" />
<img src="./img/4.png" />
<img src="./img/5.png" />
</body>
<script>
(function(){
let count = 0;
let script = ['./js/lottie.js', './js/1.js', './js/2.js', './js/3.js'];
let link = ['./css/1.css', './css/2.css', './css/3.css', './css/4.css', './css/5.css'];
let img = document.querySelectorAll('img');
let num = img.length + script.length + link.length;
for(let i1 = 0, len1 = img.length; i1 < len1; i1++) {
(function(j1){
img[j1].onload = function () {
count++;
console.log('img=' + count);
document.querySelector('#bar').style.width = (count / num) * 100 + '%';
};
})(i1);
}
for(let i2 = 0, len2 = script.length; i2 < len2; i2++) {
//这里使用立即执行函数,是让这些变量成为临时变量,而不是全局变量,全局对象以参数的形式传入,即修改参数不会污染全局变量
(function(j2){
let scriptE = document.createElement('script');
scriptE.src = script[j2];
scriptE.onload = function () {
count++;
console.log('script=' + count);
document.querySelector('#bar').style.width = (count / num) * 100 + '%';
};
document.head.appendChild(scriptE);
})(i2);
}
for(let i3 = 0, len3 = link.length; i3 < len3; i3++) {
(function(j3){
let linkE = document.createElement('link');
linkE.href = link[j3];
linkE.rel = 'stylesheet';
linkE.onload = function () {
count++;
console.log('script=' + count);
document.querySelector('#bar').style.width = (count / num) * 100 + '%';
};
document.head.appendChild(linkE);
})(i3);
}
})();
</script>
如果是Ajax 请求,那么就使用 XHR 的 progress 事件,事件处理程序接受一个 event 参数,这个参数有三个属性:lengthComputabel:表示进度信息是否可用,为一个布尔值;potion:表示已经接收的字节数;totalSize:便是根据 Content-Length 响应头部确定的预期字节数,即需要加载的总字节数
11、this 关键字
在全局环境中,this 指向 window 对象,ES5 函数中,this 对象是在运行时基于函数的执行环境(变量对象,如全局是 window),匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window(在非严格模式下),在严格模式下 this 指向的是 undefined,为了在严格模式下,this 重新指向 window,可以使用非直接调用 eval 的方式,如 (0, eval)('this'),使用了逗号运算符,括号前面会返回 eval,但是和直接调用的区别就是在严格模式下 this 指向不一样。ES6 的箭头函数中,this 对象是在函数定义的执行环境。
12、函数式编程
函数式编程中的函数指的数学概念中的函数,即自变量的映射,得到的结果由输入的值决定,不依赖于其他状态,是声明式(依赖于表达式),而非命令式,组合纯函数来构建软件的编程方式。
13、手动实现 parseInt
几乎与原生parseInt 的结果一样,如果有不同的结果,请一定留言告诉我
function compare(str, radix) {
let code = str.toUpperCase().charCodeAt(0),
num;
if(radix >= 11 && radix <= 36) {
if(code >= 65 && code <= 90) {
num = code - 55;
}else {
num = code - 48;
}
}else {
num = code - 48;
}
return num;
}
function isHex(first, str) {
return first === '0' && str[1].toUpperCase() === 'X'
}
function _parseInt(str, radix) {
str = String(str);
if(typeof str !== 'string') return NaN;
str = str.trim();
let first = str[0],
sign;
//处理第一个字符为 '-' || '+' 的情况
if(first === '-' || first === '+') {
sign = str[0];
str = str.slice(1);
first = str[0];
}
//当 radix 不存在或者小于 11 时,第一个字符只能为数字
if(radix === undefined || radix < 11) {
if(isNaN(first)) return NaN;
}
let reg = /^(0+)/;
//截取 str 前面符合要求的一段,直到遇到非数字和非字母的字符
let reg2 = /^[0-9a-z]+/i;
str = str.match(reg2)[0];
let len = str.length;
//在没有第二个参数时或者不是数字时,给第二个参数赋值
//isNaN('0x12')会执行 Number('0x12') 可以转换成十进制
if(radix === undefined || isNaN(radix)) {
if(len === 1) return str;
//如果 str 是十六进制形式,就转换成十进制
if(isHex(first, str)) {
return Number(str);
}else {
radix = 10;
}
}else {
//如果有第二个参数,并且是数字,要处理第二个参数
radix = String(radix);
//如果有小数点,取小数点前面一段,处理不为整数的情况
radix = radix.split('.')[0];
//如果 radix 等于零的话,就按照 str 来判断 radix 的基数
if(radix === '0') {
if(isHex(first, str)) {
return Number(str);
}else {
radix = 10;
}
}
//如果 radix 前面有零将零去除,十六进制除外
if(radix.length > 1) {
let twoR = radix[1].toUpperCase();
if(radix[0] === '0' && twoR !== 'X') radix = radix.replace(reg, '');
}
//如果 radix 是十六进制的字符串类型,也会转变成十进制的数字类型
radix = Number(radix);
//radix是否在正确的区间
if(radix >= 2 && radix <= 36) {
//如果 radix 为 16,且 str 是十六进制形式的话,直接将十六进制转换成十进制
if(radix === 16 && isHex(first, str)) return Number(str);
}else {
//只要 radix 是一个有效的数字,但不在正确的区间里,就返回 NaN
return NaN;
}
}
//去除 str 前面的零
str = str.replace(reg, '');
if(str.length === 0) return 0;
let strArr = str.split(''),
numArr = [],
result = 0,
num;
for(let i=0; i<strArr.length; i++) {
num = compare(strArr[i], radix);
if(num < radix) {
numArr.push(num);
}else {
break;
}
}
let lenN = numArr.length;
if(lenN > 0) {
numArr.forEach(function(item, index) {
result += item * Math.pow(radix, lenN - index -1);
});
}else {
//str开头有零的话要返回零
return first === '0' ? 0 : NaN;
}
if(sign === '-') result = -result;
return result;
}
14、为什么会有同源策略
同源策略限制从一个源加载的文档或脚本如何与另一个源的资源进行交互。这是用于隔离潜在恶意文件的关键安全机制。
同源策略:协议相同、域名相同、端口相同,三者都必须相同
什么叫限制:不同源的文档不能操作另一个源的文档,在以下几个方面操作不了:
1)Cookie、localStorage、indexDB 无法读取2)DOM 无法获得3)AJAX 请求无法发送
15、怎么判断两个对象是否相等
1)判断引用是否为同一个引用;
2)如果是不同引用,判断长度是否相同;
3)通过 Object.getOwnpropertyNames(a) 拿到所有属性,判断是否有相同的属性 key,如果相同,再判断值是否相同。
参考资料
16、事件模型
一个完整的事件流分为三个阶段:第一阶段是捕获,第二阶段是目标阶段,第三阶段是目标元素通过冒泡上传到window 对象。这个过程就是用户和浏览器交互的过程,浏览器通过事件捕获知道了用户的操作,再通过事件冒泡将操作信息传递给浏览器。
//事件捕获的具体流程:
window -> document -> html -> body -> … ->目标元素
//事件冒泡的流程是反过来的
1)事件委托、代理
事件委托是将事件监听器绑定到父元素上,点击任意一个子元素,通过事件冒泡将事件传给父元素
event.currentTarget //传回事件绑定的这个元素
event.target //传回被点击的这个元素对象
2)如何让事件先冒泡后捕获
window.addEventListener('click', func, false);
//false为冒泡时触发事件,默认
//true为捕获时触发事件
//还可以这样写:
el.addEventListener(type, listener, {
capture: false, // useCapture
once: false, //是否设置单次监听
passive: false //是否让阻止默认行为preventDefault()失效
})
17、window 的 onload 事件和 DOMContentLoaded 事件
第一个是当页面所有的资源(图片、音频、视频等)全部加载完毕才会触发第二个是当DOM 结构加载完毕就会触发简单来说,页面的load 事件会在 DOMContentLoaded 被触发之后才触发。
18、for...in 迭代和 for...of 有什么区别
在循环对象属性时使用for...in,因为会将所有可枚举属性都遍历出来,但是有以下几个问题:
1)将自定义属性也会遍历出来;
2)遍历出来的属性顺序是随机的
在循环数组时使用for...of,我们可以配合迭代器,可以对复杂的、自定义的数据结构输出我们想要的结果,且不会输出自定义的属性:
//以 Symbol.iterator 为 key,以函数为值,为对象构建一个迭代器
Array.prototype[Symbol.iterator] = function () {
let arr = [].concat(this);
let getFirst = function(array) {
return array.shift();
};
return { //规定返回一个对象
next () { //规定返回的对象中必须有 next 方法
let item = getFirst(arr);
if(item) {
return { //规定 next 方法必须返回一个对象,有以下两个属性
value: item, //这里做了隐式类型转换,进行了 toString()
done: false
};
}else {
return {
done: true
};
}
}
}
};
let arr = ['a', ['b', 'c'], 2, ['d', 'e', 'f'], 'g', 3, 4];
function flat() {
let r = [];
for(let i of arr) { //调用 for...of 就是内部不断调用了 next()
r.push(i);
}
return r.join(',');
}
console.log(flat(arr));
//结果为:a,b,c,2,d,e,f,g,3,4
//当然还可以使用递归和类型转换来达到目的
如果想输出带有顺序的对象属性值的话,要配合Object.keys() 来使用:
let obj = {};
for(let key of Object.keys(obj)) { //这里 for...of 迭代的是数组
console.log(obj[key]);
}
在使用它们时,建议加上let 来使用,且在使用 for...of 时,要注意迭代对象是否是可迭代的对象,即是否有迭代器,对象默认是没有迭代器的,只有数组有迭代器,无法直接使用 for...of。
//比如无法直接迭代对象
let obj = {};
for(let key of obj) {} //会报错
19、函数柯里化
函数柯里化是将多参数或者差异化参数转变成单参数或者无差异化参数的一种函数式编程的运算方式。这么做的好处是可以将核心逻辑跟其他逻辑(包括环境判断等只需要一次性操作的逻辑)分离开来,做法是使用一个函数返回一个函数,在第一个函数中执行其他逻辑,并且对差异化参数进行处理,然后返回一个无差异参数且只有核心逻辑代码的函数,以后每次执行都只需要执行这个函数。在vue.js 源码中大量使用了函数柯里化方式。举一个 ajax 的例子:
let ajax = (function() {
let oAjax = window.XMLHttpRequest ? (new XMLHttpRequest()) : (new window.ActiveXobject('Microsoft.XMLHTTP'));
return function(url, fnSucc, fnFaild) {
oAjax.onreadystatechange = function() {
if(oAjax.readyState === 4) {
let s = oAjax.status;
if(s === 200 || s === 206 || s === 304) {
if(fnSucc) fnSucc(oAjax.responseText);
}else {
if(fnFaild) fnFaild(oAjax.status);
}
}
};
oAjax.open('GET', url, true);
oAjax.send(null);
}
})();
//如此环境判断只需要执行一次,当然这样又因为使用了闭包,要保存外部函数的作用域,占用更多的内存,所以也这都是有一定的取舍的
20、call,apply,bind 三者用法和区别,原生实现 bind
function fn(num1, num2) {
console.log(num1 + num2);
console.log(this);
}
fn.call(obj , 100 , 200); //call是一个一个传入参数
fn.apply(obj , [100, 200]); //apply是传入一个数组
//它们都是将第一个参数替换 fn 函数中的 this 关键字,然后执行 fn 函数
//只是替换 this 关键字,不执行 tempFn 函数
var tempFn = fn.bind(obj, 1, 2);
//在需要执行时,执行这个函数
tempFn();
//原生实现 bind
Function.prototype.bind2 = function(newThis) {
var aArgs = Array.prototype.slice.call(arguments, 1) //拿到除了newThis之外的预置参数序列
var that = this
return function() {
return that.apply(newThis, aArgs.concat(Array.prototype.slice.call(arguments)))
//绑定this同时将调用时传递的序列和预置序列进行合并,比如 tempFn(3) 其中 3 就是调用是传递的序列
}
}
//原生实现 call
Function.prototype.call2 = function(context, ...arg) {
context = context || window;
context.fn = this; //调用 call2 的函数就是 this,比如这里的 show 函数
let result = context.fn(...arg); //调用 show 函数,这个函数里面的 this 是执行环境中的对象
delete context.fn; //不要污染 context 对象
return result;
}
function show (){
console.log(this);
}
let obj = {haha: '123'}
show.call2(obj); //跟原生的 call 还是有差异的,会将对象的 __proto__ 属性也显示出来
show.call(obj);
21、async/await
//await一般会返回一个 promise 的表达式
//await只能放在async函数里,await 后面的代码是异步执行的,就像使用了 then 来注册回调函数一样
function doubleAfter2seconds(num) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2 * num)
}, 2000);
} )
}
//aync函数会返回一个 promise 对象
async function testResult() {
let result = await doubleAfter2seconds(30);
console.log(result);
}
testResult();
22、立即执行函数和使用场景
通过定一个匿名函数,创建了一个新的函数作用域,该命名空间的变量和方法,不会污染全局的命名空间(反过来也一样)。如果在这个函数作用域中要访问全局对象,将全局对象以参数形式传入进去,虽然函数体内可以直接访问全局对象,但为了不污染全局的命名空间,所以以参数形式传入,那么对这个参数的修改不会污染全局变量。
有多种方式使用立即执行函数:
//一般使用第一种
(function(num) {
console.log(num);
})(123);
(function(num) {
console.log(num);
}(123));
!function(num) {
console.log(num);
}(123);
+function(num) {
console.log(num);
}(123);
-function(num) {
console.log(num);
}(123);
//使用运算符 =
let fn = function(num) {
console.log(num);
}(123);
23、设计模式(要求说出如何实现,应用,优缺点)/单例模式实现
单列模式工厂模式观察者模式适配器模式代理模式侨接模式外观模式访问者模式模仿方法模式中介者模式迭代器模式组合模式备忘录模式职责链模式享元模式状态模式
24、iframe的缺点有哪些
1)页面太多不好管理;2)框架个数多的话,会出现上下左右滚动条,会分散访问者的注意力,用户体验度差;3)代码复杂,不利于搜索引擎;4)设备兼容性差,一些移动设备无法完全显示框架;5)增加服务器 HTTP 请求。
25、数组问题
1)数组去重
2)数组常用方法
3)扁平化数组
4)按数组中各项和特定值差值排序
26、BOM 属性对象方法
BOM 提供了很多对象,用于访问浏览器的功能
window 对象扮演全局对象的角色,所有在全局作用域中声明的变量和函数都会变成 window 对象的属性和方法,以下列出操作浏览器的方法:
窗口关系即框架:
window.frames[0] 每个框架都有自己的全局对象,并且保存在 frames 集合中;window.parent 指向当前框架的上层框架,如果是当前框架是上层框架,则 parent 就等于自身的 window 对象;
窗口位置:用来确定和修改window 对象位置的属性和方法
window.screenLeft/screenTop 分别用于表示窗口相对于屏幕(电脑显示屏)左边和上边的位置,在火狐中是 window.screenX/screenY
确定窗口大小:
四个属性:innerWidth innerHeight outerWidth outerHeight,不同浏览器返回的具体值不同,由于有浏览器兼容性问题,选择用:document.documentElement.clientWidth/clientHeight 用来取得页面视口的大小
导航和打开窗口:
使用window.open(url, frameName) 方法可以导航到一个特定的 url,也可以打开一个新的浏览器窗口,此方法返回指向新窗口的引用,第二个参数还可以是一个特殊的窗口名称:_self _parent _top _blank
let frame = window.open(url, frameName);
frame.close();
alert(frame.closed); //true
系统对话框:
alert() 只显示 ok 按钮confirm() 显示 ok 按钮和 cancel 按钮prompt(文本提示,输入域默认值) 这是一个提示框,显示文本输入域,供用户在其中输入内容
location 对象,提供了与当前窗口中加载的文档有关的信息,还提供了一些导航功能。其用处不只表现在它保存着当前文档的信息,还表现在它将 url 解析为独立的片段,使开发人员可以通过不同的属性访问这些片段。
hash:url 中包含 '#' 即后面的内容,如果没有则返回空字符串host:返回服务器名称和端口号(如果有)hostname:返回不带端口号的服务器名称href:返回当前加载页面的完整 urlpathname:返回 url 中的目录或文件名port:返回端口号protocol:返回页面使用的协议search:返回 url 中包含 '?' 即后面的内容,即查询字符串
每次修改location 的属性(hash 除外),页面都会以新 url 重新加载
位置操作:
location.assign(url) 立即打开新 url 并在浏览器的历史记录中生成一条记录。以下方式都是内部调用了这个方法:
window.location = url;
location.href = url;
location.replace(url) 方法导致页面改变,但不会在历史记录中生成新记录,调用这个方法后,用户不能回到前一个页面。
location.reload() 方法没有参数时,会使页面重新加载,如果有缓存就从缓存中获取,如果要强制从服务器重新加载,传入参数 location.reload(true)
navigator 对象,通常用于检测显示网页的浏览器类型,根据浏览器的不同有不同的属性可以用,主要讲下三个通用的又实用的属性:
1)appCodeName 浏览器的名称,除了 IE 浏览器之外,基本都返回:"Netscape"2)online 指明系统是否处于脱机状态,返回布尔值3)userAgent 返回客户端的完整信息
screen 对象,在编程中用处不大,基本上只用来表明客户端的能力,其中包括浏览器窗口外部的显示器的信息,如像素宽度和高度等。这些信息经常集中出现在测定客户端能力的站点跟踪工具中。列举一个属性(只有 IE 浏览器支持):
deviceXDPI:返回显示屏幕的每英寸水平点(像素)数。返回的其实就是分辨率,而那个水平点数中的一点就是一像素,DPI 是显示分辨率的一个比例值,DPI 越高说明分辨率越高,如果安卓 DPI 比率分辨值为 1 的话,苹果必然是 2,苹果的像素高
history 对象保存着用户上网的历史记录,其有一个 length 属性,保存这历史记录的数量
history.go() 方法可以在用户的历史记录中任意跳转,可以向后也可以向前,接收一个整数值作为参数;还可以传递一个字符串参数,此时浏览器会跳转到历史记录中包含该字符串的第一个位置,可能前进可能后退,具体要看哪个位置更近,如果不包含该字符串,那么这个方法什么都不做。可以用back() 来后退,forward() 来前进
history.pushState(state/null, title/null, url) 和 history.replaceState() ,第二个 API 不会加入到历史记录中,用法和第一个相同,第一个 API 的第三个参数必须是与当前页面处于同一个域,它们的作用是使 url 跳转而无需重新加载页面,不会触发 popstate 事件,只有在执行了 history.back history.forward 以及 history.go,或者点击了浏览器的前进后退或者跳转按钮(双击前进或者后退按钮),就会触发 popstate 事件。
vue 的编程式导航利用了 location.hash 属性以及 window.history 上面两个 API 实现页面跳转但不重新加载页面的效果。
27、服务端渲染
服务端渲染指的是后端(比如JAVA PHP)生成 html 文件,客户端发送 http 请求直接获得 html 文件然后直接显示出来,优点是首屏渲染快,有利于 SEO 搜索引擎,缺点是页面切换慢,因为都要发起 http 请求;客户端渲染指的是客户端通过ajax 请求数据,首次渲染通过 http 请求 JS 和 CSS 文件,然后在客户端拼接成 html 文件,再显示出来,优点是页面切换快(通过 JS 渲染,动态更新页面),但首屏渲染慢,且不利于 SEO 搜索引擎,React 和 Vue 等 MVVM 框架都是客户端渲染。如果对于百度的SEO 有需求(因为谷歌已经可以支持 JS 动态网页),那么就考虑使用前端同构,指的是原本在客户端生成 html 文件,在中间层 node 环境来生成 html 文件,首屏渲染是在 node 环境生成,之后页面切换还是在客户端,中间层和客户端使用一套 JS 代码,最终都是将虚拟 DOM 转换成 html 文件,因为虚拟 DOM 本质上是一个 JS 对象,所以可以跑在 node 环境。React 的前端同构的支持框架是 next.js,Vue 的前端同构的支持框架是 nuxt.js,在使用这些框架时,要特别注意 node 环境和客户端环境是不同的,比如说没有 window 对象的,因此使用前端框架会报错,当使用这些框架时,要声明只在客户端渲染,再如引入 npm 包,带有 DOM 操作的,不能用 import 来引入,要使用 require() 方式,总之一套代码在两个环境下运行,会遇到很多坑。因此学习成本也是较高的。
28、垃圾回收机制
JS 具有自动垃圾收集机制,执行环境会负责管理代码执行过程中使用的内存,所需内存的分配以及无用内存的回收完全实现了自动管理。垃圾收集机制的原理:找到那些不再继续使用的变量,然后释放其占用的内存。垃圾收集机制会按照固定的时间间隔周期性地执行这一操作。垃圾收集机制必须跟踪哪个变量有用哪个变量无用,对于不再有用的变量打上标记,以备将来收回其占用的内存。JS 中最常用的跟踪方法是标记清除,当函数执行完后,就会给局部变量打上“离开环境”的标记(除了闭包),在下一次垃圾回收时间到来时就会清除这一块内存,手动将一个有值的变量赋值为 null,也是让这个值离开环境,也可以释放内存。还有一种跟踪方法是引用计数,这会引起循环引用的问题,但现在所有的浏览器都使用了标记清除式的垃圾回收机制,所以不用考虑这个问题了。
29、eventloop
1)进程和线程
进程是一个程序完成的过程= CPU 加载上下文 + CPU 执行 + CPU 保存上下文
线程是构成一个程序的任务组合,在CPU 执行阶段就是执行这一个一个任务
不同进程间数据很难共享,不同线程间数据容易共享进程可以拓展到多机(多进程),线程最多适合多核(多线程),多核中可以开启多进程,比如浏览器打开一个新的页面(除了多个空白页面外),就会创建一个进程某个线程使用某些共享内存时,可以上一把“互斥锁”,其他线程只能排队等待,防止多线程同时读取某一块内存区域某些内存区域只能供给固定的数目线程使用,其他线程排队等待,这叫“信号量“,保证多线程不会互相冲突
2)任务队列
JS 是单线程的,即同一时间只能完成一个任务
任务队列也可以叫异步队列,当一个事件被触发时,就会将回调函数体扔进这个任务队列中,当运行栈的同步任务执行完毕时(一个tick 完成),就会去任务队列中取异步任务,然后执行(此时为下一个 tick)。所以 JS 遇到一个异步任务时会挂起,而不是立即执行。
30、如何快速让字符串变成以千为精度的数字
我也不知道理解题目是否到位,应该是为了考Number() 的精准度的问题,代码如下:
(Number('12.69598')*1000).toFixed(0)
//Number('12.69598')*1000得到的结果为 12695.970000000001
//通过 toFixed() 完全去除小数点
ES6--http://es6.ruanyifeng.com/