1.重绘和重排问题
2.add(2,3)和add(2)(3)问题(再考虑他的拓展性拓展),其实是考察js函数柯里化问题。
curry 的概念很简单:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。
function add1(a){
var sum=a
var tmp=function(b){
sum= sum+ b;
return tmp;
}
tmp.valueOf=function(){
return sum;
}
return tmp;
}
var result=add1(2)(3)(4)(5)(6);
console.log('%d',result); //输出14 '%d'会把result转换为10进制,从而触发tmp的valueOf函数
因为到最后tmp(6)之后,返回的是个tmp,如果输出tmp
的话,打印的是tmp这个函数对象,如下:
console.log(result) :
{ [Function: tmp] valueOf: [Function] }
所以我们要把result转换为10进制,console.log('%d',result);
'%d'会把result转换为10进制,从而触发tmp的valueOf函数,输出sum
还有一种方法是利用ES6语法中的Proxy,这个方法是我在StackOverflow上看到的
function add(n) {
sum = n;
const proxy = new Proxy(function a() {}, {
get (obj, key) {
return () => sum;
},
apply (receiver, ...args) {
sum += args[1][0];
return proxy;
}
});
return proxy
}
console.log(add(1)(2)(3)); //6
console.log(add(1)(2)(3)(4)(5)(6)); //21
proxy可以理解是对目标对象进行代理,外界对该对象的访问,都必须先通过这层拦截。
ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。
var proxy = new Proxy(target, handler);
Proxy 对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。上面的代码就主要用到了proxy的get 和 apply
具体Proxy的使用可以学习阮一峰的ECMAScript 6 入门
其实我在看完Proxy和他的用法之后,并不知道proxy有什么优势,只觉得它是另一种方法,而且有点难理解。可能是我还不知道proxy的优势吧。
3.ES6中箭头函数和普通函数的区别。
1.this的区别。普通函数如果是作为对象的方法被调用的,则其 this 指向了那个调用它的对象;但是箭头函数的则会捕获其所在上下文的 this 值,作为自己的 this 值。箭头函数需要记着这句话:“箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this.
var obj = {
i: 10,
b: () => console.log(this.i, this),
c: function() {
console.log( this.i, this)
}
}
obj.b(); // prints undefined, Window
obj.c(); // prints 10, Object {...}
2.箭头函数不绑定arguments,取而代之用rest参数 … 解决 (rest参数介绍 )
function consol() {
console.log(arguments)
}
consol('haha',1) // { '0': 'haha', '1': 1 }
var conso = (...args)=>{ console.log(args)}
conso('hahaha',1) // [ 'hahaha', 1 ]
4.javascript闭包问题。在网上看了很多关于对于什么是闭包的解释。
看过一个答案,我觉得挺通俗易懂的。
闭包
就是把函数以及其所依赖的所有外部自由变量保存在一起的结合体
闭包
就是一个函数把外部的那些不属于自己的对象也包含(闭合)进来了。
参见:如何通俗易懂的理解闭包
闭包的牛逼解答
这两者都很好理解。看一段代码:
var foo = function(){
var name = "exe";
return function inner(){
console.log( name );
}
}
var bar = foo();//这里虽然得到的是函数inner的引用,而不是那一坨代码
bar();//这里开始执行inner函数 "exe" 读取foo函数内部的变量
这里面的 name 就是相对于函数 inner( )的外部自由变量。所以inner函数就是一个闭包。
闭包的作用
它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。(因为闭包引用了上一级的环境,所以导致上一级也释放不了。拿上例来说,inner()函数引用name,而这个name是上一级的变量。这导致inner始终在内存中,而inner的存在依赖于foo,因此foo也始终在内存中,不会在调用结束后,被垃圾回收机制回收。
闭包的原理(为什么能够访问外部的自由变量)
其实就是作用域链。关于作用域对象及作用域链的讲解:
推荐一篇博客: JavaScript闭包的底层运行机制
最后:
闭包是什么时候被创建的?因为所有JavaScript对象都是闭包,因此,当你定义一个函数的时候,你就定义了一个闭包。
闭包是什么时候被销毁的?当它不被任何其他的对象引用的时候。
5.实现数组扁平化处理。
一种方法是用递归的方式。
var arr = [1, [2, [3, 4]]];
function flatten(arr) {
var result = [];
for (var i = 0, len = arr.length; i < len; i++) {
if (Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]))
}
else {
result.push(arr[i])
}
}
return result;
}
console.log(flatten(arr))
另一种是用ES6的扩展运算符 ...
扩展运算符 ... 可以把一个数组转换为参数序列
var arr = [1, [2, [3, 4]]];
console.log(...arr) // ...运算符把数组arr 转换为 1 [2,[3,4]] 两个参数
再用
arr = [].contact(...arr) // [1,2,[3,4]
这样就去掉了数组的一层 顺着这个方法一直继续下去,就写出了如下代码
function flatten(arr) {
while (arr.some(item => Array.isArray(item))) { //some函数会对每一个数组元素执行一次箭头函数
var arr = [].concat(...arr);
}
return arr;
}
console.log(flatten(arr))
中间可以输出一下函数执行的过程。
1 [ 2, [ 3, [ 4, 5 ] ] ]
[ 1, 2, [ 3, [ 4, 5 ] ] ]
1 2 [ 3, [ 4, 5 ] ]
[ 1, 2, 3, [ 4, 5 ] ]
1 2 3 [ 4, 5 ]
[ 1, 2, 3, 4, 5 ]
奇数行是...arr 的执行结果
偶数行是[].concat(...arr); 的执行结果
6.es6和es5中的继承
ES5继承
es5的继承实际上是用原型链实现的,将子类的原型设置为父类的实例,从而实现继承
function father(){
this.flag = true;
}
function child(){
this.childFlag = false;
}
// 将子类的prototype对象指向父类的实例,实现继承
child.prototype = new father();
var child1 = new child();
console.log(child1.__proto__ === child.prototype) // true
这张图好像是我在知乎上看到的。
ES6继承
es6继承就很容易理解了,差不多就是java的class
详细学习可以看阮一峰的ES6入门
7.数组去重
indexOf
var array = [1, 1, '1'];
function unique(array) {
var res = [];
for (var i = 0, len = array.length; i < len; i++) {
var current = array[i];
if (res.indexOf(current) === -1) {
res.push(current)
}
}
return res;
}
console.log(unique(array));
由于indexOf是一个一个进行比较,效率不高。
可以先对数组进行排序,排序后,相同的值就会被排在一起,然后我们就可以只判断当前元素与上一个元素是否相同,相同就说明重复,不相同就添加进 res。这种方法效率高于使用 indexOf。
8.浅拷贝和深拷贝
浅拷贝
对于数组和对象这种属于引用类型的,如果浅拷贝(例如用 = 直接赋值,或者只遍历第一层 )那么对于复杂的,具有嵌套结构的数据来说,浅拷贝只是拷贝了同一个引用,而这个引用指向存储在堆中的一个对象。拷贝完成后,两个变量实际上将引用同一个对象,如果改变其中一个变量,就会影响另一个变量。
// 引用类型值复制
var object1 = {a: 1};
var object2 = object1;
object1.a = 2
console.log(object1) // { a: 2 }
console.log(object2) // { a: 2 }
可以看出来,改变其中一个变量,另一个也会改变
深拷贝
第一种方法:使用 JSON.stringify 和 JSON.parse 方法
var arr = ['old', 1, true, ['old1', 'old2'], {old: 1}]
var new_arr = JSON.parse( JSON.stringify(arr));
console.log(new_arr); // [ 'old', 1, true, [ 'old1', 'old2' ], { old: 1 } ]
但是这种方法有一个问题,不能拷贝函数。
第二种方法。利用递归
function deepClone(source) {
// 递归终止条件
if (!source || typeof source !== 'object') {
return source;
}
var targetObj = source instanceof Array ? [] : {};
for (var key in source) {
if (source.hasOwnProperty(key)){
if (source[key] && typeof source[key] === 'object') { //数组 typeof的结果也是object
targetObj[key] = deepClone(source[key]);
} else {
targetObj[key] = source[key];
}
}
}
return targetObj;
}
var object1 = {'year':12, arr: [1, 2, 3], obj: {key: 'value' }, func: function(){return 1;}};
var object2 = [1,[2,[3,4,[5,6]]]]
var newObj= deepClone(object1);
var newObj2 = deepClone(object2)
console.log(newObj) // { year: 12,arr: [ 1, 2, 3 ],obj: { key: 'value' }, func: [Function: func] }
console.log(newObj2) // [ 1, [ 2, [ 3, 4, [Array] ] ] ]