zhaozy93 / blog

All | So Young
1 stars 0 forks source link

关于new的认识、附带lazyman和currying的理解 #6

Open zhaozy93 opened 7 years ago

zhaozy93 commented 7 years ago

关于new的认识、附带lazyman和currying的理解

最近在知乎看到了关于this的一个讨论和一篇简短精炼的专栏文章,在这里面确实加深了对this的理解,也写点什么记录巩固一下。文章里面也附带了两个小案例,都是之前碰到了,也不知道怎么在看文章时就联想到了,就又拿出来翻了翻,自己也实现了一下。

new

但是记忆里好像也知道new是如何使用的,在学习原型链的时候也知道new会起一些作用,但是没有专门理解过new的具体做法。 于是借着这个引子就仔细的看了一下大家的答案和文章。

在js中new确实是一个神奇的存在,大家都知道js不是真正的面向对象,然而在java这类语言中,new是一个通用的用来实例对象的关键字。用new实例后产生的新对象会继承类的一些属性和方法。

js中(es6之前)压根也没有类的概念,那new到底做了些什么呢。其实我们用new的都知道,new在js中也有实现继承的概念。上代码

function f( ) {
    this.x = 100;
}
let _f = new f( )
_f.x // 100

这样看来,new也起到了类似继承的概念,而且我们实践中告诉我们,如果一个函数(构造函数)是用来被new的,内部是可以放心使用this的,但是普通执行函数我们是不会在内部使用this的。

但是这是为什么呢,为什么构造函数内部使用this就有意义,普通函数内部的this就没意义呢。下面内容辅以看到的文章,再加上一点自己的理解,如有错误,轻喷。

this在什么情况下可以使用呢,我们知道this指向一个对象。如果在浏览器直接输出this,那么就是Window。如果是一个普通函数执行过程中输出this,就是执行环境。 如下面代码。 相信在像我这样的初级选手都会踩到这个坑,在不同的情况下,函数内的this指向不同的对象,因此有时先辈们才会发明出用self、that临时保存this的方法。

obj = {
    a: function(){
        console.log(this);
    }
}
obj.a( )   // Object {a: function}

现在可以得到一个共识,也是后面内容的基础,那就是this指向的是一个对象, 可是我们在书写构造函数的时候,没有声明this啊,而且this也不指向当前的运行环境。于是我们有了下面一个测试。

window.test;     // undefined
function f ( ){
    window.test == null;
    window.test = this;
}
f( );
window.test ;  // Window

let a = new f( );
window.test  // { }
a // {}
a === window.test   // true

神奇吗, 这段代码我们就可以知道了, 如果对一个函数使用new时,内部的this将不再指向执行环境,而是执行了一个不知道哪里来的对象,而且!!!最后这个不知道哪里来的对象还被return出去了。 我们通过 === 可以得出这个结论。

在做下一个测试,关于继承的。我们都知道js的继承是靠原型链来继承。在chrome下查看函数的原型链是func.prototype,查看对象的原型链是obj.__proto__

function f(){
}
f.prototype.test = function( ) {
    return ‘test’;
}
let a = new f ( );
a.test( )   // ‘test’
a.__proto__    // Object {test: function, constructor: function}
f.prototype == a.__proto__     // true

以上也证明了,在new过成功,f.prototype属性被赋给了 a.proto

因此 我们可以这样理解new let a = new f( ) ===

let instance = {};
f.apply(instance);
instance.__proto__ = f.prototype;
a = instance;

这样看来也就能解释之前的两个问题?

这样一看好像new所做的事情其实就是帮我们简写了代码量。 很不错,可能我们自己也能实现一个new关键字。嘻嘻。

new注意事项

lazyman

反正就突然想到了之前看过的这个题目。

实现一个LazyMan,可以按照以下方式调用: LazyMan(“Hank”)输出: Hi! This is Hank! LazyMan(“Hank”).sleep(10).eat(“dinner”)输出 Hi! This is Hank! //等待10秒. Wake up after 10 Eat dinner~

分析

这个题目很像jQuery,链式写法。ps:不知道为啥最近看啥都能想到jQuery,可能是在看源码吧,也可能jQuery对前端影响确实很深。这一点就告诉我们每次返回this大概能实现这个写法。然后这里面有一个很重要的内容就是sleep(10)要停顿10秒,js里面关于时间的就俩函数setTimeoutsetInterval,可能是setTimeout喽。也就是说要有一个方法能让她在setTimeout结束后再执行下一个任务,类似于一个总调度师。 于是大概就能知道了。

代码

最近的学习确实没白瞎,第一时间就想到了lazyMan只是一个表面函数,内部需要用new来实现,用一个任务管理器tasks来维护所有任务,在每个任务结束时执行next方法,所有方法都是通过new来实现继承的。最后链接中给出的答案可能更优,用的方法更简单比如 Array.prototype.shfit,日常不太常用,没想到。

function lazyMan(name){
  return new _lazyMan(name);
}
function _lazyMan(name){
  this.tasks = [];
  console.log(`hello ${name}`);
  setTimeout(()=>{
    this.next();
  },0)
}
_lazyMan.prototype.sleep =function(times){
  let that = this;
  let fn = function(){
    setTimeout(function(){
      console.log(`walk up after ${times}s`);
      that.next();
    }, times * 1000);
  }
  this.tasks.push(fn)
  return this;
}
_lazyMan.prototype.eat =function(food){
  let that = this;
  let fn = function(){
   console.log(`eat ${food}`);
   that.next();
  }
  this.tasks.push(fn)
  return this;
}
_lazyMan.prototype.next = function(){
  let task = this.tasks[0];
  if (task){
    this.tasks.splice(0,1);
    task();
  }
}
lazyMan('w').eat('a').sleep(10).eat('s')

柯理化

柯理化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

其实这次要讲的也不是柯理化,只是恰巧算是柯理化而已。

实现一个函数,按要求实现调用 f(1, 2) // 3 f(1, 2)(2) // 5 f(2)(2, 3)(3) //10

乍一看和上面lazyMan有点类似,也是一直调用。但有本质的区别,这一次是函数的一直调用,而不再是对象方法的一直调用,直观一点就是这一次是一直()()(),lazyMan是obj.a().b().c(), 因此lazyMan每次返回的是对象,这次肯定是返回function喽,否则肯定报错, error: xxx is not a function

而且要解决一个问题就是在最后一次调用完我们要将它输出累加的结果,而不能再返回一个function。还是一步一步来看,先实现每次都能返回一个function再说。

代码

function f(){
  let all = Array.prototype.slice.apply(arguments);
  function _f(){
    all = all.concat(Array.prototype.slice.apply(arguments));
    return _f
  }
  return _f
}

其实搞定一直循环return同一个function并没有啥难度,难的是最后那个数字是什么算出来的,也就是说最后怎么触发的。 其实这里是运用了valueOf 或者 toString两个函数。 然后添加_f的代码

_f.toString = function(){
    let sum = all.reduce(function(acc, val) {
      return acc + val;
    }, 0);
    return sum.toString();
  }
f.valueOf = function(){
    let sum = all.reduce(function(acc, val) {
      return acc + val;
    }, 0);
    return sum;
  }

这两个函数会在类型隐式转换时调用。以完成获取累加的效果。

references

zhihu: 关于JavaScript new 的一些疑问? zhihu专栏:JS 的 new 到底是干什么的? lazyMan:如何实现一个 LazyMan?

curring:掌握 JavaScript 函数的柯里化

bluexiaowei commented 7 years ago

老司机,带带我

zhaozy93 commented 7 years ago

@bluexiaowei 快上车