js 对一个字段去重_js基本知识考核.md

### 引言

在学习 JavaScript 的过程中看了不少书,但却很少有专项练习,因此开这样一个专题,希望能在练习中复习巩固所学的知识~ :)

#### 函数篇(一)

本篇主要考察编写函数的技能和基本知识,同时也是对 JavaScript 标准库函数的练习。

**1. 写一个函数用来反转数字**

```

reverseNumber(12345); // 54321

```

**2. 写一个函数来检测传入的字符串是否为回文**

```

isPalindrome('hello'); // false

isPalindrome('madam'); // true

```

**3. 写一个函数对传入的字符串输出所有子字符串组合**(注意去重)

```

substrings('dog'); // ['d', 'do', 'dog', 'o', 'og', 'g']

```

**4. 写一个函数对传入的字符串重新按字母排序**

```

reorderStr('webmaster'); // abeemrstw

```

**5. 写一个函数对传入的字符串中每个单词的首字母大写**

```

upperWords('the quick brown fox'); // The Quick Brown Fox

```

**6. 写一个函数找出传入的字符串中最长的单词**

```

findLongest('Web Development Tutorial'); // Development

```

#### 答案

以下给出我的解法,期待能有更好的答案。

```

// 1. 写一个函数用来反转数字

(function(){

'use strict';

function reverseNumber(num) {

if(typeof num !== 'number') {

throw "Pls input a number!";

}

var result = num.toString().split('').reverse().join('');

return +result;

}

console.log(reverseNumber(12345));

})();

```

```

// 2. 写一个函数来检测传入的字符串是否为回文

(function(){

'use strict';

function isPalindrome(str) {

if(typeof str !== 'string') {

throw "Pls input a string!";

}

var tmp = str.split('').reverse().join('');

return tmp === str;

}

console.log(isPalindrome('hello'));

console.log(isPalindrome('madam'));

})();

```

```

// 3. 写一个函数对传入的字符串输出所有子字符串组合

(function(){

'use strict';

function substrings(str) {

if(typeof str !== 'string') {

throw "Pls input a string!";

}

var result = [];

function next(idx) {

var i, n = str.length - idx;

for(i=1; i<=n; i++) {

add(str.substr(idx, i));

}

if(idx < str.length){

next(idx+1);

}

}

function add(item) {

if(result.indexOf(item)<0) {

result.push(item);

}

}

next(0);

return result;

}

console.log(substrings('dog'));

})();

```

```

// 4. 写一个函数对传入的字符串重新按字母排序

(function(){

'use strict';

function reorderStr(str) {

if(typeof str !== 'string') {

throw "Pls input a string!";

}

return str.split('').sort().join('');

}

console.log(reorderStr('webmaster'));

})();

// 5. 写一个函数对传入的字符串中每个单词的首字母大写

(function(){

'use strict';

function upperWords(str) {

if(typeof str !== 'string') {

throw "Pls input a string!";

}

return str.split(' ').map(upperFirstLetter).join(' ');

function upperFirstLetter(str) {

return str.charAt(0).toUpperCase().concat(str.substr(1));

}

}

console.log(upperWords('the quick brown fox'));

})();

// 6. 写一个函数找出传入的字符串中最长的单词

(function(){

'use strict';

function findLongest(str) {

if(typeof str !== 'string') {

throw "Pls input a string!";

}

var items = str.split(' ');

return getMax(items);

function getMax(arr) {

var i, max = 0, n=arr.length;

for(i = 0; i < n; i++) {

if(arr[i].length > arr[max].length) {

max = i;

}

}

return arr[max];

}

}

console.log(findLongest('Web Development Tutorial'));

})();

```

——————————————————————————————————————————————————

# 数组去重

今天要聊的,也是我以前笔试时碰到过的一个问题,数组去重,不知道现在的笔试题还考不考这个?

数组去重,一般需求是给你一个数组,调用去重方法,返回数值副本,副本中没有重复元素。一般来说,两个元素通过 `===` 比较返回 true 的视为相同元素,需要去重,所以,`1` 和 `"1"` 是不同的元素,`1` 和 `new Number(1)` 是不同的元素,`{}` 和 `{}` 是不同的元素(引用不同)。(当然如果需求认为 `{}` 和 `{}` 算作相同的元素,那么解法就不一样了)

### 方法一

无需思考,我们可以得到 O(n^2) 复杂度的解法。定义一个变量数组 res 保存结果,遍历需要去重的数组,如果该元素已经存在在 res 中了,则说明是重复的元素,如果没有,则放入 res 中。

```

function unique(a) {

var res = [];

for (var i = 0, len = a.length; i < len; i++) {

var item = a[i];

for (var j = 0, jLen = res.length; j < jLen; j++) {

if (res[j] === item)

break;

}

if (j === jLen)

res.push(item);

}

return res;

}

var a = [1, 1, '1', '2', 1];

var ans = unique(a);

console.log(ans); // => [1, "1", "2"]

```

代码非常简单,那么是否能更简洁些?如果不考虑浏览器兼容,我们可以用 ES5 提供的 Array.prototype.indexOf 方法来简化代码。

```

function unique(a) {

var res = [];

for (var i = 0, len = a.length; i < len; i++) {

var item = a[i];

(res.indexOf(item) === -1) && res.push(item);

}

return res;

}

var a = [1, 1, '1', '2', 1];

var ans = unique(a);

console.log(ans); // => [1, "1", "2"]

```

既然用了 indexOf,那么不妨再加上 filter。

```

function unique(a) {

var res = a.filter(function(item, index, array) {

return array.indexOf(item) === index;

});

return res;

}

var a = [1, 1, '1', '2', 1];

var ans = unique(a);

console.log(ans); // => [1, "1", "2"]

```

### 方法二

法一是将原数组中的元素和结果数组中的元素一一比较,我们可以换个思路,将原数组中重复元素的最后一个元素放入结果数组中。

```

function unique(a) {

var res = [];

for (var i = 0, len = a.length; i < len; i++) {

for (var j = i + 1; j < len; j++) {

// 这一步十分巧妙

// 如果发现相同元素

// 则 i 自增进入下一个循环比较

if (a[i] === a[j])

j = ++i;

}

res.push(a[i]);

}

return res;

}

var a = [1, 1, '1', '2', 1];

var ans = unique(a);

console.log(ans); // => ["1", "2", 1]

```

虽然复杂度还是 O(n^2),但是可以看到结果不同,1 出现在了数组最后面,因为结果数组取的是元素最后一次出现的位置。

### 方法三(sort)

如果笔试面试时只答出了上面这样 O(n^2) 的方案,可能还不能使面试官满意,下面就来说几种进阶方案。

将数组用 sort 排序后,理论上相同的元素会被放在相邻的位置,那么比较前后位置的元素就可以了。

```

function unique(a) {

return a.concat().sort().filter(function(item, pos, ary) {

return !pos || item != ary[pos - 1];

});

}

var a = [1, 1, 3, 2, 1, 2, 4];

var ans = unique(a);

console.log(ans); // => [1, 2, 3, 4]

```

但是问题又来了,`1` 和 `"1"` 会被排在一起,不同的 Object 会被排在一起,因为它们 toString() 的结果相同,所以会出现这样的错误:

```

var a = [1, 1, 3, 2, 1, 2, 4, '1'];

var ans = unique(a);

console.log(ans); // => [1, 2, 3, 4]

```

当然你完全可以针对数组中可能出现的不同类型,来写这个比较函数。不过这似乎有点麻烦。

### 方法四 (object)

用 JavaScript 中的 Object 对象来当做哈希表,这也是几年前笔试时的解法,跟 sort 一样,可以去重完全由 Number 基本类型组成的数组。

```

function unique(a) {

var seen = {};

return a.filter(function(item) {

return seen.hasOwnProperty(item) ? false : (seen[item] = true);

});

}

var a = [1, 1, 3, 2, 1, 2, 4];

var ans = unique(a);

console.log(ans); // => [1, 3, 2, 4]

```

还是和方法三一样的问题,因为 Object 的 key 值都是 String 类型,所以对于 `1` 和 `"1"` 无法分别,我们可以稍微改进下,将类型也存入 key 中。

```

function unique(a) {

var ret = [];

var hash = {};

for (var i = 0, len = a.length; i < len; i++) {

var item = a[i];

var key = typeof(item) + item;

if (hash[key] !== 1) {

ret.push(item);

hash[key] = 1;

}

}

return ret;

}

var a = [1, 1, 3, 2, '4', 1, 2, 4, '1'];

var ans = unique(a);

console.log(ans); // => [1, 3, 2, "4", 4, "1"]

```

虽然解决了讨厌的 `1` 和 `"1"` 的问题,但是还有别的问题!

```

var a = [{name: "hanzichi"}, {age: 30}, new String(1), new Number(1)];

var ans = unique(a);

console.log(ans); // => [Object, String]

```

但是如果数组元素全部是基础类型的 Number 值,键值对法应该是最高效的!

### 方法五 (ES6)

ES6 部署了 Set 以及 Array.from 方法,太强大了!如果浏览器支持,完全可以这样:

```

function unique(a) {

return Array.from(new Set(a));

}

var a = [{name: "hanzichi"}, {age: 30}, new String(1), new Number(1)];

var ans = unique(a);

console.log(ans); // => [Object, Object, String, Number]

```

# _.unique

最后来看看 underscore 对此的实现方式,underscore 将此封装到了 _.unique 方法中,调用方式为 _.unique(array, [isSorted], [iteratee])。其中第一个参数是必须的,是需要去重的数组,第二个参数可选,如果数组有序,则可以传入布尔值 true,第三个参数可选,如果需要对数组迭代的结果去重,则可以传入一个迭代函数。而数组元素去重是基于 `===` 运算符的。

其实很简单,underscore 中的实现方式和上面的方法一相似。

我们来看它的核心代码:

```

for (var i = 0, length = getLength(array); i < length; i++) {

var value = array[i],

// 如果指定了迭代函数

// 则对数组每一个元素进行迭代

computed = iteratee ? iteratee(value, i, array) : value;

// 如果是有序数组,则当前元素只需跟上一个元素对比即可

// 用 seen 变量保存上一个元素

if (isSorted) {

// 如果 i === 0,则直接 push

// 否则比较当前元素是否和前一个元素相等

if (!i || seen !== computed) result.push(value);

// seen 保存当前元素,供下一次对比

seen = computed;

} else if (iteratee) {

// 如果 seen[] 中没有 computed 这个元素值

if (!_.contains(seen, computed)) {

seen.push(computed);

result.push(value);

}

} else if (!_.contains(result, value)) {

// 如果不用经过迭代函数计算,也就不用 seen[] 变量了

result.push(value);

}

}

```

外面的循环遍历数组元素,对于每个元素,如果数组有序,则和前一个元素比较,如果相同,则已经出现过,不加入到结果数组中,否则则加入。而如果有迭代函数,则计算传入迭代函数后的值,对值去重,调用 *.contains 方法,而该方法的核心就是调用 *.indexOf 方法,和我们上面说的方法一异曲同工。

——————————————————————————————————————————————————

#### 先谈谈作用域

> -- **什么是作用域**?

> 就是某个变量有(起)作用的范围;

> --**词法作用域和动态作用域**

> 词法作用域:在变量声明的时候,它的作用域就已经确定了;

> ​ 动态作用域:在程序运行的时候,由程序的当前上下文(执行环境)决定的;

> -- **js属于词法作用域**

> 词法作用域的访问规则:

> ​ 先在当前作用域中查找,如果找到就直接使用,如果没有找到,那么就到上一级作用域中查找,如果还没有找到那么就重复这个查找的过程,直到全局作用域

- js中的作用域

(01) script标签构成的全局作用域;

(02) 在js中,函数是唯一一个可以创建作用域的对象;

```

var a1 = "a1";

var b1 = "b1";

function func(){

var a1 = "n1";

console.log(a1); //n1(此行a1先在func函数内部作用域中查询;

console.log(b1); //b1

}

func();

```

#### 变量和函数的提升

**js的执行过程:**

预解析阶段 变量和函数的提升(声明);

具体的执行阶段

**变量和函数的提升:**

js代码是一个从上至下逐步解析的过程,在这个过程中之前会把所有的的变量和函数提前申明.

```

console.log(a); //undefined 而不是报错

var a = 10;

f1(); //f1而不是报错

function f1(){

console.log("f1");

}

```

上面一段js代码,会先把var a与 func函数提前申明,所以代码实际可用模拟成下面这段代码:

```

var a; //变量a提前申明,但未定义

function f1(){//函数提前申明

console.log("f1");

}

console.log(a); //申明未定义,结果为undefined

a = 10;

f1(); //结果为f1

```

经过上面的解析,结果便可以理解了.

**具体会出现的一些问题和几种情况:**

①变量和变量同名的情况:后面的变量会把前面的变量覆盖;

```

var n1 = "n1";

console.log(n1); //n1

function test(){

console.log(n1);

}

test(); //n1

var n1 = "new n1"; //覆盖了之前n1的值

console.log(n1); //new n1

test(); //new n1

```

②函数和函数同名的情况,后面的函数会覆盖前面的函数.

```

f1(); // 20

function f1(){

console.log(10);

}

f1(); //20

function f1(){

console.log(20);

}

f1();//20

```

③函数和变量同名,可以理解为函数声明提升而变量的声明不提升(实际上变量也提升了,但是会被函数覆盖).

```

console.log(demo); // demo函数

var demo = "我是字符串";

function demo(){

console.log("我是函数");

}

console.log(demo); //我是字符串

```

④变量提升是分作用域的

```

var num=5;

function test1(){

console.log(num);//undefined

var num=10;//此处函数test1作用域中有申明变量num,会提升申明

}

test();

```

⑤如果函数是函数表达式定义,那么在做函数声明提升的时候,仅仅只会把var 变量的名称(函数)提升到当前作用域中.

```

console.log(func); //undefined

var func = function(){//此处只提升申明var func;所以为undefined;

console.log("func");

}

var func = "我是MT";

console.log(func); //'我是MT'

```

一键复制

编辑

Web IDE

原始数据

按行查看

历史

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值