1. undefined与null
在JavaScript中将一个变量赋值为undefined或是null基本上没有区别。在if语句中都会被默认的转换为false,当使用 == 运算符的时候,这两者相等。
区别:
在使用JavaScript的时候,我们通常的认为,将null表示成一个“无对象”,有点类似JAVA中的对象初始化,这个对象存在初始为null。在转换数值的时候变成。
undefined表示无的原始值,即没有这个对象,转换数值的时候为NaN。
所以两者重点的区别在于,null表示对象存在,undefined表示对象不存在。
2.call的模拟实现
简介:
call() 方法在使用一个指定的this值和若干个指定的参数值的前提下调用某个函数或方法。
Function.prototype.call = function(context){
var context = context || window;
context.fn = this;
var args = [];
for(var i = 0 ; i< arguments.length;i++){
args.push('arguments['+i+']');
}
var result = eval('context.fn('+res+')');
delete conext.fn();
return result;
}
//ES6版本
Function.prototype.call = function(context,...args){
var context = context || window;
context.fn = this;
let result = context.fn(...args);
delete context.fn;
return result;
}
在这里浅析一下args.push('arguments['+i+']');
这一行,为什么arguments需要加上引号,因为在执行eval
的时候需要传入的应该是一个字符串,而不是值,当我们不加引号args中存放的将是传入参数的值,比如说传入call中的参数是[‘zhangsan’,’lis’],那么此时的args = [‘zhangsan’,’lis’],当我们执行eval('context.fn('+res+')');
这一行代码的时候,我们实际传入的是'zhangsan','lisi'
这样执行就会报错。
3.apply的模拟实现
与call的实现类似,不同的传参不同,call可以接受任意个参数,apply接受的需要是一个参数数组。
Function.prototype.apply = function(context,arr){
var context = context || window;
context.fn = this;
if(!arr){
return context.fn();
}
var args = [];
var result ;
for (var i = 0, len = arr.length; i < len; i++) {
args.push('arr[' + i + ']');
}
result = eval('context.fn('+args+')');
delete context.fn;
return result;
}
//es6
Function.prototype.apply = function(context,...arr){
var context = context || window;
context.fn = this;
let result = context.fn(...arr);
delete context.fn;
returl result;
}
4.bind的模拟实现
bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。
Function.prototype.bind = Function.prototype.bind || function(context){
var self = this;//指向调用者函数
var args = Array.prototype.slice.call(arguments,1);
var fNOP = function(){};
var fBound = function(){
var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(this instanceof fNOP ? this : context,args.concat(bindArgs));
}
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
//可以使用 fBound.prototype = Object.create(this.prototype);
return fBound;
}
//es6写法
Function.prototype.bind = function(context,...rest){
var self = this;//指代调用者的执行上下文
return function F(...args){
if(this instanceof self){
return new self(...rest,...args);
}
return self.apply(context,rest.concat(args);
}
}
Object.create = function(o){
function f(){}
f.prototype = o;
return new f;
}
5.new的模拟实现
new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型之一
function objectFactory(){
var obj = new Object(),
Constructor = [].shift.call(arguments);
obj._proto_ = Constructor.prototype;
var result= Constructor.apply(obj,arguments);
return typeof result=== 'object' ? result: obj;
}
6.数组扁平化
数组扁平化是指将一个多维数组变为一维数组
例如:[1,2,3,[4,5]] ==> [1,2,3,4,5]
实现
1). 使用循环遍历
function flatten(arr){
var result = [];
for(var i=0;i<arr.length;i++){
if(Array.isArray(arr[i])){
result = result.concat(flatten(arr[i]))
}else{
result.push(arr[i]);
}
}
return result;
}
2).使用toString
function flatten_tostring(arr){
return arr.toString().split(',').map(function(item){
return +item;
});
}
3).使用join
function flatten_join(arr){
return arr.join(",").split(",").map(function(item)){
return +item;
}
}
==`方法2,3存在弊端:仅仅支持数字==
4).使用es5中reduce
function flatten_reduce(arr){
return arr.reduce(function(prev,next){
return prev.concat(Array.isArray(next)?flatten_reduce(next) : next);
},[])
}
5).使用es6可扩展运算符
function flatten_es6(arr){
while(arr.some((item)=>Array.isArray(item))){
arr = [].concat(...arr);
}
return arr;
}
7.防抖
将几次操作合并为一此操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。
1.第一版
function _debounce(fn,delay){
var delay = delay || 200;
var timer;
return function(){
var context = this,
args = arguments;
if(timer){
clearTimeout(timer);
}
timer = setTimeout(function(){
timer = null;
fn.apply(context,args);
},delay);
}
}
2.第二版
function _debounce(fn,wait,immedilate){
var timer,
args,
context,
timerstap,
result;
var later =function(){
var last = new Date().getTime() -timestamp;
if(last<wait&& last>=0){
timer = setTimeout(later,wait-last);
}else{
timer = null;
if(!immedilate){
result = fn.apply(context,args);
if(!timer){
context =args = null;
}
}
}
};
return function(){
context = this;
args = arguments;
timerstap = new Date().getTime();
var callNow = immedilate && !timer;
if(!timer){
timer = setTimeout(later,wait);
}
if(callNow){
result = fn.apply(context,args);
context = args = null;
}
return result;
}
}
8.节流
节流的目的是让触发频繁的函数或者事件能够间歇性的触发,像scroll,resize,move这样的事件在一秒钟触发很多次,这样对程序的性能可能会造成很大的影响。我们采取节流的方式,让事件不那么频繁的触发,我们通过节流函数来进行控制,当需要触发的事件在下次事件触发之前还没有触发,那么我们就不让之前事件触发,使得一定时间内只触发一次函数
1.第一版:
var throttle =function(fn,interval){
var _self = fn,
timer,
firstTime = true;
return function(){
var args = arguments,
_this = this;
if(firstTime){
_self.apply(_this,args);
return firstTime =false;
}
if(timer){
firstTime = false;
}
timer = setTimeout(function(){
clearTimeout(timer);
timer = null;
_self.apply(_this,args);
},interval||500)
}
}
2.第二版
var throttle = function(fn,interval){
var last,timer,interval = interval||500;
return function(){
var context = this,
args = arguments,
now += new Date();
if(last&&now-last<interval){
clearTimeout(timer);
timer = setTimeout(function(){
last = now;
fn.apply(context,args);
},inerval)
}else{
last = now;
fn.apply(context,args);
}
}
}
3.第三版(来源于underscore
)
var throttle =function(fn,wait,options){
var context,
args,
timer =null,
result,
previous =0;
if(!options){
options ={};
}
var later =function(){
pervious = options.leading == false ? 0 : new Date();
timer = null;
result = fn.apply(context,args);
if(!timer){
context = args = null;
}
};
return function(){
var now = new Date();
if(!pervious && options.leading == false){
pervious = now;
}
var remaning = wait - (now - pervious);
context = this;
args = arguments;
if(remaning<=0||remaning>wait){
if(timer){
clearTimeout(timer);
timer = null;
}
previous = now;
result = fn.apply(context,args);
if(!timer){
context = args = null;
}else if(!timer && options.trailing != false){
timer =setTimeout(later,remaning);
}
return result;
}
}
}