我不知道的前端(三)

181 阅读3分钟

记录我的学习笔记,本篇之前都是JavaScript相关的内容

prototype和proto的灵活应用

想必时间戳的各种转换需求大家见到的不算少,有时候封装一个方法非常有必要。 下边是一个format方法,可以让我们用期望的格式将Date对象转换为String

Date.prototype.format = function (format) {
  var o = {
    "M+": this.getMonth() + 1,
    "d+": this.getDate(),
    "h+": this.getHours(),
    "m+": this.getMinutes(),
    "s+": this.getSeconds(),
    "q+": Math.floor((this.getMonth() + 3) / 3),
    "S": this.getMilliseconds()
  }
  if (/(y+)/.test(format)) {
    format = format.replace(RegExp.$1, (this.getFullYear() + "")
      .substr(4 - RegExp.$1.length));
  }
  for (var k in o) {
    if (new RegExp("(" + k + ")").test(format)) {
      format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] :
        ("00" + o[k]).substr(("" + o[k]).length));
    }
  }
  return format;
}

调用

new Date().format("yyyy-MM-dd hh:mm:ss")

image.png 同理的,除了Date,我们也可以尝试为各种内置类添加各种实用的方法,有一个专门的prototype.js扩展库就为内置类添加了很多的实用方法。

使用上边的方法你会发现,你可以调用一个实例化Date对象的format方法,但是Date.format()是行不通的 这是自然的,想做到Date.format这种看着很像静态方法的操作(实际应用场景中其实没有所谓Date.format,常规需求里应该是希望有个类似于Array.range()的函数) 而想这么做的话,我们就需要使用下边这种方式:

Array.__proto__.range=function(a,b){
  let arr=[]
  if (a>b) {
    for (let index = a; index >= b; index--) {
      arr.push(index)
    }
  }else{
    for (let index = a; index < b; index++) {
      arr.push(index)
    }
  }
  return arr;
}

其实回过头来看,所谓的类就是一个构造函数,对应Date.__proto__其实就是Function.prototype

javascript中children和childNodes的区别

1,childNodes:它是标准属性,它返回指定元素的子元素集合,包括HTML节点,所有属性,文本节点。 可以通过nodeType来判断是哪种类型的节点,只有当nodeType==1时才是元素节点,2是属性节点,3是文本节点。

有些人错误的使用()去取该集合元素,下表列出各浏览器对childNodes(i)的支持情况:

IE6/7/8/Safari/Chrome/Opera IE9/Firefox childNodes(i) 支持 不支持 有时候需要获取指定元素的第一个HTML子节点(非属性/文本节点),最容易想到的就是firstChild 属性。代码中第一个HTML节点前如果有换行,空格,那么firstChild返回的就不是你想要的了。可以使用nodeType来判断下。

function getFirst(elem){
    for(var i=0,e;e=elem.childNodes[i++];){
        if(e.nodeType==1)
            return e;
    }
}

2,children:非标准属性,它返回指定元素的子元素集合。 但它只返回HTML节点,甚至不返回文本节点,虽然不是标准的DOM属性,但它和innerHTML方法一样,得到了几乎所有浏览器的支持。

和childNodes 一样,在Firefox下不支持()取集合元素。因此如果想获取指定元素的第一个HTML节点,可以使用children[0]来替代上面的getFirst函数。

这里需要注意的是children在IE中包含注释节点

this指向

this究竟绑了谁

this的作用域并不取决于代码在哪书写,而是在运行时才进行绑定。当然我们可以用bind函数硬修改this的作用域,这是我觉得非常实用的函数。

绑定规则

我们说js里所有东西都是对象

  • 默认绑定

    我们写一个独立的函数进行调用

    function foo(){
        console.log(this.a)
    }
    var a=2;
    foo();//2
    

    (类似这样的代码阅读体验非常差劲,但是我们可以从中学习一些js底层)

    这里的a声明在全局作用域中,所以被挂载到了全局对象上去。

    当我们调用this.a时,this指向全局对象故而输出2;

    整理一下这种情况的调用情况:

    • foo()在全局作用域中调用
    • foo()是直接调用
  • 隐式绑定

    如果我们把函数放到某一个对象里

    function foo(){
        console.log(this.a);
    }
    var obj={
        a:2,
        foo
    }
    obj.foo();//2
    foo();//undefined
    

    这种情况下:

    • foo()被包含在对象内
    • foo()的调用前方有对obj的引用
  • 显式绑定

    这是非常直观的绑定方式、

    function foo(){
        ...
    }
    let obj={
        a:2
    }
    foo.call(obj); // 2
    

    通过call。我们把它的this强行绑定到了obj上,于是输出obj.a

    当然我们知道对应的还有apply函数

    • bind

      bind函数是另一种强制绑定的解决方案,bind函数的工作原理非常简单,我们甚至可以手搓一个

      在这之前我们要知道bind函数接收一个对象,返回一个绑定了this的函数

      那么:

      Function.prototype.bind=function(obj){
          return ()=>{
              return this.apply(obj,arguments);
          }
      }
      

      这应该是我能想到的实现 bind 最少代码的形式;

  • new绑定

    在js中,并没有所谓的类,JavaScript的new操作符和传统面向对象的new并不一样;

    我们用new调用函数就是所谓的构造

    使用new来调用函数,会有如下操作:

    1. 创建一个全新的对象
    2. 新对象要执行[[Prototype]]连接
    3. 新对象绑定函数调用的this
    4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象
    function foo(a){
        this.a=a;
    }
    let bar=new foo(2);
    console.log(bar.a); // 2
    

不遵从this词法

我们上边说的并无问题,不过如果对于箭头函数,那么this的作用域又不一样

console.log=()=>{};

这样是一个箭头函数,而这样一个箭头函数的this实际上并不存在,箭头函数的this来自于它的上一层作用域