三元运算基础
开门见山,三元运算,我想对于很多编程语言都有提到,下面就简单一个例子来讲解一下好了。
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();
|
第三题
前面两道题都是简单热个身,下面我们来一道阿里笔试题,应该对于 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
出去了一个函数,形成了一个闭包,会一直引用着 test
的 AO
,所以可以访问 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.prototype
。
看看下面这个,会输出什么呢?
var arr = [, ,]; console.log(arr);
|
答案是 [ <2 empty items> ]
,诶,我明明有三个空的,为啥是两个 empty
呢?此时,我们打印一下长度看看,发现还是 2
。
好了,不布置坑了,直接看下面这例子吧:
var arr = [, 1,3,5,7,]; console.log(arr); console.log(arr.length);
|
从结果看出来,发现数组内置会有一个截取,最后一个空,它不会算。(注意,只是最后为空的元素不会算)这就叫做稀疏数组
那么,我们可以直接造100
个为空的数组吗?而不是一个个赋值为空,当然可以,用构造函数即可:
var arr = new Array(100); console.log(arr)
|
在原数组上修改
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);
|
重写 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)); console.log(arr);
|
pop / shift
它们没有参数,返回值为弹出去的 value
值
var arr = [1,2,3,4]; var tmp = arr.pop(); console.log(tmp); var tmp2 = arr.shift(); console.log(tmp2);
|
reverse
var arr = [1,2,3]; arr.reverse(); console.log(arr);
|
splice
var arr = ['a', 'b', 'c']; arr.splice(1,2); console.log(arr); arr.splice(1,0,2,3); console.log(arr)
|
sort()
var arr = [-1,-5,8,0,2]; console.log(arr.sort());
|
var arr = ['b','z','h','i','a']; console.log(arr.sort());
|
好的,在你以为按照升序排列后,我们再来看看下面又会打印什么?
var arr = [27,49,5,7]; console.log(arr.sort());
|
答案是 [ 27, 49, 5, 7 ]
,发现奇怪的事情,居然不排序了???现在来好好解释一下,原来数组是按照 ASCII
码来排序的。
那么,为了自定义排序, sort
里面可以传一个回调函数进来。看看下面例子吧:
var arr = [27, 49, 5, 7]; arr.sort((a,b)=>a-b); console.log(arr); arr.sort((a,b)=>b-a); console.log(arr);
|
可以以冒泡排序为例,当 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);
|
toString()
var arr = [1, 2, 3]; var arr1 = arr.toString(); console.log(arr); console.log(arr1);
|
slice
数组切割,有两个可选参数,第一个参数为 start
,第二个参数为 end
,区间为 [ start, end)
var arr = ['a', 'b', 'c', 'd', 'e', 'f']; var arr1 = arr.slice(1); console.log(arr1);
|
join
var arr = ['a','b','c','d']; var str1 = arr.join(); var str2 = arr.toString(); var str3 = arr.join(''); console.log(str1); console.log(str2); console.log(str3);
|
split
var arr = ['a', 'b', 'c', 'd']; var str1 = arr.join('-'); console.log(str1); var arr1 = str1.split('-') console.log(arr1); var arr2 = str1.split('-', 3) console.log(arr2);
|
类数组
开门见山,看看下面代码会打印什么?
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++; }
|
于是我们执行的时候会这样执行,每次找数组长度处进行赋值。
最后,我们的 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);
|
类数组转换成数组
function test(){ console.log(Array.prototype.slice.call(arguments)); } 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' ]
数组按照元素的字节数排序
unicode
中 0-255
为 1
个字节,256 - ~
为 2
个字节。
let arr = ['Chocolate','我爱你','OK','杰伦']; arr.sort((a,b)=>getBytes(a)-getBytes(b)); console.log(arr); function getBytes(str) { let bytes = str.length; for (let i = 0; i < str.length; i++) { if (str.charCodeAt(i) > 255) { bytes++; } } return bytes; }
|
封装 typeof
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)); console.log(myTypeof({name: 'chocolate'})); console.log(myTypeof([])); console.log(myTypeof(new Number(1))); console.log(myTypeof(new String(1))); console.log(myTypeof(new Boolean(1))); console.log(myTypeof(null)); console.log(myTypeof(undefined)); console.log(myTypeof(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());
|
闭包回顾
第一题
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
。