「JavaScript灵魂之问」前端知识梳理之 JS 篇(下篇)

三元运算基础

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

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

猜你喜欢

转载自blog.csdn.net/weixin_42429718/article/details/108657570
今日推荐