高级前端软件工程师知识整理之基础篇(一)
1. 说出几点前端优化的方法?
(1)减少网络请求次数。优化情形:客户端向服务端请求大量资源,如图片、js文件等。网络请求遵循HTTP协议,每一次请求都是与服务端的独立通信,在建立通信过程需要时间,如果请求次数过多会造成页面反应迟钝,遇到这种情形,可以将资源文件合并压缩成一个文件来加载,通过实现减少与服务端资源请求次数达到优化效果。
(2)数据增量加载。优化情形:客户端向服务端API接口请求大量数据,数据量甚至达到几兆。增量加载,是指先请求部分页面数据,足够把一屏页面显示出来,再分批量去请求其它数据,不必一次就要把所有数据都请求进来。
(3)静态资源使用CDN服务。优化情形与第一点相似,有些页面需要大量的图片资源,这类资源很少需要去更新但又无法再压缩,这种情形可以使用CDN服务,如阿里云的CDN服务。
(4)代码优化。代码优化可以是算法优化、路由懒加载、组件缓存、打包时删除不必要的注释和脚本等。
2. 闭包是什么?介绍闭包的使用场景以及为什么闭包没有清除?
闭包就是能够读取其他函数内部变量的函数,也可以理解为定义在一个函数内部的函数,匿名函数也是闭包的一种。
请看这段代码:
function f1() {
var n = 1;
return function f2() {
alert(n);
}
}
代码中f2函数就是闭包。要理解闭包,首先要理解javascript的特殊的变量作用域,变量的作用域无非就两种:全局变量和局部变量。函数内部可以直接读取全局变量,但是在函数外部无法读取函数内部的局部变量。闭包的作用就是实现从函数的外部也可以读取到函数内部的变量。闭包除了上例写法,还可以由外部传入函数,返回函数执行结果:
function f1(f2) {
var n = 1;
return f2(n);
}
闭包的使用场景很多,最常用的有两种:一个是前面提到的可以读取函数内部的变量,另一个就是让父函数(如 f1)的局部变量始终保持在内存中,不会在调用后被自动清除。为什么闭包没有清除,随意使用会造成内存泄漏?原因就在于f1函数的执行结果是返回f2,把f2变成了一个window下的全局变量,即f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制回收。因此,在使用闭包时要注意两点:
- 由于闭包会使得父函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法:在退出函数之前,将不使用的局部变量全部删除,如 var fn = f1(); fn=null。
当闭包格式为为自执行函数时,如下面这段代码,n值将会作为f1函数作用域中的全局变量只初始化一次,这里不建议外部函数对父函数里的变量进行修改(下面例子中的n值),外部函数仅使用数据而不操作。
function todo(n) {
console.log(n);
}
var test = (function(fn) {
var n = 0;
return function(){
n++;
fn(n);
};
})(todo);
test(); // 1
test(); // 2
test(); // 3
3. jsonp实现的原理是什么?
浏览器是不允许跨域请求服务端数据的,但是允许跨域请求js格式文件和带src属性标签指向的资源,jsonp就是利用了这个特性实现跨域请求。它的原理是:通过带src的标签向服务端发起GET请求,请求结果是由服务端包装好的一个js格式文件,当该文件加载到浏览器后会触发客户端事先定义好的一个全局回调函数,其入参即为服务端响应的数据。
我们来看代码,由前端定义一个处理数据的回调函数cbHandler并开始发送请求:
<script type="text/javascript">
// 回调函数
var cbHandler = function(json){
console.log(json);
};
var url = "url?param=data&callback=cbHandler"; // 向服务端发送get请求,参数在问号后面传递
var script = document.createElement('script'); // 创建script标签,设置其属性
script.setAttribute('src', url);
document.getElementsByTagName('head')[0].appendChild(script); // 把script标签加入head,此时调用开始
</script>
请求结果为服务端包装好的一个js格式文件,它的数据结构如下:
cbHandler(JSON.stringify(Object));
服务端响应结果将会作为入参,在请求成功时等于浏览器解析了该js格式文件,最终实现调用客户端的cbHandler函数。
通常我们不用那么麻烦,可以直接使用ajax封装好的jsonp请求功能,实现代码如下:
<script>
jQuery(document).ready(function() {
$.ajax({
type: "get",
async: false,
url: "",
dataType: "jsonp",
jsonp: "callback", //传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(一般默认为:callback)
jsonpCallback: "cbHandler", //自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名,也可以写"?",jQuery会自动为你处理数据
success: function(json) {
console.log('success');
},
error: function() {
console.log('fail');
}
});
});
</script>
4. new函数是怎么实现的?
new函数的实现是一个原型指向和数据劫持的过程,如:
function Person(sex) {
this.sex = sex;
}
Person.prototype.name = 'John';
Person.prototype.age = '30';
Person.prototype.getName = function() {
return this.name;
}
var obj = new Object();
obj.__proto__ = Person.prototype; // 新对象原型指向Person原型
Person.call(obj, '男'); // 数据劫持,劫持Person中this内容
console.log(obj);
代码中obj对象等同于new Person()对象。
5. 数组的操作有哪些?
push(value) 方法用于在数组的末端添加一个或多个元素(用逗号隔开),并返回添加新元素后的数组长度。该方法会改变原数组。
pop() 方法用于删除数组的最后一个元素,并返回该元素。该方法会改变原数组。
join(value) 方法以参数value作为分隔符分隔数组,返回一个字符串。如果不提供参数,默认用逗号分隔。该方法不会改变原数组。
concat(value) 方法用于数组合并。将新数组value添加到原数组后面,返回一个合并后的新数组,两个原数组均不变。
另外,concat 方法也可以用于将多个对象合并为数组,入参的对象间用逗号隔开。如:
[].concat({a: 1}, {b: 2}) // [{ a: 1 }, { b: 2 }]
shift() 方法用于删除数组的第一个元素,并返回该元素。该方法会改变原数组。
unshift(value) 方法用于在数组的第一个位置添加一个或多个元素(用逗号隔开),并返回添加新元素后的数组长度。该方法会改变原数组。
reverse() 方法用于颠倒数组中元素的顺序,返回改变后的数组。该方法将改变原数组。
slice(startindex, endindex) 方法用于提取原数组的一部分,返回提取出来的新数组。索引值从0开始计数。该方法不会改变原数组。
var a = ['a', 'b', 'c'];
a.slice(0) // ["a", "b", "c"]
a.slice(1) // ["b", "c"]
a.slice(1, 2) // ["b"]
a.slice(2, 6) // ["c"]
a.slice() // ["a", "b", "c"] 相当于复制数组
如果slice
方法的参数是负数,则表示倒数计算的位置。
var a = ['a', 'b', 'c'];
a.slice(-1); // ["c"]
a.slice(-2) // ["b", "c"]
a.slice(-2, -1) // ["b"]
如果参数值大于数组成员的个数,或者第二个参数小于第一个参数,则返回空数组。
var a = ['a', 'b', 'c'];
a.slice(4) // []
a.slice(2, 1) // []
splice(index, num_to_remove, addElement1, addElement2, …) 方法用于删除原数组的一部分成员,并可以在被删除的位置添加入新的数组成员,返回值是被删除的元素。索引值从0开始计数。如果只是单纯地插入元素,splice
方法的第二个参数可以设为0。该方法会改变原数组。
var a = ['a', 'b', 'c', 'd', 'e', 'f'];
a.splice(4, 2) // ["e", "f"]
a // ["a", "b", "c", "d"]
var b = [1, 1];
b.splice(1, 0, 2) // []
b // [1, 2, 1]
sort(fun) 方法对数组成员进行排序,排序后,原数组将被改变。当没有参数时按升序排列,sort也可以接受一个函数作为参数,以便我们指定哪个值位于哪个值的前面。比较函数接收两个参数,如果第一个参数应该位于第二个之前则返回一个负数,如果两个参数相等则返回 0,如果第一个参数应该位于第二个之后则返回一个正数。该方法会改变原数组。
// 无参数时
var arr1 = ["a", "d", "c", "b"];
console.log(arr1.sort()); // ["a", "b", "c", "d"]
var arr2 = [13, 24, 51, 3];
console.log(arr2.sort()); // [13, 24, 3, 51]
console.log(arr2); // [13, 24, 3, 51](原数组被改变)
// 有参数时
var a = [3, 4, 1, 2, 5];
var b = a.sort(function(v1, v2){
return v1-v2;
});
console.log(a); // [1, 2, 3, 4, 5]
console.log(b); // [1, 2, 3, 4, 5]
map(callback) 方法对数组的所有成员依次遍历执行回调函数,根据函数结果返回一个新数组。其回调函数有三个入参,分别是当前成员、当前位置和数组本身。该方法不会改变原数组。
var a = [1, 2, 3];
var b = a.map(function(elem, index, arr) {
return elem * index;
});
console.log(a); // [1, 2, 3]
console.log(b); // [0, 2, 6]
forEach(callback) 方法与map方法很相似,也是遍历数组的所有成员,执行某种操作,但是forEach
方法一般不返回值,只用来读取数据。如果需要有返回值,一般使用map
方法。其回调函数与map一样有三个入参,分别是当前成员、当前位置和数组本身。forEach
方法无法中断执行,总是会将所有成员遍历完。该方法不会改变原数组。
var a = [1, 2, 3];
var b = a.forEach(function(elem, index, arr) {
return elem * index;
});
console.log(a); // [1, 2, 3]
console.log(b); // undefined
forEach
方法也可以接受第二个参数,用来绑定回调函数的this
关键字。
var obj = {
name: '张三',
times: [1, 2, 3],
print: function () {
this.times.forEach(function (n) {
console.log(this.name);
}, this);
}
};
obj.print()
// 张三
// 张三
// 张三
filter(callback) 方法用于数组过滤,所有数组成员依次执行回调函数,返回结果为true
的成员组成一个新数组返回。该方法不会改变原数组。
[1, 2, 3, 4, 5].filter(function (elem) {
return (elem > 3);
})
// [4, 5]
some(callback),every(callback) 两个方法类似“断言”(assert),用来判断数组成员是否符合某种条件。它们接受一个函数作为参数,所有数组成员依次执行该函数,返回一个布尔值。该函数接受三个参数,依次是当前位置的成员、当前位置的序号和整个数组。some
方法是只要有一个数组成员的返回值是true
,则整个some
方法的返回值就是true
,否则false
。
var arr = [1, 2, 3, 4, 5];
arr.some(function (elem, index, arr) {
return elem >= 3;
});
// true
every
方法则是所有数组成员的返回值都是true
,才返回true
,否则false
。
var arr = [1, 2, 3, 4, 5];
arr.every(function (elem, index, arr) {
return elem >= 3;
});
// false
reduce()方法,调用如下:
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
依次处理数组的每个成员,最终累计为一个值。执行函数中有4个参数,其中前两个是必选参数,后两个是可选参数:total累计变量,currentValue当前数组元素,currentIndex当前数组索引,arr原数组。initialValue为累计的初始值。
var a = [1, 2, 3, 4, 5].reduce(function(x, y){
console.log(x, y)
return x + y;
}, 10);
console.log(a);
// 最后结果:25
// 累加过程如下:
// 10 1
// 11 2
// 13 3
// 16 4
// 20 5
indexOf(),lastIndexOf() indexOf 方法返回给定元素在数组中第一次出现的位置,如果没有出现则返回-1
。lastIndexOf 方法返回给定元素在数组中最后一次出现的位置,如果没有出现则返回-1
。
var a = 'hello';
console.log(a.indexOf('l')); // 2
var a = ['a', 'b', 'c'];
console.log(a.indexOf('b')); // 1
Array.isArray() 方法用来判断一个值是否为数组。
var a = [1, 2, 3];
Array.isArray(a) // true
技巧:上面数组操作中,改变原数组的操作有:push、pop、shift、unshift、reverse、splice和sort。
还没有评论,来说两句吧...