JS异常函数之箭头函数,Js实现继承的几种方法及其优缺点

要搞懂JS继承,我们首先要理解原型链:每一个实例对象都有一个__proto__属性,在js内部用来查找原型链;每一个构造函数都有prototype属性,用来显示修改对象的原型,实例.__proto__=构造函数.prototype=原型。原型链的特点就是:通过实例.__proto__查找原型上的属性,从子类一直向上查找对象原型的属性,继而形成一个查找链即原型链。

如果你来自传统的强类型语言,可能会很熟悉void的概念:一种类型,告诉你函数和方法在调用时不返回任何内容。

在JS中,箭头函数可以像普通函数一样以多种方式使用。但是,它们一般用于需要匿名函数表达式,例如回调函数

1.原型链继承

void作为运算符存在于 JavaScript 中,而作为基本类型存在于 TypeScript
中。在这两个世界中,void的工作机制与大多数人习惯的有点不同。

下面示例显示举例箭头函数作为回调函数,尤其是对于map(),filter(),reduce(),sort()等数组方法。

我们使用原型继承时,主要利用sub.prototype=new
super,这样连通了子类-子类原型-父类。

JavaScript 中的 void

const scores = [ 1, 28, 66, 666];const maxScore = Math.max(...scores);scores.map(score = +(score / maxScore).toFixed(2)); 
//父类,带属性 function Super(){ this.flag = true; } //为了提高复用性,方法绑定在父类原型属性上 Super.prototype.getFlag = function(){ return this.flag; } //来个子类 function Sub(){ this.subFlag = false; } //实现继承 Sub.prototype = new Super; //给子类添加子类特有的方法,注意顺序要在继承之后 Sub.prototype.getSubFlag = function(){ return this.subFlag; } //构造实例 var es5 = new Sub;

JavaScript
中的void是一个运算符,用于计算它旁边的表达式。无论评估哪个表达式,void总是返回undefined。

乍一看,箭头函数似乎可以按常规函数来定义与使用,但事实并非如此。出于箭头函数的简洁性,它与常规函数有所不同,换一种看法,箭头函数也许可以把箭头函数看作是异常的
JS 函数。

缺点:构造函数原型上的属性在所有该构造函数构造的实例上是共享的,即属性没有私有化,原型上属性的改变会作用到所有的实例上。

leti =void2;// i === undefined

虽然箭头函数的语法非常简单,但这不是本文的重点。本文主要讲讲箭头函数与常规函数行为的差异,以及咱们如果利用这些差异来更好使用箭头函数。

2.构造函数继承

我们为什么需要这样的东西?首先在早期,人们能够覆盖undefined并给它一个实际值。void总是返回realundefined。

无论在严格模式还是非严格模式下,箭头函数都不能具有重复的命名参数。箭头函数没有arguments绑定。但是,它们可以访问最接近的非箭头父函数的arguments对象。箭头函数永远不能用作构造函数,自然的不能使用new关键字调用它们,因此,对于箭头函数不存在prototype属性。在函数的整个生命周期中,箭头函数内部的值保持不变,并且总是与接近的非箭头父函数中的值绑定。命名函数参数

在构造子类构造函数时内部使用call或apply来调用父类的构造函数

其次,这是一种调用立即调用函数的好方法:

JS中的函数通常用命名参数定义。命名参数用于根据位置将参数映射到函数作用域中的局部变量。

function Super(){ this.flag = true; } function Sub(){ Super.call(this) //如果父类可以需要接收参数,这里也可以直接传递 } var obj = new Sub(); obj.flag = flase; var obj_2 = new Sub(); console.log(obj.flag) //依然是true,不会相互影响
void function() { console.log('What')}()

来看看下面的函数:

优缺点:实现了属性的私有化,但是子类无法访问父类原型上的属性。

所有这些都没有污染全局命名空间:

function logParams (first, second, third) { console.log(first, second, third);}// first = 'Hello'// second = 'World'// third = '!!!'logParams('Hello', 'World', '!!!'); // "Hello" "World" "!!!"// first = { o: 3 }// second = [ 1, 2, 3 ]// third = undefinedlogParams({ o: 3 }, [ 1, 2, 3 ]); // {o: 3} [1, 2, 3]

3.组合继承

void function aRecursion(i) { if(i  0) { console.log(i--) aRecursion(i) }}(3)console.log(typeof aRecursion) // undefined

logParams()函数由三个命名参数定义:first、second和third。如果命名参数多于传递给函数的参数,则其余参数undefined。

利用构造函数和原型链的方法,可以比较完美的实现继承

由于void总是返回undefined,而void总是计算它旁边的表达式,你有一个非常简洁的方法从函数返回而不返回一个值,但仍然调用一个回调例如:

对于命名参数,JS函数在非严格模式下表现出奇怪的行为。在非严格模式下,JS函数允许有重复命名参数,来看看示例:

function Super(){ this.flag = true; } Super.prototype.getFlag = function(){ return this.flag; //继承方法 } function Sub(){ this.subFlag = flase Super.call(this) //继承属性 } Sub.prototype = new Super; var obj = new Sub(); Sub.prototype.constructor = Sub; Super.prototype.getSubFlag = function(){ return this.flag; }
// returning something else than undefined would crash the appfunction middleware(nextCallback) { if(conditionApplies()) { return void nextCallback(); }}
function logParams (first, second, first) { console.log(first, second);}// first = 'Hello'// second = 'World'// first = '!!!'logParams('Hello', 'World', '!!!'); // "!!!" "World"// first = { o: 3 }// second = [ 1, 2, 3 ]// first = undefinedlogParams({ o: 3 }, [ 1, 2, 3 ]); // undefined [1, 2, 3]

注:

这让我想到了void最重要的通途:它是你程序的安全门。当你的函数总是应该返回undefined时,你可以确保始终如此。

咱们可以看到,first参数重复了,因此,它被映射到传递给函数调用的第三个参数的值,覆盖了第一个参数,这不是一个让人喜欢的行为。

这里还有个小问题,Sub.prototype = new Super;
会导致Sub.prototype的constructor指向Super;然而constructor的定义是要指向原型属性对应的构造函数的,Sub.prototype是Sub构造函数的原型,所以应该添加一句纠正:Sub.prototype.constructor
= Sub;

button.onclick =()=voiddoSomething();
// 由于参数重复,严格模式会报错function logParams (first, second, first) { "use strict"; console.log(first, second);}

4.寄生继承

TypeScript 中的 void

箭头函数如何处理重复的参数

即将sub.prototype=new
super改为sub.prototype=Object.creat(supper.prototype),避免了组合继承中构造函数调用了两次的弊端。

TypeScript 中的void是undefined的子类型。 JavaScript
中的函数总是返回一些东西。要么它是一个值,要么是undefined:

关于箭头函数:

function iHaveNoReturnValue(i) { console.log(i)} // returns undefined

与常规函数不同,无论在严格模式还是非严格模式下,箭头函数都不允许重复参数,重复的参数将引发语法错误。

因为没有返回值的函数总是返回undefined,而void总是在 JavaScript 中返回
undefined,TypeScript
中的void是一个正确的类型,告诉开发人员这个函数返回undefined:

// 只要你敢写成重复的参数,我就敢死给你看const logParams = (first, second, first) = { console.log(first, second);} 
declarefunctioniHaveNoReturnValue(i: number):void

函数重载

void作为类型也可以用于参数和所有其他声明。唯一可以传递的值是undefined:

函数重载是定义函数的能力,这样就可以根据不同的参数数量来调用对应的函数,
JS 中可以利用绑定方式来实现这一功能。

declare function iTakeNoParameters(x: void): voidiTakeNoParameters() // iTakeNoParameters(undefined) // iTakeNoParameters(void 2) // 

来看个简单的重载函数,计算传入参数的平均值:

所以void和undefined几乎是一样的。虽然有一点点不同,但这种差别很大:作为返回类型的void可以用不同的类型替换,以允许高级回调模式:

function average() { const length = arguments.length; if (length == 0) return 0; // 将参数转换为数组 const numbers = Array.prototype.slice.call(arguments); const sumReduceFn = function (a, b) { return a + Number(b) }; // 返回数组元素的总和除以数组的长度 return numbers.reduce(sumReduceFn, 0) / length;}
function doSomething(callback: () = void) { let c = callback() // at this position, callback always returns undefined //c is also of type undefiend}// this function returns a numberfunction aNumberCallback(): number { return 2;}// works type safety is ensured in doSometingdoSomething(aNumberCallback) 

这样函数可以用任意数量的参数调用,从0到函数可以接受的最大参数数量应该是255。

这是期望的行为,通常用于 JavaScript
程序。你可以在我的其他文章中阅读更多关于这种被称为substitutability的模式。

average(); // 0average('3o', 4, 5); // NaNaverage('1', 2, '3', 4, '5', 6, 7, 8, 9, 10); // 5.5average(1.75, 2.25, 3.5, 4.125, 5.875); // 3.5 

如果你想确保传递只返回undefined的函数,请确保调整你的回调方法签名:

现在尝试使用剪头函数语法复制average()函数,一般咱们会觉得,这没啥难的,无法就这样:

- function doSomething(callback: () = void) {+ function doSomething(callback: () = undefined) { /* ... */ }function aNumberCallback(): number { return 2; }// types don't matchdoSomething(aNumberCallback) 
const average = () = { const length = arguments.length; if (length == 0) return 0; const numbers = Array.prototype.slice.call(arguments); const sumReduceFn = function (a, b) { return a + Number(b) }; return numbers.reduce(sumReduceFn, 0) / length;}

大概大部分时间你都能和void很好的相处。

现在测试这个函数时,咱们会发现它会抛出一个引用错误,arguments未定义。

咱们做错了啥

对于箭头函数:

与常规函数不同,arguments不存在于箭头函数中。但是,可以访问非箭头父函数的arguments对象。

基于这种理解,可以将average()函数修改为一个常规函数,该函数将返回立即调用的嵌套箭头函数执行的结果,该嵌套箭头函数就能够访问父函数的arguments。

function average() { return (() = { const length = arguments.length; if (length == 0) return 0; const numbers = Array.prototype.slice.call(arguments); const sumReduceFn = function (a, b) { return a + Number(b) }; return numbers.reduce(sumReduceFn, 0) / length; })();}

这样就可以解决了arguments对象没有定义的问题,但这种狗屎做法显然很多余了。

做点不一样的

对于上面问题是否存在替代方法呢,可以使用 es6 的rest参数。

使用ES6rest参数,咱们可以得到一个数组,该数组保存了传递给该函数的所有的参数。rest语法适用于所有类型的函数,无论是常规函数还是箭头函数。

const average = (...args) = { if (args.length == 0) return 0; const sumReduceFn = function (a, b) { return a + Number(b) }; return args.reduce(sumReduceFn, 0) / args.length;}

发表评论

电子邮件地址不会被公开。 必填项已用*标注