2020前端面试(九)- javascript数据相关

点这里,欢迎关注

1.js数据类型:

https://blog.csdn.net/lareinalove/article/details/79895760

基本数据类型引用数据类型
有哪些string、number、boolean、undefined、null、symbolObject、Array、Function、RegExp、Date、特殊的基本包装类型(String、Number、Boolean)以及单体内置对象(Global、Math)。
访问方式按值访问的变量指向的是数据的地址
内存分配存储在栈中,栈里面直接开辟一个空间存储变量的值。堆中存放数据,栈中存放这个数据的地址
值的可变性基本数据类型的值是不可变的(在字符串的不可变性上有充分体现)。不可以添加属性和方法。引用数据类型可以添加属性和方法
赋值var a=3;var b=a;基本数据类型的赋值会开辟一块新的空间,然后改变变量的指针指向var obj={};var obj2=obj;仅改变引用的指针,指向的是同一个对象
调用函数时传递的参数因为传递的是‘值’,所以在进入函数内部时,会开辟一个新的变量和空间来存储这个‘值’。此时修改值,由于是不同的内存,所以不会影响原来的实参。因为传递的是‘地址,所以在进入函数内部时,会开辟一个新的变量和空间来存储这个‘地址’。由于地址相同,即指向的是堆里面同样的数据,所以修改的时候会影响之前的实参。

js中有三种原始类型的值:string,number,boolean。

通过包装类型,即三个原生对象Number、String、Boolean可以将这三种原始类型的值转换为包装对象。

这样就可以使对象这种类型覆盖js中所有的值。

当这三个原生对象作为构造函数使用时,可以将原始类型的值转为对象。

这三个原生对象作为普通函数使用时,可以将任意类型的值转换为原始类型的值。

2.关于NaN:

全称为not a number,但是!!!!typeof NaN 的值为number

NaN 是一个全局对象的属性,NaN 属性的初始值就是 NaN。

NaN与一切为敌(包括自己),与之相比都是false。

isNaN()Number.isNaN() 的区别:

isNaN('hello world');        // true   会先将内部的字符串转换为number
Number.isNaN('hello world'); // false	不存在转换,直接比较

3.深浅拷贝

(1)可枚举与不可枚举:

可枚举和不可枚举,是由属性的enumerable值决定的。取值为true,表示可枚举。否则表示不可枚举。

for…in只能遍历出可枚举的属性。

Object.keys返回的也是可枚举的属性。

var obj = {
    name: "xiaoming",
    age: 12,
    sex: "男",
};
Object.defineProperty(obj, "color1", {
    value: "red",
    writable: true,
    enumerable: false,
    configurable: false,
});
Object.defineProperty(obj, "color2", {
    value: "green",
    writable: true, //是否可以重写,默认值为false
    enumerable: true, //是否可以通过Object.keys()遍历,默认为false
    configurable: false //是否可以删除该属性以及修改enumerable和configurable,默认为false
});
console.log(Object.keys(obj)); //["name", "age", "sex", "color2"]
// 通过Object.keys可以获取一个对象含有的可枚举属性的个数
console.log(Object.keys(obj).length); //4
for (k in obj) {
    console.log(k); //name age sex color2
}
//   判断一个属性是否是可枚举的
console.log(obj.propertyIsEnumerable("color1")); //false
console.log(obj.propertyIsEnumerable("length")); //false
console.log(obj.propertyIsEnumerable("color2")); //true

var arr = ["apple", "banana", "orange"];
// Object.keys只返回可枚举的属性,Object.getOwnPropertyNames还会返回不可枚举的属性
console.log(Object.keys(arr).length); //3
console.log(Object.getOwnPropertyNames(arr)); //["0", "1", "2", "length"]
console.log(Object.getOwnPropertyNames(obj)); //["name", "age", "sex", "color1", "color2"]
console.log(Object.getOwnPropertyNames(arr).length); //4

(2)赋值,浅拷贝,深拷贝:

var obj1 = {
    'name' : 'zhangsan',
    'age' :  '18',
    'language' : [1,[2,3],[4,5]],
};

//赋值:相当于地址的直接引用
var obj2 = obj1;

//浅拷贝:浅拷贝只会拷贝最外面的一层,里面的子对象拷贝的是地址,即引用,修改时会连着修改
var obj3 = shallowCopy(obj1);
function shallowCopy(src) {
    var dst = {};
    for (var prop in src) {
        if (src.hasOwnProperty(prop)) {
            dst[prop] = src[prop];
        }
    }
    return dst;
}

//深拷贝:每一层的数据都会被拷贝,开辟的都是新的空间。

(3)浅拷贝的实现(4种):

1.传统方式:使用for…in遍历对象的最外层

var shallowCopy = function(obj) {
    // 只拷贝对象
    if (typeof obj !== 'object') return;
    // 根据obj的类型判断是新建一个数组还是对象
    var newObj = obj instanceof Array ? [] : {};
    // 遍历obj,并且判断是obj的属性才拷贝
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = obj[key];
        }
    }
    return newObj;
}

2.Object.assign()

ES6中的Object.assign()用于将源对象的属性复制到目标对象,该方法使用的是浅拷贝的方式。

(该方法针对的是所有对象,包括了数组)

//对象的浅拷贝
var obj1 = {
    name: "tim",
    msg: {
        age: 29,
    },
};
var obj4=Object.assign({}, obj1);
obj4.msg.age = 30;
obj4.name = "alice";
console.log(obj1); //tim 30

//数组的浅拷贝
let arr1 = [1, 2, { name: "liu" }];
let arr2 = Object.assign([], arr1);
console.log(arr2); //[1, 2,  {name: "liu"}]
arr2[2].name = "xiao";
console.log(arr1); [1, 2,  {name: "xiao"}]

扩展:Object.assign(obj1,obj2)//obj2的属性会覆盖掉obj1中重复的属性

3.利用数组的silce,concat方法

对于数组的浅拷贝,还可以通过数组的slice和concat方法返回一个新的数组的方式来实现,这些方法内部实现的是浅拷贝。

let arr3 = [1, 2, { name: "liu", likes: ["music", "movie"] }];
let arr4 = arr3.slice(0);
console.log(arr4); //[1, 2, { name: "liu", likes: ["music", "movie"] }]
arr4[2].name = "xiao";
console.log(arr3); //[1, 2, { name: "xiao", likes: ["music", "movie"] }]

let arr5 = [1, 2, { name: "liu", likes: ["music", "movie"] }];
let arr6 = arr5.concat([]);
console.log(arr6);
arr6[2].likes[0] = "drink"; //[1, 2, { name: "liu", likes: ["music", "movie"] }]
console.log(arr5); //[1, 2, { name: "liu", likes: ["drink", "movie"] }]

4.数组的浅拷贝还可以通过…展开符来实现

let arr3 = [1, 2, { name: "title" }];
let arr4 = new Array(...arr3);
arr4[0] = "aaa";
arr4[2].name = "xiao";
console.log(arr3); //[1, 2, { name: "xiao" }]

(4)深拷贝的实现(2种):

1.递归实现深度拷贝:(只能实现对象和数组的克隆)

// 深拷贝:每一层的数据都会被拷贝,开辟的都是新的空间。
var obj = {
    name: "tim",
    color: ["white", "black"],
    size: {
        content: "small",
    },
};
// 以下为通过函数递归的方式实现深拷贝
function deepCopy(newObj, oldObj) {
    for (k in oldObj) {
        // 判断是不是数组
        if (oldObj[k] instanceof Array) {
            newObj[k] = [];
            deepCopy(newObj[k], oldObj[k]);
            // 判断是不是对象
            // ❤ 注意:需要先判断是不是数组,再判断是不是对象,因为数组也属于对象
        } else if (oldObj[k] instanceof Object) {
            newObj[k] = {};
            deepCopy(newObj[k], oldObj[k]);
        } else {
            newObj[k] = oldObj[k];
        }
    }
}
var obj2 = {};
deepCopy(obj2, obj);
console.log(obj2);
obj2.color[0] = "yellow"; //深拷贝,不会改变原来的值
console.log(obj.color); //["white", "black"]



//简写方式
var deepCopy = function(obj) {
    if (typeof obj !== 'object') return;
    var newObj = obj instanceof Array ? [] : {};
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            //如果属性是对象,则需要进一步递归
            newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
        }
    }
    return newObj;
}

2.借助JSON对象的转换:

原理:JOSN对象中的stringify可以把一个js对象序列化为一个JSON字符串,parse可以把JSON字符串反序列化为一个js对象,通过这两个方法,也可以实现对象的深拷贝。

//对象的深拷贝
let obj1 = { a: 0 , b: { c: 0}}; 
let obj3 = JSON.parse(JSON.stringify(obj1)); 
obj3.b.c = 4; 
console.log(obj1);//{ a: 0 , b: { c: 0}} 未改变

//数组的深拷贝
var arr = ['old', 1, true, ['old1', 'old2'], {old: 1}]
var arr2 = JSON.parse( JSON.stringify(arr) );
arr2[3][0]='new1';
console.log(arr);// ['old', 1, true, ['old1', 'old2'], {old: 1}] 未改变

注意:该方法不能拷贝函数对象,还会有很多小问题。

3.包装对象Number,String,Boolean,以及正则对象RegExp和Date对象的克隆

其实谈不上克隆一说,包装对象直接通过=赋值,正则对象和Date对象直接在原基础上通过new创建新的对象

4.比较判断相关

(1)隐式转换:

https://blog.csdn.net/itcast_cn/article/details/82887895

字符串连接符+的隐式转换:

当+两侧有一个是字符串时,表明这个+是字符串连接符。此时会将其他数据类型调用toString()转换为字符串再拼接。

console.log(1+"true")//1true

算术运算符+的隐式转换:

当+两侧没有字符串时,表明这个+号是算术运算符。此时会把其他数据类型调用Number()转成数字后再做加法运算。

console.log(1+true)//会转换为1+1=2

console.log(1+undefined)//转换为1+Number(undefined)=1+NaN =NaN

console.log(1+null)//1+0=1

关系运算符> < ==的隐式转换:

当关系运算符两边只有一边是字符串时,该字符串会通过Number()转换为数字再进行比较

console.log("2">10)//false

当关系运算符两边都是字符串时,会按照字符串从左到右转换为unicode编码依次进行比较。

console.log("2">"10")//"2".charCodeAt()>"10".charCodeAt()   50>49  true

"2".charCodeAt()//50
"10".charCodeAt()//49

特殊情况:(无规则)

console.log(undefined==undefined);//true
console.log(undefined==null);//true
console.log(null==null);//true
console.log(NaN==NaN);//false  NaN不与任何数据相等,包括自己

复杂数据类型的隐式转换:

转换规则:对于对象和数组的隐式转换,会先通过valueOf()获取原始值,然后通过toString()转换为字符串。然后再通过Number()转换为数字。

注意:[].toString()为空字符串,{}.toString()为[object Object]

console.log([1,2]=='1,2')//true
//[1,2].valueOf().toString()='1,2'

let a={};
console.log(a=="[object Object]")//true
//{}.valueOf().toString()等于[object Object]

应用:if(a1&&a2&&a==3){
console.log(1)
}

如何完善a,使其正确打印1let a=??;
if(a==1&&a==2&&a==3){
    console.log(1)
}


解决方法:重写a属性的valueOf()方法
a={
    i:0;
    valueOf:function(){
        return ++a.i;//每调用一次,就让a对象的i属性自增一次并返回。
    }
}
if(a==1&&a==2&&a==3){//每次运算都会调用一次a的valueOf方法
    console.log(1)
}

带有逻辑非运算符的比较(超级大坑!!!!):

将其他数据类型通过Boolean()转换为布尔类型。

除了0,-0,NaN,undefined,null,空字符串,false,document.all()这八种情况外,其他都会被转换为true。

//注意:只有带有!才会转换为布尔型
console.log([]==0)//true  这里是通过[].valueOf().toString()先转换为空字符串,然后空字符串转换为数字0来比较的
console.log(![]==0)//true

console.log({}==!{})//false
//这里设置两种转换,前者通过{}.valueOf().toString()转换为'[object Object]',然后转换为1。后者直接可以转换为false,然后转换为0
console.log([]==![])//true
//同理,前者转换为空字符串,然后转换为数字0.后者转换为false,然后是数字0;


console.log({}=={})//false 注意:这里是引用数据类型,所以栈中的地址是不同的。
console.log([]==[])

(2)不同数据类型的值的比较,是怎么转换的,有什么规则:

隐式转换!!

(3)unll与undefined:

null表示空对象,undefined表示变量定义了但没赋值。

为什么null == undefined:

ECMAScript规范中是这样定义的…

(4)==和==、以及Object.is的区别

== 比较的是值,会自动进行隐式转换。

===不存在隐式转换,会先进行类型的比较,再比较值。

Object.is()是ES6中新增的语法,与严格比较运算符(===)基本一致。但具有一些特殊案例,这些案例在更准确,更符合实际开发使用:

//最主要的特点:
console.log(NaN==NaN);//false
Object.is(NaN,NaN);//true

console.log(+0==-0);//true
Object.is(+0,-0);//false

--------------------------

console.log(0=='');//true
Object.is(0,'');//false

console.log(null==undefined);//true
Object.is(null,undefined);//false

console.log([1]==true);//true
Object.is([1],true);//false

(5)typeof与instanceof的区别

typeof一般用于检测基本数据类型。返回的值包括string,number,boolean,undefined,function,object.

function Person() {
    this.name = "liuxiaokang";
}
var a = [34, 4, 3, 54],
    a2 = {},
    a3 = null;
var a4 = new Person();
var b = 34,
    c = "adsfas",
    d = function () {
        console.log("我是函数");
    },
    e = true,
    g;

console.log(typeof a); //object
console.log(typeof a2); //object
console.log(typeof a3); //obejct
console.log(typeof a4); //object

console.log(typeof b); //number
console.log(typeof c); //string
console.log(typeof d); //function
console.log(typeof e); //boolean
console.log(typeof g); //undefined

instanceof用于检测引用数据类型,检测某个对象是不是某个构造函数的实例。原理是按照原型链进行查找,查找该对象所在的原型链中是否包含该构造函数的原型对象。

function Man() {}
let man = new Man();
man.__proto__ = null;
console.log(man instanceof Man);//false

console.log([] instanceof Array); //true
console.log([] instanceof Object); //true

自定义instanceof:

function my_instanceof(object, fnName) {
  let prototype = object.__proto__;
  while (prototype) {
    if (fnName.prototype === prototype) {
      return true;
    } else {
      prototype = prototype.__proto__;
    }
  }
  return false;
}
console.log(my_instanceof([], Array)); //true
console.log(my_instanceof([], Object)); //true
console.log(my_instanceof({}, Array)); //false

(6)写一个函数判断变量类型:

方法一:利用typeof和instanceof

// 1.先判断是否为null, (需要使用严格比较运算符, 因为undefined == null)
// 2.然后通过typeof判断出其他的基本数据类型,若为number还需进一步判断是否为NaN,注意:function的检测直接可以在typeof的判断中进行
// 3.对于是obejct的数据在判断的时候需要先判断是否为Obejct,再进一步判断是数组,还是日期,如果都不是,那就是对象
// 总的来说分为3段:null,"非object"和object
function getType(a) {
    if (a === null) {
        console.log("空值");
        return;
    }
    let state = typeof a;

    if (state != "object") {
        switch (state) {
            case "string":
                console.log("字符串");
                break;
            case "number":
                if (isNaN(a)) {
                    console.log("NaN");
                } else {
                    console.log("数字");
                }
                break;
            case "boolean":
                console.log("布尔值");
                break;
            case "undefined":
                console.log("未赋值变量");
                break;
            case "function":
                console.log("函数");
        }
    } else {
        if (a instanceof Object) {
            if (a instanceof Array) {
                console.log("数组");
            } else if (a instanceof Date) {
                console.log("时间");
            } else {
                console.log("对象");
            }
        }
    }
}
getType("sss"); //字符串
getType(21312); //数字
getType(true); //布尔值
getType(null); //空值
let m;
getType(m); //未赋值变量
getType([]); //数组
getType({}); //对象
getType(new Date()); //时间
getType(function () {}); //函数
getType(NaN); //NaN

方法二:利用Obejct.prototype.toString()方法(参照js笔记-25.toString方法.js)

function getType(value) {
    let toString = Object.prototype.toString;
    let str = toString.call(value);//[object Function]
    let type = str.substring(8, str.length - 1);
    //上述可写为:
    //let type= str.match(/\[object (.*?)\]/)[1]
    
    // 这里需要额外判断NaN,注意:不能使用isNaN,因为存在Number转换
    if (Number.isNaN(value)) {
        console.log("NaN");
        return;
    } else {
        console.log(type);
    }
}
getType("sss"); //String
getType(21312); //Number
getType(true); //Boolean
getType(null); //Null
let m;
getType(m); //Undefined
getType([]); //Array
getType({}); //Object
getType(new Date()); //Date
getType(function () {}); //Function
getType(NaN); //NaN

5.数组:

(1)数组的方法:

添加元素:push unshift

删除元素:pop shift

在任意位置添加或删除元素:splice

数组合并:concat(原不变)

**排序相关:**翻转数组 reverse 排序 sort

迭代器函数相关:

every some forEach filter map reduce

reduce,reduceRight:返回一个将被叠加到累加器的值,reduce 方法停止执行后会返回

这个累加器。

[1,2,3,4,5].reduce((a,b)=>a+b);//15 求数组的和

搜索相关

indexOf lastIndexOf 返回第一个或最后一个匹配到的元素的索引值

find findIndex 返回第一个满足条件的值或索引

[1, 5, 10, 15].find(function(value, index, arr) {
  return value > 9;
}) // 10

includes 查询指定的索引位置后是否存在某元素

输出数组为字符串:

toString()

join()

ES6新增:

for…of遍历数组

entries,keys,values 生成一个迭代器

Array.from() Array.of() 创建数组

fill 填充数组

copywithin 数组内部元素替换

其他:

valueOf,toString

join

slice

(2)数组去重(7种):

https://segmentfault.com/a/1190000016418021?utm_source=tag-newest

// 方法1.indexOf方法,判断新数组中是否存在该元素
let arr = [11, 22, 33, 22, 44];
let newArr = [];
arr.forEach((item) => {
    if (newArr.indexOf(item) == -1) {
        newArr.push(item);
    }
});
console.log(newArr); //[ 11, 22, 33, 44 ]

// 方法2:将数组的每个元素存储在对象的键中,若后面有重复,则删除数组中的该元素
let arr2=[11, 22, 33, 22, 44];
let obj = {};
let newArr2 = [];
for(let i=0;i<arr2.length;i++){
    var tmp=arr2[i];
    if(obj[tmp]){
        arr2.splice(i,1);
        i--;
    }else{
        obj[tmp]=tmp;
    }
}
console.log(arr2);//[ 11, 22, 33, 44 ]

// 方法3:双重for循环+splice 一个元素一个元素的比较,删除重复的值
let arr5 = [11, 22, 33, 22, 44];
for (let i = 0; i < arr5.length; i++) {
    for (let j = i + 1; j < arr5.length; j++) {
        if (arr5[i] == arr5[j]) {
            arr5.splice(j, 1);
            j--;
        }
    }
}
console.log(arr5); //[ 11, 22, 33, 44 ]

// 方法4:sort排序+for循环 排序后,将后面一项与前一项进行比较
let arr6 = [11, 22, 33, 22, 44];
arr6.sort();
let newArr6 = [];
newArr6.push(arr6[0]);
for (let i = 1; i < arr6.length; i++) {
    // 如果后一项与前一项不同,则向新数组中添加元素
    if (arr6[i] != arr6[i - 1]) {
        newArr6.push(arr[i]);
    }
}
console.log(newArr6); //[ 11, 22, 22, 44 ]

// 方法5:利用filter 只返回第一次出现的值
let arr7 = [11, 22, 33, 22, 44];
let newArr7 = arr7.filter((item, index) => {
    // 只返回数组中第一次出现的该值
    return arr7.indexOf(item) == index;
});
console.log(newArr7); //[ 11, 22, 22, 44 ]

// 方法6:ES6 Set集合+Array.from()
let newArr3 = Array.from(new Set(arr)); //通过Array.from将具有iterator接口的数据结构转换为数组
console.log(newArr3); //[ 11, 22, 33, 44 ]

// 方法7:ES6 Set集合+ ...展开运算符
let newArr4 = [...new Set(arr)];
console.log(newArr4); //[ 11, 22, 33, 44 ]

(3)如何判断一个变量是否为数组(4种):

let a=[];

//方法1:instanceof
console.log(a instanceof Array);//true

//方法2:Array.isArray
console.log(Array.isArray(a));//true

//方法3:Object.prototype.toString
let state=Object.prototype.toString.call(a).includes("Array");
console.log(state)//true


//方法4:判断该对象的原型对象中的constructor属性指向的构造函数是否为Array
console.log([].__proto__.constructor.toString().includes("Array"))//true
console.log({}.__proto__.constructor.toString().includes("Array"))//false

(4)数组遍历方法的比较:

1.for循环(缺点:写法麻烦)

2.forEach

数组的每个元素都会执行指定的函数,无法通过break和return跳出循环

3.for…in

主要是为遍历对象设计的,遍历出的对象的键值,且是字符串的形式

还会遍历出原型链上的属性

for (const i in [1, 2, 3, 4]) {
    console.log(i);// 0 1 2 3
    console.log(typeof i);//string
}


let o = {
  name: "liu",
  age: 21,
};
o.__proto__.sing = function () {
  console.log("sing");
};
console.log(o);
for (let key in o) {
  console.log(key);//name age sing
}

4.for…of

跟for…in一样简洁;

可以与break,continue,return搭配使用;

可以遍历所有数据结构。一个数据结构只要部署了Symbol.iterator属性,就被视为具有 iterator 接口,就可以用for…of循环遍历它的成员。for…of的原理是该语法内部会自动调用该数据结构的Symbol.iterator属性,然后根据next获取其中的值。

原生具备iterator接口的数据结构:Array String Set Map arguments对象 (DOM NodeList 对象) Generator 对象;

Object不具有Iterator接口,所以不能通过for…of遍历。

for (const i of [1, 2, 3, 4, 5]) {
    if (i > 4) {
        return;
    }
    console.log(i); //1 2 3 4
}

6.String包装对象:

(1)String的方法:

indexOf() lastIndexOf()

charAt()

charCodeAt()

concat()

substring() substr()

split()

trim()

toLowerCase() toUpperCase()

slice()

replace()

match()

search()

ES6新增:

String.prototype.includes()

(2)字符串转数字的方法:

parseInt() parseFloat()

Number()

‘12121’-0 隐式转换

(3)string的startwith和indexof两种方法的区别:

indexOf是ES5中的方法,用于返回字符串中某个字符出现的位置,返回值为数字。

startwith是ES6新增的方法,用于检测某字符是否在原字符串的头部,返回值为布尔值。第二个参数表示开始搜索的位置。

let s = 'Hello world!';
s.startsWith('Hello') // true
s.startsWith('world', 6) // true

7.Object:

怎么获得对象上的属性:

for…in 注意:还会遍历出对象原型对象上的方法,可以通过obj.hasOwnProperty(key)选择属于自身的属性。

Object.keys() 会返回一个包含属性的数组。

Object.getOwnPropertyNames() 返回一个包含属性的数组。

前两种只能遍历出可枚举的属性,第三种还能遍历出不可枚举的属性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值