一.函数柯里化
参考文章
1. 前端开发者进阶之函数柯里化Currying
2. 柯里化的注释
3. 关于Array.slice.call(arguments, 1) 的思考
- 函数柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
- 通用的柯里化函数
function currying(fn) {
var slice = Array.prototype.slice,
__args = slice.call(arguments, 1);
return function () {
var __inargs = slice.call(arguments);
return fn.apply(null, __args.concat(__inargs));
};
}
第一处的arguments指的是curring的参数,而第二处的参数是指返回的函数所带的参数
关于柯里化的应用
var fn=function(a,b,c){
return a+b+c;
}
需要写一个函数,满足
curry(fn)(1)(2)(3) //6
解决1(容易理解):segmentfault
解决2:
var fn = function(a,b,c) {
return a+b+c;
}
function curry(fn) {
var arr = [],
mySlice = arr.slice
fnLen = fn.length;
function curring() {
arr = arr.concat(mySlice.call(arguments));
if(arr.length < fnLen) {
return curring;
}
return fn.apply(this, arr);
}
return curring
}
curry(fn)(1)(2)(3);//6
* callee *
arguments.callee在哪个函数中调用就是指哪个函数。
在匿名函数中有时需要自己调用自己,所以就可以通过arguments.callee指代自身函数。下面是通过callee实现阶乘:
(function(n){
if(n > 1) return n* arguments.callee(n-1);
return n;
})(10);
* caller*
返回的是函数的调用体所在的函数
function parentCheck() {
check("");
function check() {
subCheck();
function subCheck() {
console.log(arguments.callee); //返回的是subcheck()
console.log(subCheck.caller.caller) //返回的是parentCheck()
}
}
}
parentCheck();
二. setTimeOut
- setTimeOut原理(JavaScript引擎,gui渲染线程,事件触发线程)
- setTimeOut(function,0)
- 不只传两个参数,还能更多,这些是传给function的参数
三.继承
参考资料
* javascript继承详解
* javascript继承详解(二)
- JavaScript通过构造函数和原型的方式模拟实现了类的功能。
constructor
constructor始终指向创建当前对象的构造函数。
var Foo = function() { }; console.log(Foo.constructor === Function); // true // 由构造函数实例化一个obj对象 var obj = new Foo(); console.log(obj.constructor === Foo); // true // 将上面两段代码合起来,就得到下面的结论 console.log(obj.constructor.constructor === Function); // true
每个函数都有一个默认的属性prototype,而这个prototype的constructor默认指向这个函数。如下例所示:
function Person(name) { this.name = name; }; Person.prototype.getName = function() { return this.name; }; var p = new Person("ZhangSan"); console.log(p.constructor === Person); // true console.log(Person.prototype.constructor === Person); // true // 将上两行代码合并就得到如下结果 console.log(p.constructor.prototype.constructor === Person); // true
- _proto_和prototype
- 普通对象具有_proto_属性,指向该对象的构造方法的原型对象
- 方法(function)除了具有 _proto_属性,还具有属性prototype,prototype指向该方法的原型对象。
- prototype 用来实现基于原型的继承以及属性的共享
- _proto_ 构成原型链,同样用于继承的实现
函数的构造函数不就是Function嘛,因此它的__proto__指向了Function.prototype
原型对象也是对象,它的_proto_指向它的构造函数的原型对象,即Object.prototype
* 原博还有第三、四、五篇,都是更高级的用法,对继承、声明的优化以及更优雅的写法,止步于第三篇…… *
继承的方式:
- 原型链继承
- 构造继承
- 实例继承
- 拷贝继承
- 组合继承
- 寄生组合继承
(详见:JS继承的实现方式)
四.闭包
闭包保存了变量
实现一个暴露内部变量,而且外部可以访问修改的函数
var person = function(){
//变量作用域为函数内部,外部无法访问
var name = "default";
return {
getName : function(){
return name;
},
setName : function(newName){
name = newName;
}
}
}();
print(person.name);//直接访问,结果为undefined
print(person.getName());
person.setName("abruzzi");
print(person.getName());
—–分割线——–
具体地学习了作用域链的知识点后,又重新把闭包的知识点看了一遍,真的是好理解很多
很关键的东西是,定义时和调用时的作用域。
一个函数(外部函数)在它的内部定义函数(称为内部函数),这个内部函数就有了一个作用域,其中就包含着外部函数里面所拥有的变量。当内部函数被返回出去在外面调用时,就有一个外部引用,会指向被嵌套的内部函数,它不会被当做垃圾回收,此时在外部环境,就能访问到外部函数中的变量了(它们可以捕捉到局部变量,并一直保存下来)
此外,闭包函数每次被调用都会产生一个独立的作用域,指向外部函数的变量,它们之间是互不影响,不存在共享关系
五.栈和堆
当一个方法执行时,每个方法都会建立自己的内存栈,在这个方法内定义的变量将会逐个放入这块栈内存里,随着方法的执行结束,这个方法的内存栈也将自然销毁了。因此,所有在方法中定义的变量都是放在栈内存中的;
当我们在程序中创建一个对象时,这个对象将被保存到运行时数据区中,以便反复利用(因为对象的创建成本通常较大),这个运行时数据区就是堆内存。堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用(方法的参数传递时很常见),则这个对象依然不会被销毁,只有当一个对象没有任何引用变量引用它时,系统的垃圾回收机制才会在核实的时候回收它。(闭包的实现基础)
- 栈中存储的是基础变量以及一些对象的引用变量,基础变量的值是存储在栈中,而引用变量存储在栈中的是指向堆中的数组或者对象的地址,这就是为何修改引用类型总会影响到其他指向这个地址的引用变量。
原文地址: js的栈与堆
function person(){}
var p1 = new person();
p1.name= 'zhangshan';
var p2= p1;//对象之间赋值,现在p1 和 p2指向的是同一个内存空间
console.log(p2.name);
p2.name = 'apple'; //将p2的值发生改变会影响p1的值
console.log(p1.name);
p2 = null ;//这里是指将p2的栈内存清除了,但是p2指向的堆内存还是存在!
// 也就是将p2的指针指向null了,并不是改变p2指向的地址的值
console.log(p1.name);//所以这里可以输出结果!
六 深度克隆
- 这篇文章中详细地讲解了为什么需要深度克隆,以及深度克隆的实现代码
- 涉及原始类型和对象类型
- 原始类型:存放的是对象的实体数据
- 对象类型:存放的是对象的引用地址(实际内容存放在内存地址中,为了减少数据开销)
实现代码
function clone(Obj){
var buf;
if(Obj instanceof Array){ //数组类型
buf = [];
var i = Array.length;
while(i--){
buf[i]= clone(Obj[i]);
}
return buf;
}else if(Obj instanceof Object){ //对象类型
buf = {};
for(var k in Obj){
buf[k] = clone(Obj[k]);
}
return buf;
}else{
return Obj;
}
}
七.extend
javascript object的用法
Prototype 对Object类进行的扩展主要通过一个静态函数Object.extend(destination, source)实现了JavaScript中的继承。 从语义的角度, Object.extend(destination, source)方法有些不和逻辑, 因为它事实上仅仅实现了从源对象到目标对象的全息拷贝。不过你也可以这样认为:由于目标对象拥有了所有源对象所拥有的特性, 所以看上去就像目标对象继承了源对象(并加以扩展)一样。另外, Prototype对Object扩展了几个比较有用的静态方法, 所有其他的类可以通过调用这些静态方法获取支持。
// 一个静态方法表示继承, 目标对象将拥有源对象的所有属性和方法
Object.extend = function(destination, source) {
for (var property in source) {
destination[property] = source[property];
// 利用动态语言的特性, 通过赋值动态添加属性与方法
}
return destination; // 返回扩展后的对象
}
JQ中的extend
- $.extend:为jq的全局对象添加方法
- $.fn.extend:为所有的类实例添加方法($.fn 相当于 $.prototype)
八.jq中的click、live、bind、delegate
- click是最普通的…
- live可以为所有的元素绑定事件,即使是未来才出现的元素。(之前都是采用事件代理来处理那种动态生成的元素的事件的,现在通过live就能轻松搞定)
- bind可以将数据json作为第二个参数传入事件中(貌似live也可以)
- delegate跟live一样,不过比live跟强大,它支持链式写法
$("#test").delegate("a", "mouseover", function() {
alert("hello");
});
九.js中的内存泄露
参考文章 js内存泄露的几种情况
JavaScript常见的内存泄漏原因
js跟java一样,都具有自动垃圾回收机制。
常见泄露原因
虽然JavaScript 会自动垃圾收集,但是如果我们的代码写法不当,会让变量一直处于“进入环境”的状态,无法被回收。下面列一下内存泄露常见的几种情况。
- 全局变量引起的内存泄露
闭包引起的内存泄露
var leaks = (function(){ var leak = 'asdf'; //被闭包所引用,不会被回收 return function(){ console.log(leak) } })()
dom清空或删除时,事件未清空导致的内存泄漏
$('#id').bind('click',function(){ XXXXX }).remove() 解决办法:清除事件 $('#id').bind('click',function(){ XXXXX }).off('click').remove()
$('#id').onclick = function(){....} 解决办法: 将事件清除: $('#id').onclick = function(){$('#id').onclick = null;.....} 或采用事件委托
子元素存在引用引起的内存泄露
子元素如果没有被清空的话,那么跟它存在间接引用关系的父元素即使删除了,也仍然存在。变量之间相互引用引起的内存泄露
a和b的相互引用,导致a、b都不会被释放 var a=document.getElementById("xx"); var b=document.getElementById("xxx"); a.r=b; b.r=a;
删除的属性引用依然存在引起的内存泄露
有点像第4种的情况,存在间接引用的话不会被回收 a = {p: {x: 1}}; b = a.p; delete a.p;
自动类型装箱转换引起的内存泄露
看网上资料,说下面的代码在ie系列中会导致内存泄露,先提个神,具体泄露与否先不管
var s=”lalala”; alert(s.length);
s本身是一个string而非object,它没有length属性,所以当访问length时,JS引擎会自动创建一个临时String对象封装s,而这个对象一定会泄露。这个bug匪夷所思,所幸解决起来相当容易,记得所有值类型做.运算之前先显式转换一下:
var s="lalala"; alert(new String(s).length);
十.map的用法
size属性
map.size()
返回映射中的元素数clear方法
清除所有映射delete方法
map.delete(key)
删除对应的键值对forEach方法
map.forEach(callbackfn())
回调函数中有三个参数
callbackfn(item,key,mapObj)get方法
得到键对应的值has方法
判断是否含有某个keyset方法
设置键值对
详细文档 Map
十一.json的parse和stringify
parse是从字符串中解析出json对象
var str = '{"name":"huangxiaojian","age":"23"}'; //str是一个拼装的字符串 var jsonobj = JSON.parse(str); //通过parse转换成json对象
stringify是将json对象转换成字符串
var a = {a:1,b:2}; //a是一个对象 var str = JSON.stringify(a); //通过stringify转换成字符串
十二. 原生js实现事件委托
1. 事件一共有3个阶段:捕获阶段、目标阶段、冒泡阶段
2. 阻止事件传播
w3c中,使用stop Propogation();
ie中,设置cancelBubble=TRUE;
3. 阻止事件的默认行为
w3c中,使用preventDefault();
ie中,设置window.event.returnValue=false;
4. 原生js实现事件代理
<ul id="list">
<li id="li-1">li1</li>
<li id="li-2">li2</li>
<li id="li-3">li3</li>
<li id="li-4">li4</li>
</ul>
<javascript>
function getEventTarget(e){
e = e || window.event;
return e.target || e.srcElement;
}
var parent = document.getElementById('list');
if(document.addEventListener){
parent.addEventListener('click',function(e)){
//得到事件target,这里应该是点击的那个li标签
var target = getEventTarget(e);
alert(target.id);
}
}else if(document.attachEvetn){
parent.attachEvent(on+'click',funciton(e){
var target = getEventTarget(e);
alert(target.id);
})
}else{
parent.onclick=function(){
var target = getEventTarget(e);
alert(target.id);
}
}
</javascript>
原文链接:原生js实现事件代理
十三.立即执行函数
立即执行函数是得到一个独立的作用域
但是es6标准入门这本书却说由于块级作用域的出现,IIFE已经不再必要了。
十四.array对象自带的方法
1.复制方法(这些方法将直接修改数组本身)
- pop和push
- shift(删除第一个)和unshift(插入)
- splice(index,howmany,value1,value2…)
- reverse
- sort 比较方法是如果返回<0,则按照从小到大的排序
2.访问方法(这些方法只是返回结果,不会修改数组)
- concat
- join
- slice
- toString
- indexOf和lastIndexOf
3.迭代方法
forEach
- array.forEach(function(val,index,array))
- forEach是无法通过break来中断数组的遍历的,可以通过使用try方法来抛出异常,终止遍历
try{ [1,2,3].forEach(function(val){ console.log(val); if(val==' ') throw(e) }) } catch(e){ console.log(e) }
- map
- filter
- array.filter(function(){}) 通过function来过滤array
- every和some
- reduce和reduceRight
十五.作用域链
之前都没有好好系统地学习作用域链,因此对这一块的知识都比较模糊
函数作为一个对象,拥有属于它的作用域链
1.在函数定义(创建)时,就保存了一个作用域链,它的作用域链被创建此函数的作用域中可访问的数据对象填充(也就是,创建对象的作用域链)
由于创建对象是window,所以作用域链里也只有全局对象2.在函数调用时,它创建了一个新的对象来存储它的局部变量,再将这个新的对象添加到之前的作用域链上(指创建时所保存的那个作用域链),同时创建一个新的表示该函数调用作用域的“链”(因为调用时是独一无二的,所以每次调用都会产生一个新的作用域链)
当查找变量x的值时,会逐个寻找作用域链上的每个对象,直到直到为止
所以,在js中要频繁访问地,处于比较深的作用域链上的对象,可以使用一个局部变量来保存,可提高访问速度
function (){
//可以先使用局部变量来保存全局变量document
//这样,如果需要重复使用document时,在作用域链上的第一个对象就能找到所需要的变量值
var doc = document
var list = document.getElementById('list')
}
参考文章:JavaScript 开发进阶:理解 JavaScript 作用域和作用域链
十六._proto_和prototype
首先,先了解一下new一个对象的过程
var Person = function() {} var p = new Person();
上面代码中,Person()是一个构造函数。
第一步,创建一个空白对象p,var p = {}
第二步,p._proto_ = Person.prototype,将对象的_proto_属性指向构造函数的prototype
第三步,Person.call(p),也就是构造p【将对象p通过this关键字传递到构造函数中并执行构造函数】_proto_是每个对象都会有的属性,它的值是对应的原型对象,而prototype是只有函数才有的属性。当一个函数作为构造函数(或使用new来调用)时,js会自动创建该函数的实例,创建的实例会继承构造函数的prototype的所有属性和方法
继承的实现就是将一个对象的_proto_属性指向构造函数的prototype。当查找一个对象的某个属性时,先查找自身是否有该属性,没有就寻找它的原型对象,通过_proto_来获得,还没找到就继续往上,诸如
obj._proto_._proto_...
,这就是原型链js正是通过
_proto_
和prototype
的合作实现了原型链,以及对象的继承。构造函数,通过
prototype
来存储要共享的属性和方法(因此常可以见到Obj.prototype.name
这样的写法来添加原型对象的属性),也可以直接设置 prototype指向现存的对象来继承该对象。
五星好评参考文章:_proto_和prototype
十七.严格模式
十八.设计模式
- 工厂模式
- 单体模式
- 实例模式
- 代理模式
- 职责链模式
- 命令模式
- 模板方法模式
- 策略模式
- 发布-订阅模式
- 中介者模式
* 尾记: 这篇文章是我在日常学习JS知识点中做的笔记,将记录我认为比较重要的参考资料,并写下一些我自己的理解。未完待续 *