三元运算基础
开门见山,三元运算,我想对于很多编程语言都有提到,下面就简单一个例子来讲解一下好了。
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
出去了一个函数,形成了一个闭包,会一直引用着 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构造函数
所有的数组都继承于 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' ]
数组按照元素的字节数排序
unicode
中 0-255
为 1
个字节,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
。