在牛客网上面有很多的编程题去练习,js能力测评是其中的一个,相对来说比较简单,但是也是比较不错的练手巩固js能力的方法
自己在做的过程中,发现很多部分自己能力不足(特别是函数还有正则表达式),下面对一些题目进行总结归纳
函数上下文
改变函数上下文
//三种方案
//apply
function speak(fn, obj) {
return fn.apply(obj);
}
//call
function speak(fn, obj) {
return fn.call(obj);
}
//bind
function speak(fn, obj) {
return fn.bind(obj)();
}
返回函数
简单版
兼顾了多个参数(有点难理解)
function functionFunction(str) {
var ret = Array.prototype.slice.call(arguments).join(', ');
var temp = function (str) {
ret = [ret, Array.prototype.slice.call(arguments).join(', ')].join(', ');
return temp;
};
temp.toString = function () {
return ret;
};
return temp;
}
使用闭包
要求的是返回一个函数数组,如果在循环中直接写
result[i] = function () {
return fn(arr[i]);
}
//or
result.push(function () {
return fn(arr[i]);
})
最终的结果是不正确的,因为在每次迭代的时候,那样的语句后面的方法并没有执行,只是创建了一个函数体为“return fn(arr[i]);”的函数对象而已,当迭代停止时,i为最终迭代停止的值,在函数被调用时,i依旧为最终迭代停止的值,因此无法返回正确的结果。
//这种是错误的写法会导致result中每个函数的参数都是arr[arr.length]
function makeClosures(arr, fn) {
var result = new Array();
for (var i = 0; i < arr.length; i++) {
result[i] = function () {
return fn(arr[i]);
};
}
return result;
}
为了解决这个问题,需要声明一个匿名函数,并立即执行它。
function (num) {
return function () {
return fn(arr[num]);
};
}(i)
函数执行后,i立即传入并被内部函数访问到,因此就能得到正确的结果。闭包允许你引用存在于外部函数中的变量。
下面是forEach循环
function makeClosures(arr, fn) {
var result = [];
arr.forEach(function (e) {
result.push(function (num) {
return function () {
return fn(num);
};
}(e));
});
return result;
}
//参考《JavaScript高级程序设计》的典型方法
function makeClosures(arr, fn) {
var result = new Array();
for (var i = 0; i < arr.length; i++) {
result[i] = function (num) {
return function () {
return fn(num);
}
}(arr[i]);
}
return result;
}
//使用ES5的bind()方法
function makeClosures(arr, fn) {
var result = new Array();
for (var i = 0; i < arr.length; i++) {
result[i] = fn.bind(null, arr[i]);
}
return result;
}
二次封装函数
function partialUsingArguments(fn) { //先获取p函数第一个参数之后的全部参数
var args = Array.prototype.slice.call(arguments, 1); //声明result函数
var result = function () { //使用concat合并两个或多个数组中的元素
return fn.apply(null, args.concat([].slice.call(arguments)));
}
return result;
}
// call和apply必须显式地调用str3,立即执行
// bind不是立即执行,未传入str3时,并未执行,只是返回一个函数,等待参数传入
// this用于上下文不确定的情况
// call
function partial(fn, str1, str2) {
function result(str3) {
return fn.call(this, str1, str2, str3);
}
return result;
}
// apply(这里只是为了对照)
function partial(fn, str1, str2) {
function result(str3) {
return fn.apply(this, [str1, str2, str3]);
}
return result;
}
// 这个bind会生成一个新函数(对象), 它的str1, str2参数都定死了, str3未传入, 一旦传入就会执行
function partial(fn, str1, str2) {
return fn.bind(this, str1, str2); // 或 return fn.bind(null, str1, str2);
}
// bind同上, 多了一步, 把str3传入的过程写在另一个函数里面,
// 而另一个函数也有str1, str2参数
// 此法有种多次一举的感觉,但是表示出了后续的调用。
function partial(fn, str1, str2) {
function result(str3) {
return fn.bind(this, str1, str2)(str3);
}
return result;
}
// 匿名函数,默认this绑定global,与bind的第一个参数为this时效果一样。
function partial(fn, str1, str2) {
return function (str3) {
return fn(str1, str2, str3);
}
}
// ES6。this指向undefined.
const partial = (fn, str1, str2) => str3 => fn(str1, str2, str3);
使用apply调用函数
function callIt(fn) {
//将arguments转化为数组后,截取第一个元素之后的所有元素
var args = Array.prototype.slice.call(arguments, 1); //调用fn
var result = fn.apply(null, args);
return result;
}
柯里化
柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术
function curryIt(fn) {
//获取fn参数的数量
var n = fn.length;
//声明一个数组args
var args = [];
//返回一个匿名函数
return function(arg){
//将curryIt后面括号中的参数放入数组
args.push(arg);
//如果args中的参数个数小于fn函数的参数个数,
//则执行arguments.callee(其作用是引用当前正在执行的函数,这里是返回的当前匿名函数)。
//否则,返回fn的调用结果
if(args.length < n){
return arguments.callee;
}else return fn.apply("",args);
}
}
二进制转换
parseInt方法可以将其它进制转换为十进制,只需要给该方法传入需要转换的字符串和该字符串的进制表示两个参数即可。
function base10(str) {
/**
其它进制转十进制
parseInt(str,2)
parseInt(str,8)
parseInt(str,16)
*/
return parseInt(str, 2);
}
function valueAtBit(num, bit) {
return (num >> (bit -1)) & 1;
}
function convertToBinary(num) { //转换为2进制格式
var s = num.toString(2); //获得2进制数长度
var l = s.length;
if (l < 8) { //声明一个字符串用于补满0
var s1 = "0000000";
var s2 = s1.slice(0, 8 - l);
s = s2 + s;
}
return s;
}
批量改变对象的属性
这是原型链问题。访问一个对象的方法或者是属性,首先会在该对象中寻找,如果找到则返回,如果没找到,则在其原型链上面向上寻找,直至基原型,如还未找到,则返回undefined。将 constructor 的所有实例的 greeting 属性指向给定的 greeting 变量,只需要在constructor的原型上面添加greeting属性,并指定值。
function alterObjects(constructor, greeting) {
constructor.prototype.greeting = greeting;
}
遍历属性
function iterate(obj) {
var arr = [];
//使用for-in遍历对象属性
for(var key in obj){
//判断key是否为对象本身的属性
if(obj.hasOwnProperty(key)){
//将属性和值按格式存入数组
arr.push(key+": "+obj[key]);
}
}
return arr;
}
apply和call的用法
总结
上面的题目是非常经典的,而且在项目中经常用到,这也是这个专题为什么教过js能力测评的原因。javascript变化很快,但是我想只要掌握了基础,那么就算出es8/es9都万变不离其宗,务必领悟其中的思想