一、概述
本章节我们主要对ES5常用的数组方法进行封装,以及改变this指向的方法
bind
、call
、apply
进行封装除此之外也会涉及到一些常用的案例及使用场景
二、bind封装
bind方法会创建一个新的函数,当这个函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。
总结:
- bind会返回一个函数体,而不是立即执行返回的函数,这也是它与call、apply的区别
// bind封装
Function.prototype.bindAlley = function(context) {
context = context || window;
// 其实this就是调用bindAlley的那个函数
var _this = this;
// 获取bindAlley中传入的其他参数
var args = Array.prototype.slice.call(arguments,1);
// 返回函数体
return function() {
var bindArgs = Array.prototype.slice.call(arguments);
return _this.apply(context,args.concat(bindArgs));
}
}
这样封装是不是很完美?不其实我们还差很多,因为使用bind返回的函数体也是可以使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
也就是说当 bind 返回的函数作为构造函数的时候,bind 时指定的 this 值会失效,但传入的参数依然生效。举个例子:
var foo = {
value: 2
};
function bar(name, age) {
this.habit = 'shopping';
console.log(this.value);
console.log(name);
console.log(age);
}
bar.prototype.friend = 'kevin';
var bindFoo = bar.bindAlley1(foo, 'daisy');
var obj = new bindFoo('18');
console.log(obj.habit); // undefined
console.log(obj.friend);// undefined
完整版实现
// bind封装
Function.prototype.bindAlley = function(context) {
// 其实this就是调用bindAlley的那个函数
var _this = this;
// 获取bindAlley中传入的其他参数
var args = Array.prototype.slice.call(arguments,1);
var fNOP = function() {};
var fBound = function() {
// 将函数体的参数与bindAlley中的参数进行组合
var argsArr = args.concat(Array.prototype.slice.call(arguments));
// isPrototypeOf:检测一个对象是否是另一个对象的原型
return _this.apply(fNOP.prototype.isPrototypeOf(this) ? this : context, argsArr);
};
// 以下这几行代码其实就是一个构造函数继承
if(this.prototype) {
fNOP.prototype = this.prototype;
}
fBound.prototype = new fNOP();
// 返回函数体
return fBound
}
三、call封装
相比较而言call的封装比较简单。我们先来说下call的基本用法,
fn.call(context,arg1,arg2,...)
- 参数一是this的指向
- 剩余参数是需要传入到函数中的参数
- 特点是call会立即执行,而不是像bind一样返回一个函数体
// call 封装
Function.prototype.callAlley = function(context){
context = context || window;
// 这里记住this指调用callAlley的函数
context.fn = this;
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
/*
在eval中,args 自动调用 args.toString()方法,最终的效果相当于:
var result = context.fn(arguments[1], arguments[2], ...);
*/
var result = eval('context.fn('+args+')');
delete context.fn;
return result;
}
四、apply封装
相比较而言apply的封装比较简单。我们先来说下apply的基本用法,
fn.call(context,[arg1,arg2,...])
- 参数一是this的指向
- 参数二是一个数组
- 特点是apply会立即执行,而不是像bind一样返回一个函数体
Function.prototype.applyAlley = function(context,args) {
context = context || window;
context.fn = this;
var result;
// 如果参数没有传递
if(!args) {
result = context.fn();
} else {
// 如果参数传递封装方法与call一样
var arr = [];
for(var i=0;i<args.length;i++){
arr.push('args['+i+']');
}
result = eval('context.fn('+arr+')');
}
delete context.fn;
return result;
}
五、数组map方法封装
首先我们先分析map的参数
Array.map(callback,context)
参数一是需要执行的回调函数,回调函数中参数有3个value
、index
、调用map方法的这个数组,context
为回调函数的this指向
// map方法封装
Array.prototype.mapAlley = function(callback,context) {
context = context || window;
if(typeof callback !== 'function') {
throw new TypeError(callback+'is not a function');
}
var arr = new Array(this.length);
for(var i=0;i<arr.length;i++) {
if(this.hasOwnProperty(this[i])) {
arr.push(callback.apply(context,this[i],i,this));
}
}
return arr;
}
六、数组forEach方法封装
原文:segmentfault.com/a/1190000030694113
我们都知道,
forEach()
方法对数组的每个元素执行一次给定的函数。它的语法也很简单:arr.forEach(callback(currentValue [, index [, array]])[, thisArg])
:因为很多时候,forEach 方法被用来代替 for 循环来完成数组的遍历,所以经常可以看见 forEach 的一些 js 实现,例如
Array.prototype.forEachAlley = function (fn, context) {
context = context || arguments[1];
if (typeof fn !== 'function') {
throw new TypeError(fn + 'is not a function');
}
for (let i = 0; i < this.length; i++) {
fn.call(context, this[i], i, this);
}
};
但是这个
forEach
真的对吗?我们和原生的forEach
进行对比
// 示例1
const items = ['', 'item2', 'item3', , undefined, null, 0];
items.forEach((item) => {
console.log(item); // 依次打印:'',item2,item3,undefined,null,0
});
items.forEachAlley((item) => {
console.log(item); // 依次打印:'',item2,item3,undefined,undefined,null,0
});
// 示例2
let arr = new Array(8);
arr.forEach((item) => {
console.log(item); // 无打印输出
});
arr[1] = 9;
arr[5] = 3;
arr.forEach((item) => {
console.log(item); // 打印输出:9 3
});
arr.forEachCustom((item) => {
console.log(item); // 打印输出:undefined 9 undefined*3 3 undefined*2
});
图一.jpg这到底是怎么回事?我们可以通过下图分析到,真正执行遍历操作的是第 8 条,通过一个 while 循环来实现,循环的终止条件是前面获取到的数组的长度(也就是说后期改变数组长度不会影响遍历次数),while 循环里,会先把当前遍历项的下标转为字符串,通过 HasProperty 方法判断数组对象中是否有下标对应的已初始化的项,有的话,获取对应的值,执行回调,没有的话,不会执行回调函数,而是直接遍历下一项。
如此看来,forEach 不对未初始化的值进行任何操作
改版后的实现
Array.prototype.forEachAlley = function(callback,context) {
context = context || arguments[1];
if(typeof callback !== 'function') {
throw new TypeError(callback+'is not a function');
}
let len = this.length;
let n = 0;
while(n<len){
if(this.hasOwnProperty(n)){
callback.call(context,this[n],n,this);
}
n++;
}
}
七、数组isArray方法封装
该方法检测当前对象是否是一个数组,返回一个布尔值
Array.isArrayAlley = function(params) {
return Object.prototype.toString.call(params).slice(8,-1).toLocaleLowerCase()
===
'array';
}
八、数组indexOf方法封装
该方法用于查找传入对象在数组中的位置,并返回该位置,若没有找到则返回-1,该方法不能用于寻找undefined。
Array.prototype.indexOfAlley = function(params) {
var index = -1;
var len = this.length;
for(var i=0; i<len; i++) {
if(this[i] === params && this[i] !== 'undefined') {
index = i;
break;
}
}
return index;
}
九、数组lastIndexOf方法封装
lastIndexOf
与indexOf
方法一样只不过它是倒着循环
Array.prototype.lastIndexOfAlley = function(params) {
var index = -1;
var len = this.length;
for(var i = len-1; i>=0; i--) {
if(this[i] === params && this[i] !== 'undefined') {
index = i;
break;
}
}
return index;
}
十、数组every方法封装
every()是对数组中每一项运行给定函数,如果该函数对每一项返回true,则返回true
Array.prototype.everyAlley = function(callback) {
if(typeof callback !== 'function') {
throw new TypeError(callback+'is not a function');
}
var len = this.length,
bStop = true;
for(var i=0; i<len; i++) {
if(!(bStop = callback(this[i],i,this))) {
break;
}
}
return bStop;
}
十一、数组some方法封装
some()是对数组中每一项运行给定函数,如果该函数对任一项返回true,则返回true
Array.prototype.someAlley = function(callback) {
if(typeof callback !== 'function') {
throw new TypeError(callback+'is not a function');
}
var len = this.length,
bStop = false;
for(var i=0; i<len; i++) {
if(bStop = callback(this[i],i,this)) {
break;
}
}
return bStop;
}
十二、数组filter方法封装
filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
注意: filter() 不会对空数组进行检测。
注意: filter() 不会改变原始数组
Array.prototype.filterAlley = function(callback) {
if(typeof callback !== 'function') {
throw new TypeError(callback+'is not a function');
}
if(this.length === 0) return;
var len = this;
var arr = [];
for(var i=0;i<len;i++) {
if(callback(this[i],i,this)) {
arr.push(this[i]);
}
}
return;
}
十三、数组reduce方法封装
reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
reduce() 可以作为一个高阶函数,用于函数的 compose。
注意: reduce() 对于空数组是不会执行回调函数的。
Array.prototype.reduceAlley = function(callback,initiaVale) {
if(typeof callback !== 'function') {
throw new TypeError(callback+'is not a function');
}
var len = this.length;
if(len === 0) return;
var prev, isInitiaValue = false, arr = this;
if (initiaVale) {
prev = initiaVale;
isInitiaValue = true;
}
for(var i=0; i<len; i++) {
if (isInitiaValue) {
prev = callback(prev,arr[i],i,this);
} else {
prev = arr[i];
isInitiaValue = true;
}
}
return prev;
}
十四、reduce常见的应用
1、数组扁平化
var arr1 = [[1],[[2],[3]],[[[4]]]];
function flat(arr) {
return arr.reduce((prev,curr)=>{
return prev.concat(Array.isArray(curr) ? flat(curr) : curr);
},[])
}
console.log(flat(arr1))
2、计算数组中每个元素出现的次数
var arr = [1,1,2,2,3,4];
var count = arr.reduce((prev,curr)=>{
if(curr in prev){
prev[curr]++
}else{
prev[curr] = 1;
}
return prev;
},{})
3、获取url中传递的参数
/**
* @module getUrlParams
* @description 获取url中的参数,可以
* @param {String} url 一个url地址,可以不传,默认会取window.location.href
* @returns {*} 返回一个对象
*/
function getUrlParams(url) {
const _url = url || window.location.href;
const _urlParams = _url.match(/([?&])(.+?=[^&]+)/igm);
return _urlParams ? _urlParams.reduce((prev, curr) => {
const value = curr.slice(1).split('=');
prev[value[0]] = value[1]
return prev;
}, {}) : {};
}
网友评论