三元运算基础

开门见山,三元运算,我想对于很多编程语言都有提到,下面就简单一个例子来讲解一下好了。

var str = 89 > 9? ('89' > '9'? '通过了': '内层未通过') : '外层未通过';
console.log(str);

答案是 内层未通过,注意 '89' > '9'的比较,由于都是字符串,会从第一位以 ASCII码来进行比较。由于89第一位为8,于是小于 9,返回 false,走后面那个,最后打印了 内层未通过

浅拷贝

var person1 = {
name: 'Chocolate',
age: 21,
child: {
car: ['Benz', 'Mazda'],
first: {
name: 'cc',
age: 10
},
second: {
name: 'dd',
age: 11
}
}
}

var person2 = clone(person1);
person2.child.car.push('BYD');
console.log(person2);

console.log(person1);

/* 浅拷贝 */
function clone(origin, target) {
var tar = target || {};
for (var key in origin) {
if (origin.hasOwnProperty(key)) {
tar[key] = origin[key];
}
}
return tar;
}

答案如下:

从这个例子,我们发现,浅拷贝没办法拷贝引用地址,会污染原对象,深拷贝就不会出现这个问题。

深拷贝

接下来,我们来探究一下深拷贝。

var person1 = {
name: 'Chocolate',
age: 21,
child: {
car: ['Benz', 'Mazda'],
first: {
name: 'cc',
age: 10
},
second: {
name: 'dd',
age: 11
}
}
}

var person2 = deepClone(person1);
person2.child.car.push('BYD');
console.log(person2);

console.log(person1);

function deepClone(origin, target) {
var tar = target || {};
for(var key in origin) {
if (origin.hasOwnProperty(key)) {
if (typeof (origin[key]) == 'object' && origin[key] !== null) {
if (Object.prototype.toString.call(origin[key]).slice(8, -1) == 'Array') {
tar[key] = [];
} else {
tar[key] = {};
}
deepClone(origin[key], tar[key]);
} else {
tar[key] = origin[key];
}
}
}
return tar;
}

答案如下:


另外,还有一种关于 JSON的方法来克隆,但一般用的不是特别多,因为对于函数方面没办法进行拷贝。

var person3 = JSON.parse(JSON.stringify(person1));
console.log(person3);

真题演练

第一题

function test(){
console.log(foo);
var foo = 2;
console.log(foo);
console.log(a);
}
test();

答案是 undefined 2 error,不解释了哈。

第二题

function a(){
var test;
test();
function test(){
console.log(1);
}
}
a();

答案 1,简单哈。

第三题

前面两道题都是简单热个身,下面我们来一道阿里笔试题,应该对于 this指向最难的题了。

var name = '222';
var a = {
name: '111',
say: function(){
console.log(this.name);
}
}
var fun = a.say;
fun(); //
a.say(); //
var b = {
name: '333',
say: function(fun){
fun();
}
}
b.say(a.say); //
b.say = a.say;
b.say(); //

答案是 222 111 222 333,详细解释一下,对于第一句var fun = a.say;,也就相当于把匿名函数赋值给了 fun,详细如下:

var fun = function(){
console.log(this.name);
}

因此,这里 this,就会指向 window,所以就是 222

那么,第二个答案 a.say();必须得是 111,因为我们访问的是对象里面的方法,this当然指向这个对象。

接下来,对于第三个答案 b.say(a.say);,相当于在GO中执行该函数,这里其实和第一问类型,都是放在 GO里面执行,因此也是指向 window

对于最后一个答案b.say();,和第二个答案类似,执行对象里面的方法,this当然指向这个对象。

第四题

继续,再来一道关于 this指向问题的题目:

function test(){
var marty = {
name: 'marty',
printName: function(){
console.log(this.name)
}
}
var test1 = {
name: 'test1'
}
var test2 = {
name: 'test2'
}
var test3 = {
name: 'test3'
}
test3.printName = marty.printName;
marty.printName.call(test1); //
marty.printName.apply(test2); //
marty.printName(); //
test3.printName(); //
}
test();

答案是 test1 test2 marty test3,对于第一个和第二个答案,类似的,通过 call / apply改变了 this 指向问题,对于第三个答案,执行对象里面的方法,this当然指向这个对象。而对于第四个答案,也是执行对象里面的方法,this当然指向这个对象。

第五题

下面这题是百度一道真题,但是感觉比较水,直接看看会打印什么吧:

var bar = {
a: '1'
}
function test(){
bar.a = 'a';
Object.prototype.b = 'b';
return function inner(){
console.log(bar.a);
console.log(bar.b);
}
}
test()(); //

答案是 a b,函数里面 return出去了一个函数,形成了一个闭包,会一直引用着 testAO,所以可以访问 a,而 b访问不到,会去原型链上一直找,最后找到了 b

经典题

下面来一道最经典的题,特别容易出错,注意!

function Foo(){
getName = function(){
console.log(1);
}
return this;
}
Foo.getName = function(){
console.log(2);
}
Foo.prototype.getName = function(){
console.log(3);
}
var getName = function(){
console.log(4);
}
function getName(){
console.log(5);
}

Foo.getName(); //
getName(); //
Foo().getName(); //
getName(); //
new Foo.getName(); //
new Foo().getName(); //
new new Foo().getName(); //

答案是 2 4 1 1 2 3 3。详细解释一下:

第一问,执行Foo.getName();我们执行的是这段代码:

Foo.getName = function(){
console.log(2);
}

因为,这里访问的对象的属性,把函数充当了一个特殊对象,我们直接访问它的属性,然后打印 2

第二问,执行 getName();在全局GO里面,是这样的情况,存在函数提升

GO: {
getName: function getName(){ console.log(5);} -> function(){console.log(4);}
}

第三问,执行 Foo().getName();时,里面有一个变量 getName,没有申明,放到外面,就会给到全局,此时

GO: {
getName: function getName(){ console.log(5);} -> function(){console.log(4);} -> function(){console.log(1);}
}

于是访问 getName又进行了覆盖,打印 1

第四问,执行 getName(); GO和上一问一样,打印 1

第五问,执行 new Foo.getName();这里要牵扯到优先级问题了,. 运算符的优先级要比 new要高。因此,先执行Foo.getName();(即先执行下面这段代码)

Foo.getName = function(){
console.log(2);
}

先打印 2,然后new没有什么意义,等于没写。

第六问,执行new Foo().getName();这里又要牵扯到优先级问题了,()运算符的优先级要比 .高。而在执行()时会带着 new一起执行。然后返回 this,此时我们执行的是 this.getName,发现此时没有 this.getName,然后就会去原型链上找,能找到如下代码:

Foo.prototype.getName = function(){
console.log(3);
}

于是最终结果打印 3

第七问,执行 new new Foo().getName();,也是与优先级有关,首先,()运算符的优先级要比 .高,先得到 new this.getName(); 然后. 运算符的优先级要比 new要高,和第六问一样了,访问原型链上的方法,得到 3,最后new没有什么意义,等于没写。

数组

先来一些前置知识,后续我们会把数组相关知识好好整理一番。

创建数组

/* 创建数组 */
var arr1 = []; // 通过字面量创建
var arr2 = new Array(); // 通过系统内置的Array构造函数

所有的数组都继承于 Array.prototype

看看下面这个,会输出什么呢?

var arr = [, ,];
console.log(arr);

答案是 [ <2 empty items> ],诶,我明明有三个空的,为啥是两个 empty呢?此时,我们打印一下长度看看,发现还是 2

console.log(arr.length); // 2

好了,不布置坑了,直接看下面这例子吧:

var arr = [, 1,3,5,7,];
console.log(arr); // [ <1 empty item>, 1, 3, 5, 7 ]
console.log(arr.length); // 5

从结果看出来,发现数组内置会有一个截取,最后一个空,它不会算。(注意,只是最后为空的元素不会算)这就叫做稀疏数组

那么,我们可以直接造100个为空的数组吗?而不是一个个赋值为空,当然可以,用构造函数即可:

var arr = new Array(100);
console.log(arr) // [ <100 empty items> ]

在原数组上修改

push / unshift

看看会输出什么?

var arr = [1,2,3,4];
console.log(arr.push(5));

答案是 5,诶,你会不会一位会打印数组,当然不是哈, push / unshift返回值是执行方法以后数组的长度

举个例子:

var arr = [1,2,3,4];
console.log(arr.push(5));
console.log(arr.unshift(0));
console.log(arr);

答案如下:

5
6
[ 0, 1, 2, 3, 4, 5 ]

重写 push 方法

Array.prototype.myPush = function(){
for(var i=0;i<arguments.length;i++){
this[this.length] = arguments[i];
}
return this.length;
}
var arr = [1,2,3,4];
console.log(arr.myPush(5)); // 5
console.log(arr); // [ 1, 2, 3, 4, 5 ]

pop / shift

它们没有参数,返回值为弹出去的 value

var arr = [1,2,3,4];
var tmp = arr.pop();
console.log(tmp); // 4
var tmp2 = arr.shift();
console.log(tmp2); // 1

reverse

var arr = [1,2,3];
arr.reverse();
console.log(arr); // [ 3, 2, 1 ]

splice

// arr.splice(开始项的下标,剪切长度,剪切以后最后一位开始添加数据)
var arr = ['a', 'b', 'c'];
arr.splice(1,2);
console.log(arr); // ['a']
arr.splice(1,0,2,3);
console.log(arr) // [ 'a', 2, 3 ]

sort()

var arr = [-1,-5,8,0,2];
console.log(arr.sort()); // [ -1, -5, 0, 2, 8 ]
var arr = ['b','z','h','i','a'];
console.log(arr.sort()); // [ 'a', 'b', 'h', 'i', 'z' ]

好的,在你以为按照升序排列后,我们再来看看下面又会打印什么?

var arr = [27,49,5,7];
console.log(arr.sort());

答案是 [ 27, 49, 5, 7 ],发现奇怪的事情,居然不排序了???现在来好好解释一下,原来数组是按照 ASCII码来排序的。

那么,为了自定义排序, sort里面可以传一个回调函数进来。看看下面例子吧:

/* 
sort 按照ascii码来进行排序
1、参数a,b
2、返回值:负值,a 就排在前面
正值,b 就排在前面
0, 保持不动
*/
var arr = [27, 49, 5, 7];
arr.sort((a,b)=>a-b);
console.log(arr); // [ 5, 7, 27, 49 ]
arr.sort((a,b)=>b-a);
console.log(arr); // [ 49, 27, 7, 5 ]

可以以冒泡排序为例,当 a-b返回值为正数的时候,就将 a冒泡上去,然后就是从小到大排序啦。

总结归纳:

修改原数组的方法:

push/unshift pop/shift reverse splice sort

新建数组(不对原数组产生影响)

concat

var arr1 = ['a', 'b', 'c'];
var arr2 = ['d', 'e', 'f'];
var arr3 = arr1.concat(arr2);
console.log(arr3); // [ 'a', 'b', 'c', 'd', 'e', 'f' ]

toString()

var arr = [1, 2, 3];
var arr1 = arr.toString();
console.log(arr); // [ 1, 2, 3 ]
console.log(arr1); // 1,2,3

slice

数组切割,有两个可选参数,第一个参数为 start,第二个参数为 end,区间为 [ start, end)

var arr = ['a', 'b', 'c', 'd', 'e', 'f'];
var arr1 = arr.slice(1);
console.log(arr1); // [ 'b', 'c', 'd', 'e', 'f' ]

join

var arr = ['a','b','c','d'];
var str1 = arr.join();
var str2 = arr.toString();
var str3 = arr.join('');
console.log(str1); // a,b,c,d
console.log(str2); // a,b,c,d
console.log(str3); // abcd

split

var arr = ['a', 'b', 'c', 'd'];
var str1 = arr.join('-');
console.log(str1); // a-b-c-d
var arr1 = str1.split('-')
console.log(arr1); // [ 'a', 'b', 'c', 'd' ]
var arr2 = str1.split('-', 3)
console.log(arr2); // [ 'a', 'b', 'c' ]

类数组

开门见山,看看下面代码会打印什么?

function test(){
console.log(arguments);
}
test(1,2,3);

结果如下,发现会是一个数组形式,但里面有很多东西,但是没有数组方法,因为它并没有继承 Array.prototype

接下来,我们再来看看下面这个例子:

function test(){
console.log(arguments);
}
test(1,2,3);

var obj = {
'0': 1,
'1': 2,
'2': 3,
'length': 3
}
console.log(obj);

结果如下,发现一个是系统自带的,一个是由我们来生成的,是不一样的!

另外,我们发现obj也不是数组的形式,那么我们怎么变成数组的形式呢?看看如下操作吧:

var obj = {
'0': 1,
'1': 2,
'2': 3,
'length': 3,
'splice': Array.prototype.splice
}
console.log(obj);

在我们继承数组的 splice的方法后,居然真变成了数组的形式,那么我们可以使用 push方法吗?

试一试:

var obj = {
'0': 1,
'1': 2,
'2': 3,
'length': 3,
'splice': Array.prototype.splice,
'push': Array.prototype.push
}
obj.push(7);
console.log(obj);

结果如下,继承数组的push方法后,真的可以!

既然刚刚是直接对象来继承数组的方法,那么可以直接挂载到 Object上吗?看如下例子:

var obj = {
'0': 1,
'1': 2,
'2': 3,
'length': 3,
}
Object.prototype.push = Array.prototype.push;
Object.prototype.splice = Array.prototype.splice;
obj.push(7);
console.log(obj);

答案是可以的,因为大部分对象都继承于Object嘛。

真题演练

看看下面代码会输出什么,一道经典的笔试题:

var obj = {
'2': 3,
'3': 4,
'length': 2,
'splice': Array.prototype.splice,
'push': Array.prototype.push
}
obj.push(1);
obj.push(2);
console.log(obj);

答案如下,来解释一下

其实内部是这样做的:

Array.prototype.push = function(elem){
this[this.length] = elem;
this.length++;
}

于是我们执行的时候会这样执行,每次找数组长度处进行赋值。

obj[2] = 1;
obj[3] = 2;

最后,我们的 length 加到 4,而前面两个补空

下面我们再来看看一个例题吧,加深巩固。

var person = {
'0': '张小一',
'1': '张小二',
'2': '张小三',
'name': '张三',
'age': 32,
'height': 140,
'weight': 180,
'length': 3
}
Object.prototype.push = Array.prototype.push;
Object.prototype.splice = Array.prototype.splice;

console.log(person[1]); // 张小二
console.log(person.weight);// 180

类数组转换成数组

function test(){
console.log(Array.prototype.slice.call(arguments)); // [ 1, 2, 3 ]
}
test(1,2,3);

结果如下:

有了转换数组操作后,我们又可以来封装一下 unshift方法了。

var arr = ['d', 'e', 'f']
Array.prototype.myUnshift = function () {
let argArr = Array.prototype.slice.call(arguments);
let newArr = argArr.concat(this);
return newArr;
}
let newArr = arr.myUnshift('a', 'b', 'c');
console.log(newArr);

答案是 [ 'a', 'b', 'c', 'd', 'e', 'f' ]

数组按照元素的字节数排序

unicode0-2551个字节,256 - ~2 个字节。

/* 数组按照元素的字节数排序 */
let arr = ['Chocolate','我爱你','OK','杰伦'];
arr.sort((a,b)=>getBytes(a)-getBytes(b));
console.log(arr); // [ 'OK', '杰伦', '我爱你', 'Chocolate' ]
function getBytes(str) {
let bytes = str.length;
for (let i = 0; i < str.length; i++) {
if (str.charCodeAt(i) > 255) {
bytes++;
}
}
return bytes;
}

封装 typeof

/* 封装 typeof */
// 返回值 number string boolean object function
// undefined
function myTypeof(val){
let type = typeof(val);
let toStr = Object.prototype.toString;
if(val === null){
return 'null';
}else if(type === 'object'){
let res = toStr.call(val);
res = res.slice(8,-1);
res = res[0].toLowerCase() + res.substr(1);
return res;
}else{
return type;
}
}
console.log(myTypeof(1)); // number
console.log(myTypeof({name: 'chocolate'})); // object
console.log(myTypeof([])); // array
console.log(myTypeof(new Number(1))); // number
console.log(myTypeof(new String(1))); // string
console.log(myTypeof(new Boolean(1))); // boolean
console.log(myTypeof(null)); // null
console.log(myTypeof(undefined)); // undefined
console.log(myTypeof(function(){})); // function

数组去重

/* 数组去重 */
let arr = [0,0,0,1,1,1,2,3,3,3,'a','b','a'];

Array.prototype.unique = function(){
let tmp = {};
let newArr = [];
for(let i=0;i<this.length;i++){
if(!tmp.hasOwnProperty(this[i])){
tmp[this[i]] = this[i];
newArr.push(this[i]);
}
}
return newArr;
}
console.log(arr.unique()); // [ 0, 1, 2, 3, 'a', 'b' ]

闭包回顾

第一题

function Test(a, b, c) {
var d = 0;
this.a = a;
this.b = b;
this.c = c;
function e() {
d++;
console.log(d);
}
this.f = e;
}
var test1 = new Test();
test1.f();
test1.f();
var test2 = new Test();
test2.f();

答案是 1 2 1,解释最后一个为什么还是 1,因为后面又实例化了一个新的对象,和之前的对象地址当然不是一个地方了,d的初始值都是 0

第二题

看看下面代码会输出什么?

function test(){
console.log(typeof(arguments));
}
test(); //

答案是 object,因为 arguments是类数组(类似于数组的对象,即用对象模拟的数组)

第三题

var test = function a(){
return 'a';
}
console.log(typeof(a)); //

答案是 undefined,函数表达式是忽略函数名的,等于 a 根本没有。相当于 a 没有申明,如果直接打印会直接报错,但是 typeof的话会打印 undefined