Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
zhaozy93 opened this issue Jun 24, 2017 · 2 comments
Open

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

zhaozy93 opened this issue Jun 24, 2017 · 2 comments

Comments

@zhaozy93
Copy link
Owner

zhaozy93 commented Jun 24, 2017

关于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;

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

  • this指向的这个对象不是飞来的,是new帮我们新建的,假设名为insatnce
  • 在执行构造函数时,this确实不指向运行环境,使用了类似于bind、apply、call的方法使其运行上下文变为刚刚新建的instance
  • 另一方面new将构造函数的原型链赋值给了新建的instance,主意是引用赋值哦

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

new注意事项

  • 在zhihu中还看到了对new Array(1, 2)和Array(1, 2)的讨论,翻了一下es标准

    The Array constructor is the %Array% intrinsic object and the initial value of the Array property of the global object. When called as a constructor it creates and initializes a new exotic Array object. When Array is called as a function rather than as a constructor, it also creates and initializes a new Array object. Thus the function call Array(…) is equivalent to the object creation expression new Array(…) with the same arguments.

    原文大概意思就是对于Array这个方法来说,new和直接调用做的事情其实都是new, 神奇吧。如果自己实现一个这样的方法也其实很简单啊,

    // 真正做事情的其实是_Array而已
    function Array(){
    	return new _Array(arguments)
    }
    
  • 构造函数内部有返回值的情况
    这个可以测试一下

     // 证明构造函数返回值为引用类型时,直接返回函数的引用值,不会为其增加原型链信息。然而,window.self对象上面还是会有原型链的属性,也就是说刚才那个模拟的过程在return时候出来那么一点点小问题。
     function ref(){
     	window.self = this;
     	return { a: 1}
     }
     ref.prototype.test = 2
     let a = new ref( ); 
    
     // 证明构造函数返回值为原始类型时,这个return可以忽略,最开始的模拟过程还是无懈可击。
     function ref(){
     	window.self = this;
     	return 100
     }
     ref.prototype.test = 2
     let a = new ref( ); 
    

new的最终模拟过程

function newLike( fn, arguments ){
	let instance = {}; // 新建模拟的this对象
	instance.__proto__ = fn.prototype; // 为新建的模拟this对象设置原型链
	let temp = fn.apply(instance, Array.prototype.slice(arguments)); 
	if( !temp){  // 如果fn没有返回值
		return instance
	} else if( typeof temp === ‘object’) {  // 如果是引用类型
		return temp
	} else {   // 原始类型 返回刚刚模拟的this
		return instance
	}
}

优化一下

// 当然最后这个if可以更精简
if (typeof temp === ‘object’){
	return temp
   }
 return instance

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
Copy link

老司机,带带我

@zhaozy93
Copy link
Owner Author

@bluexiaowei 快上车

@zhaozy93 zhaozy93 mentioned this issue May 1, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants