JS知识点

一.函数柯里化

参考文章
1. 前端开发者进阶之函数柯里化Currying
2. 柯里化的注释
3. 关于Array.slice.call(arguments, 1) 的思考

  1. 函数柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
  2. 通用的柯里化函数
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的秘密

  • setTimeOut原理(JavaScript引擎,gui渲染线程,事件触发线程)
  • setTimeOut(function,0)
  • 不只传两个参数,还能更多,这些是传给function的参数

三.继承

参考资料
* javascript继承详解
* javascript继承详解(二)

  1. JavaScript通过构造函数和原型的方式模拟实现了类的功能
  2. 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
  3. _proto_和prototype
    • 普通对象具有_proto_属性,指向该对象的构造方法的原型对象
    • 方法(function)除了具有 _proto_属性,还具有属性prototype,prototype指向该方法的原型对象。
    • prototype 用来实现基于原型的继承以及属性的共享
    • _proto_ 构成原型链,同样用于继承的实现
    函数的构造函数不就是Function嘛,因此它的__proto__指向了Function.prototype
    原型对象也是对象,它的_proto_指向它的构造函数的原型对象,即Object.prototype

* 原博还有第三、四、五篇,都是更高级的用法,对继承、声明的优化以及更优雅的写法,止步于第三篇…… *

继承的方式:
  1. 原型链继承
  2. 构造继承
  3. 实例继承
  4. 拷贝继承
  5. 组合继承
  6. 寄生组合继承
    (详见: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);//所以这里可以输出结果!

六 深度克隆

js中对象的深度克隆

  • 这篇文章中详细地讲解了为什么需要深度克隆,以及深度克隆的实现代码
  • 涉及原始类型和对象类型
    • 原始类型:存放的是对象的实体数据
    • 对象类型:存放的是对象的引用地址(实际内容存放在内存地址中,为了减少数据开销)

实现代码

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");
});

Query中click(),bind(),live(),delegate()的区别

九.js中的内存泄露

参考文章 js内存泄露的几种情况
JavaScript常见的内存泄漏原因

js跟java一样,都具有自动垃圾回收机制。

常见泄露原因

虽然JavaScript 会自动垃圾收集,但是如果我们的代码写法不当,会让变量一直处于“进入环境”的状态,无法被回收。下面列一下内存泄露常见的几种情况。

  1. 全局变量引起的内存泄露
  2. 闭包引起的内存泄露

    var leaks = (function(){
        var leak = 'asdf';   //被闭包所引用,不会被回收
        return function(){
            console.log(leak)
        }
    })()
  3. dom清空或删除时,事件未清空导致的内存泄漏

    1. $('#id').bind('click',function(){
          XXXXX
      }).remove()
      
      解决办法:清除事件
      $('#id').bind('click',function(){
          XXXXX
      }).off('click').remove()
    2. $('#id').onclick = function(){....}
      
      解决办法:
      将事件清除:
      $('#id').onclick = function(){$('#id').onclick = null;.....}
      
      或采用事件委托
  4. 子元素存在引用引起的内存泄露
    子元素如果没有被清空的话,那么跟它存在间接引用关系的父元素即使删除了,也仍然存在。

  5. 变量之间相互引用引起的内存泄露

    a和b的相互引用,导致a、b都不会被释放
    var a=document.getElementById("xx");
    var b=document.getElementById("xxx");
    a.r=b;
    b.r=a;
  6. 删除的属性引用依然存在引起的内存泄露

    有点像第4种的情况,存在间接引用的话不会被回收
    a = {p: {x: 1}};
    b = a.p;
    delete a.p;
  7. 自动类型装箱转换引起的内存泄露

    看网上资料,说下面的代码在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的用法

  1. size属性
    map.size()
    返回映射中的元素数

  2. clear方法
    清除所有映射

  3. delete方法
    map.delete(key)
    删除对应的键值对

  4. forEach方法
    map.forEach(callbackfn())
    回调函数中有三个参数
    callbackfn(item,key,mapObj)

  5. get方法
    得到键对应的值

  6. has方法
    判断是否含有某个key

  7. set方法
    设置键值对

详细文档 Map

十一.json的parse和stringify

  1. parse是从字符串中解析出json对象

    var str = '{"name":"huangxiaojian","age":"23"}'; //str是一个拼装的字符串
    var jsonobj = JSON.parse(str); //通过parse转换成json对象
  2. stringify是将json对象转换成字符串

    var a = {a:1,b:2}; //a是一个对象
    var str = JSON.stringify(a); //通过stringify转换成字符串

十二. 原生js实现事件委托

事件的3个阶段
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

  1. 首先,先了解一下new一个对象的过程

        var Person = function() {}
        var p = new Person();

    上面代码中,Person()是一个构造函数。
    第一步,创建一个空白对象p, var p = {}
    第二步,p._proto_ = Person.prototype,将对象的_proto_属性指向构造函数的prototype
    第三步,Person.call(p),也就是构造p【将对象p通过this关键字传递到构造函数中并执行构造函数】

  2. _proto_是每个对象都会有的属性,它的值是对应的原型对象,而prototype是只有函数才有的属性。当一个函数作为构造函数(或使用new来调用)时,js会自动创建该函数的实例,创建的实例会继承构造函数的prototype的所有属性和方法

  3. 继承的实现就是将一个对象的_proto_属性指向构造函数的prototype。当查找一个对象的某个属性时,先查找自身是否有该属性,没有就寻找它的原型对象,通过_proto_来获得,还没找到就继续往上,诸如obj._proto_._proto_... ,这就是原型链

  4. js正是通过_proto_prototype 的合作实现了原型链,以及对象的继承。

  5. 构造函数,通过prototype 来存储要共享的属性和方法(因此常可以见到Obj.prototype.name 这样的写法来添加原型对象的属性),也可以直接设置 prototype指向现存的对象来继承该对象。

五星好评参考文章:_proto_和prototype

十七.严格模式

详见:Javascript 严格模式详解

十八.设计模式

  1. 工厂模式
  2. 单体模式
  3. 实例模式
  4. 代理模式
  5. 职责链模式
  6. 命令模式
  7. 模板方法模式
  8. 策略模式
  9. 发布-订阅模式
  10. 中介者模式

详见:Javascript常用的设计模式详解


* 尾记: 这篇文章是我在日常学习JS知识点中做的笔记,将记录我认为比较重要的参考资料,并写下一些我自己的理解。未完待续 *

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值