《函数柯里化》

183 阅读6分钟

一.

什么是函数柯里化?把接受多个参数的函数,转换成一个接受单一参数的函数

就是函数里return 另一个函数

function sum(x){
  return function(y){
    return x+y
  }
}

sum(1)(2)   //3

普通方法:

function sum(x,y){
    return x+y
}

也就是说,原来实现两个数相加,要在一个括号里传两个参数。

利用函数柯里化,每次调用只传一个参数,返回一个匿名函数,再传一个参数调用,就会得到最后的结果。

三个数相加也同理:

function sum(x){
  return function(y){
    return function(z){
      return x+y+z
    }
  }
}
console.log(sum(1)(2)(3))  //6

二. Object.defineProperty

它有哪些可配置的选项?

首先,该方法可用于给一个对象添加新的属性,或者,修改已有的属性。

语法:Object.defineProperty(obj, prop, descriptor)

obj:要定义属性的对象

prop:要添加或修改的属性名(一定要加引号!)

descriptor:对象,属性描述符(包括数据描述符和存取描述符两种,只能选其一)

数据描述符:

可以有value writable两个可配置的选项,是一个具有值的属性,该值可以是可写的,也可以是不可写的。

Object.defineProperty(obj,'a',{
    value:1,
    writable:true
})

value就是该属性的值。

writable 表示该属性是否可写。如果不指定,默认是false,表示不能修改obj.a,但是修改了也不会报错,只是改不了,下次再读a的值,还是1.

存取描述符:

由 getter 函数和 setter 函数所描述的属性

var bValue = 38;
Object.defineProperty(o, "b", {
  get() { return bValue; },
  set(newValue) { bValue = newValue; },
});

两类描述符可以共有的配置选项

1. enumerable

当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。 默认为 false。

是否是枚举属性,体现在该属性是否能在for(let key in obj)中循环,和Object.keys() 被枚举。如果是ture,这两种操作就能得到该属性。是false,就得不到。

2. configurable

表示对象的属性是否可以被删除,以及除 value 和 writable 特性外的其他特性是否可以被修改。如果不设置,默认就是false。

看看数据描述符:

let obj={}
Object.defineProperty(obj,'a',{
  value:1,
  enumerable:true,
})
Object.defineProperty(obj,'b',{
  value:2,
})
Object.defineProperty(obj,'c',{
  value:3,
})
Object.defineProperty(obj,'d',{
  value:4,
  enumerable:true,
})

for(let key in obj){
  console.log(key)
}

console.log(Object.keys(obj))
//结果:
"a"
"d"
["a", "d"]

三. 绑定事件中,@click和@click.native有什么区别

@click

就是给html元素绑定事件

@click.native

是给组件绑定原生事件的,只能用在组件上,不可以用在原生元素上。@click.native 监听组件根元素的原生事件 。

如:父组件A,子组件B

// 父组件:
<template>
    <B @click="c"/>
</template>
methods:{
    c(){}
}

在子组件的身上监听,如果只写@click,点击B是不会触发事件c的,监听不了。

一种解决方法是在子组件的里边,某一个元素上监听事件@click,如果点击了,触发一个函数a,a里边用$emit手动触发click函数,然后在父组件那里,子组件的身上再用@click监听。

使用.native 修饰符,就可以使代码变得简洁。子组件里边,不再需要监听,触发。只需要在父组件那里,用@click.native ,这样点击B,事件处理函数c能被触发了。

<template>
    <B @click.native="c"/>
</template>

@click.self

<div @click.self="c">
    aaa
</div>

只有e.target是当前这个元素时,才会触发函数。冒泡或捕获经过它,都不会触发函数。

四. CSS实现各种形状

1. 半圆

.semi{
  margin:100px;
  width:100px;
  height:100px;
  background:black;
  border-radius:100px 100px 0 0;
  height:50px;
}

先画宽高相等的正方形,然后加圆角,左上和右上的圆角值等于宽高值,左下和右下无圆角。半圆:再把高度减半。

2. 扇形

1. 正方形=》加一个圆角

.shan1{
  margin:50px;
  width:50px;
  height:50px;
  background:black;
  border-radius:50px 0 0 0;
}

圆角值和宽高值相等。四分之一扇形。

2. 给三角形加圆角实现扇形

.shan{
  width:0;
  height:0;
  border-top:50px solid red;
  border-left:50px solid transparent;
  border-right:50px solid transparent;
  border-top-left-radius:50%;
  border-top-right-radius:50%;
}

宽高为0,利用border弄出一个三角形。再给三角形加圆角。

3. 三角形

1. 宽高为0,加border

.shan{
  width:0;
  height:0;
  border-top:50px solid red;
  border-left:50px solid transparent;
  border-right:50px solid transparent;
}

得到一个等腰直角三角形,锐角或钝角呢?

锐角:上边界>左右边界border-top:100px solid red;

钝角:上边界<左右边界 border-top:20px solid red;

2. 用clip-path 描点画边

.shan{
  background:black;
  width:100px;
  height:100px;
  clip-path:polygon(0px 0px,100px 0px,50px 100px);
}

4. 弧形

.semi{
  margin:100px;
  width:100px;
  height:100px;
  background:black;
  border-radius:100px 0 100px 0;
  transform:rotate(45deg);
}

先正方形,对角加圆角,而且值是宽高值。比如,左上角 和右下角

五. 手写实现call,apply,bind

1. 实现call

let obj={
  name:'胡安琪',
  say(){
    console.log(`我叫${this.name}`)
    return this.name
  }
}
let obj2={name:'啊啊啊'}
let result=obj.say.call(obj2)

首先明确,调用call的一定是一个函数,不管是对象里的函数还是普通函数。

call()函数需要哪些参数?情况1:可能调用的时候没传参数f() === f.call() 情况2:传了新的thisobj.say.call(obj2) 情况3:除了this,还传了其他参数列表obj.say.call(obj2,1,2,3)

综上,定义一个自己写的myCall函数。它要定义在函数实例的原型上:Function.prototype.myCall=function(context){} 并且接受一个context作为第一个参数,context就是我们指定的调用了call的那个函数里边的新this。如obj2

首先判断,如果没传context,或者传的是undefined 或 null,那么就要修改为window。

怎么把say函数里的this修改为obj2呢? 给obj2也就是context扩展一个属性say,让context也拥有say这个方法,然后调用context.say,这样say函数里的this自然就是context了。具体做法:让context里的一个新属性指向函数say。在myCall函数里的this就是外边调用它的say函数。say.myCall() === say.myCall.call(say) say是函数,函数也是对象,对象.函数的调用就是隐式传this,this就是前边的对象也就是say

Function.prototype.myCall=function(context){
  console.log(context)
  if(context===undefined || context===null){context=window}
  let key=Symbol()
  console.log(this)
  context[key]=this
  let args=[...arguments].slice(1)
  let result=context[key](...args)
  delete context[key]
  return result
}

Symbol() 会生成一个独一无二的symbol值,把这个key变量作为context的一个属性,为了避免与其他属性冲突。

arguments是伪数组,是在调用myCall时包含了context的所有参数。所以要把context排除,得到其余的参数。然后调用这个在context身上扩展的方法。以防say函数里有返回值,我们也要拿到返回值并且return 出去。return 之前还要记得把这个扩展的属性删掉。

重点:context[key]=this给context扩展一个新的属性,指向调用了myCall的那个函数。那个函数在myCall里就是它的this

2. 实现apply

apply和call几乎完全一样,只有参数形式不同。fn.apply(obj2,[1,2,3]) 调用apply,第一个参数是新this,第二个参数必须是数组,数组里的每一项是fn的参数。

所以实现apply,只有处理参数的地方有所不同。

Function.prototype.myApply=function(context){
  if(context===undefined || context===null){context=window}
  let key=Symbol()
  let result
  context[key]=this
  let args=arguments[1]  //数组
  if(args){
    result=context[key](...args)
  }else{
    result=context[key]()
  }
  delete context[key]
  return result
}

通过arguments[1] 拿到数组参数,要判断有没有。如果fn.myApply() 时没给传参数列表数组,那args就是undefined。

3. 实现bind

bind的基础用法:

var name='周杰伦'
let obj={
  name:'胡安琪',
  say(x,y){
    console.log(`我叫${this.name},和为${x+y}`)
    return this.name
  }
}
let obj2={name:'啊啊啊'}
let newsay=obj.say.bind(obj2,3,4)(1,2)  //"我叫啊啊啊,和为7"
let newsay=obj.say.bind(obj2,3)(2,1)   //"我叫啊啊啊,和为5"

一个函数调用了bind,并且返回一个新函数。调用bind可以传参数,第一个参数是为返回的新函数绑定的this上下文,也就是说,如果调用这个新函数,新函数里的this就是bind后的第一个参数。后边也可以给传一些预设参数。

然后这个新函数拥有say函数相同的功能。调用新函数的时候,也可以给传参数。但是如果say只需要两个实参,但是bind的时候传了两个,调用新函数的时候又传了两个,那么实际只有前两个起作用。如果bind传了一个,新函数传了两个,那么实际也是前两个起作用。

所以两次的参数都需要提取出来。

Function.prototype.myBind=function(context){
  if(typeof this !=='function'){return}
  let args=[...arguments].slice(1)  // [] 或[x,x,x]
  let self=this
  return function(){
    let innerArgs=[...arguments]
    return self.apply(context,args.concat(innerArgs))
  }
}

返回的新函数,其实就是执行bind前边的那个函数,只不过this是指定的context,其余参数是bind 和新函数的参数拼接,至于真正需要几个,自己就取几个。

bind 返回的新函数,除了正常调用之外,也有可能被当作构造函数 前边加new 调用。当用new调用时,self.apply调用旧函数,这时后边的第一个参数上下文就不是bind绑定的那个context了,而是调用新函数得到的那个实例,let a=new say1() 也就是匿名函数里的this。所以加一个判断,看看这个返回的新函数有没有被new 调用