原生Javascript 基础知识

637 阅读7分钟

严格模式(Strict mode)

  1. 严格模式对Javascript的语法和行为,都做了一些改变。

  2. 严格模式开启检测和一些其他措施,使JavaScript变成更整洁的语言。推荐使用严格模式。为了开启严格模式,只需在JavaScript文件或script标签第一行添加如下语句:

    'use strict';

你也可以在每个函数上选择性开启严格模式,只需将上面的代码放在函数的开头:

function functionInStrictMode() {    'use strict';}

1.全局变量显式声明!:在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,全局变量必须显式声明。

"use strict";

v = 1; // 报错,v未声明

for(i = 0; i < 2; i++) { // 报错,i未声明
}

2.增强的安全措施(禁止this关键字指向全局对象)

// 正常模式
function f(){
    return this;
} 
// “this”指向全局对象

// 严格模式
function f(){ 
    "use strict";
    return this;
} 
// 严格模式下,this的值为undefined


//使用构造函数时,如果忘了加new,this不再指向全局对象,而是报错。
function f2(){ 
    "use strict";
    this.a = 1;
};

f2();// 报错,this未定义


//禁止在函数内部遍历调用栈
function f3(){

  "use strict";

  f3.caller;    // 报错
  f3.arguments; // 报错

}

f3();

3.禁止删除变量(严格模式下只有configurable设置为true的对象属性,才能被删除。)

"use strict";

var x;

delete x; // 语法错误

var o = Object.create(null, {
  x: {
    value: 1,
    configurable: true
  }
});

delete o.x; // 删除成功

4.显式报错

//    严格模式下,设置字符串的length属性,会报错。
'use strict';
'abc'.length = 5;


//正常模式下,对一个对象的只读属性进行赋值,不会报错,只会默默地失败。严格模式下,将报错。
var o = {};
Object.defineProperty(o, "v", { value: 1, writable: false });
o.v = 2; // 报错


//严格模式下,对一个使用getter方法读取的属性进行赋值,会报错。
var p = {
    get v() { return 1; } 

};
p.v = 2; // 报错


//严格模式下,对禁止扩展的对象添加新属性,会报错。
var o = {};
Object.preventExtensions(o);
o.v = 1; // 报错


// 严格模式下,删除一个不可删除的属性,会报错。
delete Object.prototype; // 报错

5.重名错误

(1)对象不能有重名的属性
正常模式下,如果对象有多个重名属性,最后赋值的那个属性会覆盖前面的值。严格模式下,这属于语法错误。

"use strict";

var o = {
    p: 1,
    p: 2
}; // 语法错误

(2)函数不能有重名的参数

正常模式下,如果函数有多个重名的参数,可以用arguments[i]读取。严格模式下,这属于语法错误。

"use strict";
function f(a, a, b) { // 语法错误
    return ; 
}

6.禁止八进制表示法

正常模式下,整数的第一位如果是0,表示这是八进制数,比如0100等于十进制的64。严格模式禁止这种表示法,整数第一位为0,将报错。

"use strict";

var n = 0100; // 语法错误

7.arguments对象的限制(arguments是函数的参数对象,严格模式对它的使用做了限制。)

(1)不允许对arguments赋值

"use strict";

arguments++; // 语法错误

var obj = { set p(arguments) { } };  // 语法错误

try { } catch (arguments) { }  // 语法错误

function arguments() { }  // 语法错误

var f = new Function("arguments", "'use strict'; return 17;");  // 语法错误

(2)arguments不再追踪参数的变化

function f(a) {
  a = 2;
  return [a, arguments[0]];
}

f(1); // 正常模式为[2,2]

function f(a) {
  "use strict";    
  a = 2;
  return [a, arguments[0]];
}

f(1); // 严格模式为[2,1]

(3)禁止使用arguments.callee

"use strict";

var f = function() { return arguments.callee; };

f(); // 报错

8.函数必须声明在顶层

将来JavaScript的新版本会引入“块级作用域”。为了与新版本接轨,严格模式只允许在全局作用域或函数作用域的顶层声明函数。也就是说,不允许在非函数的代码块内声明函数。

以下代码在if代码块和for代码块中声明了函数,在严格模式下都会报错。

"use strict";

if (true) {
  function f1() { } // 语法错误
}

for (var i = 0; i < 5; i++) {
  function f2() { } // 语法错误
}

9.保留字

为了向将来Javascript的新版本过渡,严格模式新增了一些保留字:implements, interface, let, package, private, protected, public, static, yield。使用这些词作为变量名将会报错。

ECMAscript第五版本身还规定了另一些保留字(class, enum, export, extends, import, super),以及各大浏览器自行增加的const保留字,也是不能作为变量名的。

function package(protected) { // 语法错误

  "use strict";

  var implements; // 语法错误

}

内置类型

JS 中分为八种内置类型nullundefinedbooleannumberstringsymbol,BigInt,Object 。注意:对象和数组都属于Object 类型

Typeof

typeof 的详细判断

typeof "John"                 // 返回 string 
typeof 3.14                   // 返回 number
typeof NaN                    // 返回 number 
typeof false                  // 返回 boolean
typeof [ 1,2,3,4]              // 返回 object
typeof {name: 'John', age:34}  // 返回 object
typeof new Date()             // 返回 object
typeof function () {}         // 返回 function
typeof myCar                  // 返回 undefined (if myCar is not declared) 
typeof null                   // 返回 object

判断JS数据类型的四种方法

1、typeof

typeof 是一个操作符,其右侧跟一个一元表达式,并返回这个表达式的数据类型。返回的结果用该类型的字符串(全小写字母)形式表示,包括以下 7 种:number、boolean、symbol、string、object、undefined、function 等。

缺陷:typeof 操作符会返回一些令人迷惑但技术上却正确的值:

  1. 对于基本类型,除 null 以外,均可以返回正确的结果。
  2. 对于引用类型,除 function 以外,一律返回 object 类型。
  3. ( [ ] 和 { } )均返回object类型

typeof ''``;``// string 有效

typeof 1;``// number 有效

typeof Symbol();``// symbol 有效

typeof true``;``//boolean 有效

typeof undefined;``//undefined 有效

typeof null``;``//object 无效

typeof [] ;``//object 无效

typeof new Function();``// function 有效

typeof new Date();``//object 无效

typeof new RegExp();``//object 无效

2、instanceof

instanceof 是用来判断 A 是否为 B 的实例,表达式为:A instanceof B,如果 A 是 B 的实例,则返回 true,否则返回 false。 在这里需要特别注意的是:instanceof 检测的是原型,我们用一段伪代码来模拟其内部执行过程

function instanceof(Self, Target) {
    // 获得(目标类型)的原型
    var prototype = Target.prototype
    // 获得(自身对象)的原型
    Self = Self.__proto__
    // 判断(自身对象)的类型 是否等于(目标类型)的原型
    while (true) {
    	if (Self === null) return false
    	if (prototype === left) return true
    	left = left.__proto__
    }
}

instanceof 只能用来判断两个对象是否属于实例关系**, 而不能判断一个对象实例具体属于哪种类型。因为如果别人如果更(改了原型)就会导致类型判断错误**

3、constructor

当一个函数 F被定义时,JS引擎会为F添加 prototype 原型,然后再在 prototype上添加一个 constructor 属性,并让其指向 F 的引用。如下所示:

可以看出,F 利用原型对象上的 constructor 引用了自身,当 F 作为构造函数来创建对象时,原型上的 constructor 就被遗传到了新创建的对象上。接下来我们使用 constructor 来进行类型判断

var a = "iamstring.";
var b = 222;
var c= [1,2,3];
var d = new Date();
var e = function(){alert(111);};
var f = function(){this.name="22";};
alert(a.constructor === String) ----------> true
alert(b.constructor === Number) -----------> true
alert(c.constructor === Array) ----------> true
alert(d.constructor === Date) -----------> true
alert(e.constructor === Function) -------> true

// 注意: constructor 在类继承时会出错
function A(){};
function B(){};
A.prototype = new B(); //A继承自B
var aObj = new A();
alert(aobj.constructor === B) -----------> true;
alert(aobj.constructor === A) -----------> false;

// 在new 的时候constructor 指向了实例所以出错。需要手动设置回来才能用 constraint 判断
aObj.prototype.constructor = A // 将自己的类赋值给对象的constructor属性
alert(aobj.constructor === A) -----------> true;
alert(aobj.constructor === B) -----------> false;

可以看到 constraint 在类继承时就会报错,这种方法似乎也不完美。

4、Object.prototype.toString.call():(建议使用):通用但很繁琐的方法

var a = "iamstring.";
var b = 222;var c= [1,2,3];
var d = new Date();
var e = function(){alert(111);};
var f = function(){this.name="22";};

alert(Object.prototype.toString.call(a) === '[object String]') // --> true;
alert(Object.prototype.toString.call(b) === '[object Number]') // --> true;
alert(Object.prototype.toString.call(c) === '[object Array]') // --> true;
alert(Object.prototype.toString.call(d) === '[object Date]') // --> true;
alert(Object.prototype.toString.call(e) === '[object Function]') // --> true;
alert(Object.prototype.toString.call(f) === '[object Function]') // --> true;

//  获取类型的方法使用 slice 截取一下。前面的object 总是不会变
var type = Object.prototype.toString.call(a).slice(8,-1);  // -->"String"

对象转基本类型

对象在转换基本类型时,首先会调用 valueOf 然后调用 toString。并且这两个方法你是可以重写的。

var a = {
    valueOf() {
    	return 0
    }
}

当然你也可以重写 Symbol.toPrimitive ,该方法在转基本类型时调用优先级最高。

var a = {
  valueOf() {
    return 0;
  },
  toString() {
    return '1';
  },
  [Symbol.toPrimitive]() {
    return 2;
  }
}
1 + a // => 3
'1' + a // => '12'

隐式类型转换

大家都知道 JS 中在使用运算符号或者对比符时,会自带隐式转换,规则如下:

1.字符串连接符与算术隐式转换规则混淆

console.log(1 + true);      //2
console.log(1 + "true");        //"1true"
console.log(1 + undefined);     //NaN
console.log(1 + null);      //1

2.比较运算符会把其他数据类型转换number数据类型后再比较

  1. 字符串与数字比较时:字符串会先被转换成数字(注意:如果碰到转换不了的为NaN)

  2. 字符串跟字符串比较时:会先调用.charCodeAt()方法。这时比对的是字符串的第一个值,如果相等则继续比对第二第三个以此类推。

    console.log("2" > 10); //false console.log("a" > 10); //false "a" --> Nan console.log(10 > "a"); //false console.log(Number("a")); //NaN console.log("2" > "10"); //true '2'.charCodeAt() > '10'.charCodeAt() = 50 > 49 = true console.log(false == 0); //true console.log(false == ""); //true console.log(Number(false)); //0 console.log(NaN == NaN); //false console.log(undefined == null); //true console.log(Number(NaN)); //NaN console.log(Number(undefined)); //NaN console.log(Number(null)) //0

3.(引用)类型的隐式转换

先使用 valueOf() 方法获取原始值,在使用 toString() 方法进行类型转换 

console.log([1,2] == '1,2');    // true    [1,2].valueOf().toString() --> '1,2'
console.log([1,2] + '2');       // "1,22" [1,2].valueOf().toString() --> '1,2'
console.log([1,5] + 2);       // "1,52" [1,5].valueOf().toString() --> '1,5'
console.log([] == 0);       // true  [].valueOf().toString() --> '0'
console.log([] === 0);       // false [].valueOf().toString() --> '0'
var a = {};
console.log(a == 0);        // false a.valueOf().toString() --> "[object Object]"
console.log(a + 2);        // "[object Object]2" 

//大坑
console.log ( [] == 0 ); //true [].valueOf().toString() --> ""
console.log ( [] === 0 ); //false [].valueOf().toString() --> ""
console.log ( ![] == 0 ); //true    ![] --> false
//神坑
console.log ( [] == ![] ); //true  ''==false 所以为true
console.log ( [] == [] ); //false  (注意:这里比对的是引用内存空间)
//史诗级坑
console.log({} == !{}); //false  {} --> "[object Object]" !{} --> false
console.log({} == {});  //false (注意:这里比对的是引用内存空间)

var a = {} 
a == "[object Object]"  // true a.valueOf().toString() --> "[object Object]"
a + 3 // "[object Object]3" 
a.valueOf = function(){
    return 3
}
a == "[object Object]" // false 注意重写了valueOf()方法。所以最后结果返回 3

为什么会是6?而不是'33'? 因为如果是 + 两个方法或只有valueOf()重写了 只会调用valueOf()方法
a + 3 // 6   a.valueOf() --> 3  

浏览器本地存储

cookie,localStorage,sessionStorage,indexDB

特性

cookie

localStorage

sessionStorage

indexDB

数据生命周期

一般由服务器生成,可以设置过期时间

除非被清理,否则一直存在

页面关闭就清理

除非被清理,否则一直存在

数据存储大小

4K

5M

5M

无限

与服务端通信

每次都会携带在 header 中,对于请求性能影响

不参与

不参与

不参与

从上表可以看到,cookie 已经不建议用于存储。如果没有大量数据存储需求的话,可以使用 localStoragesessionStorage 。对于不怎么改变的数据尽量使用 localStorage 存储,否则可以用 sessionStorage 存储。

var obj = new Base(); js中的new()到底做了些什么?? 

  1. 创建一个新对象

  2. 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象)

  3. 执行构造函数中的代码(为这个新对象添加属性);

  4. 返回新对象。

    (1) 创建一个新对象; var obj = {}; (2) 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象) obj.proto = Base.prototype;
    (3) 执行构造函数中的代码(为这个新对象添加属性) Base.call(obj); (4) 返回新对象。 return obj;

JavaScript 的继承

圣杯模式缺陷

  1. 挂载在父级的私有属性没有赋值过来,只继承了原型链里面的方法。 

  2. 覆盖了子级的原型方法

  3. 当 for in 的时候 会出现异常, 多了一个 继承的 constructor 可枚举属性

    function extend(Target,Father){ function F(){}; //F是中间层的继承 而Origin是真正的被继承的类, 是超类 F.prototype=Father.prototype; Target.prototype=new F(); Target.prototype.constructor=Target;//归位!把丢失的 constructor 重新赋值回来} function Father(){ this.age = 18; }

    function Son(){ }

    Son.prototype.getAge = function(){

    }

    extend(Son,Father);

    var Example =new Son();

    // Uncaught TypeError: Example.getAge is not a function at 子级的原型方法丢失 // Example.getAge();

    // 当 for in 的时候 会出现异常, 多了一个 继承的 constructor 可枚举属性 for (var i in Example) {
    console.log( Example[i] ); }

ES6(Babel编译后的继承代码逻辑)

  1. 重写原型 prototype和构造器 constructor,设置__proto__的执向到父级

  2. 重写原型 prototype 和构造器 constructor 后原有的child方法丢失,重新赋值上去。

  3. 父级的私有数据通过利用 Super.apply(this, arguments);拷贝过来,最后返回。

  4. 具体实现链接:juejin.cn/post/699112…

js基础实战

模仿call

  1. 第一个参数为null或者undefined时,this指向全局对象window,值为原始值的指向该原始值的自动包装对象,如 StringNumberBoolean

  2. 将函数作为传入的上下文(context)属性执行

  3. 函数执行完成后删除该属性

  4. 返回执行结果

    Function.prototype.myCall = function(context) { context = context || window var key = "fn" context[key] = this var result = contextkey delete context[key] return result }

模仿apply

Function.prototype.myApply = function(context) {
    context =  context || window
    const key = "fn"
    context[key] = this
    const result= context[key](arguments[1])
    delete context[key]
    return result
}

模仿bind

  1. 使用 call / apply 指定 this

  2. 返回一个绑定函数

    Function.prototype.myBind = function(context) { var fn = context || window; var _this = this; var bindFn = function () { return _this.call(fn, arguments[1]);
    } return bindFn }

模仿instanceof

instanceof 可以正确的判断对象的类型,因为内部机制是通过判断对象的原型链中是不是能找到类型的 prototype

function instanceof(Self, Target) {
    // 获得(目标类型)的原型
    var prototype = Target.prototype
    // 获得(自身对象)的原型
    Self = Self.__proto__
    // 判断(自身对象)的类型 是否等于(目标类型)的原型
    while (true) {
    	if (Self === null) return false
    	if (prototype === left) return true
    	left = left.__proto__
    }
}

分解URL参数

使用正则表达式。

var URL = 'http://weibo.com/?c=spr_qdhz_bd_360jsllqcj_weibo_001&key2=JavaScript';

function queryURLParameter(URL){	//使用正则分解URL参数
	var reg = /([^&?=]+)=([^&?=]+)/g,
		obj = {};
	URL.replace(reg,function() {
		obj[arguments[1]] = arguments[2];
		console.log(arguments)
	});
	return obj;
}
console.log(queryURLParameter(URL));

使用字符串附加在原型链身上。

Object.defineProperty(String.prototype, 'queryURLParameter', {
  value: function() {
	var obj = {};
	if(this.indexOf('?')<0) return obj;	//如果没有找到(?) 侧把(obj)原封不动返回出去
	var URLDATA = this.split('?');	//用(?)分割路劲
	var URL = URLDATA[1];	//此步骤把URL地址(?)右边的参数赋值下来
	URLDATA = URL.split('&');	//用(&)把参数分割成数组 如( ["data"="1","data2"="2"] )
	for (var i = 0; i < URLDATA.length; i++) {
		var array = URLDATA[i].split('=');	//把数组每一项用(=)分割出来
		obj[array[0]] = array[1];	//把(array[0])当作对象(obj的键名) 把(array[1])当作obj的键值 赋值给obj
	}
	return obj;
  },
  configurable: true,	//是否可以删除属性,是否可以修改属性的 writable 、 enumerable 、 configurable 属性。
  enumerable: false,	//是否可枚举 
  writable: true		//是否能重写 默认为false
});

console.log(URL.queryURLParameter());

封装cookie

var Cookie = (function(){	//使用闭包的方式 向外暴漏封装的方法
	function setCookie(key,value,t){   //添加一个Cookie
		var oDate=new Date();
		oDate.setDate(oDate.getDate()+5); //得到一个对象的时间
		// oDate.toGMTString(); //为了兼容ie 将时间对象转化为字符串
		oDate.setDate(oDate.getDate()+t); //得到一个对象的时间,(设置未来过期天数)
		document.cookie = key+'='+value+';expire='+oDate.toGMTString(); 
	}
	function getCookie(key){   //获取Cookie的值
	 var arr = document.cookie.split(';');//获取到字符串Cookie 以;号分割为数组
	 for( var i=0; i<arr.length;i++){
	   var arr2 = arr[i].split('=');
	   if(arr2[0]==key){//如果与传进来的(key)相同
	     return decodeURI(arr2[1]);//返回解码后的(value)值
	   }
	 }
	}

	function removeCookie(key){    //删除一个Cookie
	 setCookie(key,'',-1);  //把值赋值为空 把过期时间设置为-1(让Cookie过期)
	}

	return {
		set:setCookie,
		get:getCookie,
		remove:removeCookie
	}
})();
// console.log(Cookie.set("name","wangsheng",3));
// console.log(Cookie.get("name"));
// console.log(Cookie.remove("name"));

多维数组去重

Object.defineProperty(Array.prototype, 'heavy', {
  //在数组的原型上(添加一个)公有的( 数组去重方法 ),传入true代表多维数组去重
  value: function(data) {	
  	function checkedType(Target){	// 检测数据类型功能函数
		// 返回的是 【Object Array】 类型这样的检测数据类型
		return Object.prototype.toString.call(Target).slice(8,-1);
	}
	for (var i = 0; i < this.length; i++) {	
		if( checkedType(this[i])== "Array"&&data ){	// 判断是否需要N维数组去重
			this[i].heavy(true);
		}

		//console.log(this.indexOf(this[i])!=i);
                //indexOf如果找不到相同元素就会返回(-1)indexOf找到相同的之后就会返回相应下标
		if(this.indexOf(this[i])!=i){
			this.splice(i,1);	//删除数组元素后数组长度减1后面的元素前移
			i--; //数组下标回退
		}
	}
	return this;	//把(去重后的数组)重新返回出去
  },
  configurable: true,	//是否可以删除,修改 writable 、 enumerable 、 configurable 属性。
  enumerable: false,	//是否可枚举 
  writable: true	//是否能重写 默认为false
});

// var q1 = [5,"1aa",1,2,3,4,2,1,3,3,2,"1aa",[1,2,3,1,2,[1,2,[1,2,3,1,2],3,1,2]]];
// q1.heavy(true);
// console.log(q1);

N维数组去重

Object.defineProperty(Array.prototype, 'max', {
  value: function() {	// N维数组求最大值
	function checkedType(Target){	// 检测数据类型功能函数
		// Object.prototype.toString.call(Target) 返回的是 【Object Array】 类型这样的检测数据类型
		return Object.prototype.toString.call(Target).slice(8,-1);
	}
	var arr = 0;
	for(var key in this){
		if(!this.hasOwnProperty(key)) continue;	//如果不是 for in 遍历出来的不是自身属性 则跳出当前循环
		// console.log(this.hasOwnProperty(key),this[key]);
		if( checkedType(this[key])=="Array"){	//如果遍历的是数组
			// console.log(this[key],typeof this[key]);
			if(arr<=this[key].max()){
				arr = this[key].max();
			}
		}else if(checkedType(this[key])=="Object"){		//如果遍历的是对象
			for(var key2 in this[key]){
				// console.log(this[key],this[key][key2]);
				if( checkedType(this[key][key2])=="Array"||checkedType(this[key][key2])=="Object" ){
					// console.log(this[key],typeof this[key]);
					if(arr<=this[key].max()){
						arr = this[key].max();
					}
				}else if( arr<=this[key][key2] ){
					arr = this[key][key2]
				}
			}
		}
		else if(arr<=this[key]){
			arr=this[key];
		}
	}
	return arr;
  },
  configurable: true,	//是否可以删除属性,是否可以修改属性的 writable 、 enumerable 、 configurable 属性。
  enumerable: false,	//是否可枚举 
  writable: true		//是否能重写 默认为false
});

深拷贝克隆

var DeepClone = (function(){	// 深度克隆方法
	function checkedType(Target){	// 检测数据类型功能函数
		return Object.prototype.toString.call(Target).slice(8,-1);
		
	}
	function clone(Target){
		var data,TargetType = checkedType(Target);
		// 第一步根据传入的数据类型(初始化数据类型)
		if( TargetType === 'Object' ) data = {};
		else if( TargetType === "Array" ) data = [];
		else return Target;
		
		// 第二步遍历数据
		for( var key in Target ){
			var value = Target[key];	//获取遍历数据结构的每一项
			if( checkedType(value)==='Object'||checkedType(value)==='Array' ){
				data[key] = clone(value);
			} else data[key] = value;
		}
		// 第三步把data数据返回出去
		return data;
	}
	return function(Target){
		return clone(Target);
	}
})();
// var arr3 = [1,2,[3,4,{name:"wang2",age:19}],{ name:"wang",age:18 },2];
// var arr4 = DeepClone( arr3 );

发布订阅模式

var Emitter = function(){
        var listener = {}

	function subscribe(event,fn){
		if(listener[event]&&listener[event]!==undefined){
			listener[event]=[...listener[event],fn];
		}else{
			listener[event] = []
			listener[event].push(fn)
		}
		return ()=>{
			listener[event]=listener[event].filter(item=>item!=fn)
		}
	}

	function emit(event,...args){
		if(!listener[event]||listener[event]===undefined){
			console.log(listener[event],1);
			return false
		}
		listener[event].forEach(item=>item(...args));
	}

	return { subscribe,emit };
}

var emitter = Emitter();
var sub1 = emitter.subscribe('click',(...args)=>{console.log(...args,"sub1")});
var sub2 = emitter.subscribe('click',(...args)=>{console.log(...args,"sub2")});
emitter.emit('click',1,2,3);
sub1();	//释放
emitter.emit('click',1,2,3);

实现一个消息队列,题目如下:

    var array = {
        // [time]:[]
    };
    var time = null;
    function start(number){
        setTimeout(function(){
            array[number+""].forEach(item=>{
                item();
            });
        },0);
    }  

    function LazyMan(str){
        time = new Date().getTime()+Math.random();
        array[time+""] = [];
        array[time+""].push(function(){
            console.log("Hi! This is "+str+"!");
        });
        start(time);
        return this;
    }

    function sleep(number){
        array[time+""].push(function(){
            number = number*1000;
            var start = (new Date()).getTime()+number;
            while(new Date().getTime() < start ) {
                continue;
            }
        });
        return this;
    }

    function sleepFirst(number){
        array[time+""].unshift(function(){
            number = number*1000;
            var start = (new Date()).getTime()+number;
            while(new Date().getTime() < start ) {
                continue;
            }
        });
        return this;
    }

    function eat(str){
        array[time+""].push(function(){
            console.log("Eat " + str);
        });
        return this;
    }

    LazyMan("Hank").sleep(2).eat("dinner");
    LazyMan("Hank2").sleepFirst(2).eat("supper");