目录
9、 hash+hasOwnProperty+JSON.stringify(终级版)
javascript中的this与call/apply/bind的6中关系
js基础数据类型有哪些
基本数据类型(值类型): Number、String、Boolean、Undefined、Null、Symbol(es6新增独一无二的值) 和 BigInt(es10新增);
引用数据类型: Object。包含Object、Array、 function、Date、RegExp。
备注: 基本数据类型,又称值类型。
栈堆存储
值类型栈存储: 主要针对(Number、String、Boolean)三种数据。直接存储在栈(stack)中,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储。
例如:
当我们执行下方代码时栈内存中的变化:
var a=100;
var b=100;
a=200;
引用类型堆栈存储: 主要针对Object、Array这两种引用数据以及null, 同时存储在栈(stack)和堆(heap)中,占据空间大、大小不固定。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
备注:
引用类型存储把值存储在堆内存中,堆内存是从下往上存储。生成唯一内存地址。然后在栈内存中把地址赋值给变量。栈内存是从上往下存储的。之所以如此划分内存主要考虑到特别大的对象进行值传递时的效率问题。
js数据类型判断
四种方法
typeof、instanceof、constructor、Object.prototype.toString.call()、jquery.type()
1 .typeof
console.log(
typeof 100, //"number"
typeof 'abc', //"string"
typeof false, //"boolean"
typeof undefined, //"undefined"
typeof null, //"object"
typeof [1,2,3], //"object"
typeof {a:1,b:2,c:3}, //"object"
typeof function(){console.log('aaa');}, //"function"
typeof new Date(), //"object"
typeof /^[a-zA-Z]{5,20}$/, //"object"
typeof new Error() //"object"
typeof new Number(100), //'object'
typeof new String('abc'),// 'string'
typeof new Boolean(true),//'boolean'
);
基本数据类型中:Number,String,Boolean,undefined 以及引用数据类型中Function ,可以使用typeof检测数据类型,分别返回对应的数据类型小写字符。
另:用typeof检测构造函数创建的Number,String,Boolean都返回object
基本数据类型中:null 。引用数据类型中的:Array,Object,Date,RegExp。不可以用typeof检测。都会返回小写的object
2 . instanceof
除了使用typeof来判断,还可以使用instanceof。instanceof运算符需要指定一个构造函数,或者说指定一个特定的类型,它用来判断这个构造函数的原型是否在给定对象的原型链上。
console.log(
100 instanceof Number, //false
'dsfsf' instanceof String, //false
false instanceof Boolean, //false
undefined instanceof Object, //false
null instanceof Object, //false
[1,2,3] instanceof Array, //true
{a:1,b:2,c:3} instanceof Object, //true
function(){console.log('aaa');} instanceof Function, //true
new Date() instanceof Date, //true
/^[a-zA-Z]{5,20}$/ instanceof RegExp, //true
new Error() instanceof Error //true
)
基本数据类型中:Number,String,Boolean。字面量值不可以用instanceof检测,但是构造函数创建的值可以,如下:
var num = new Number(123);
var str = new String('dsfsf');
var boolean = new Boolean(false);
还需要注意null和undefined都返回了false,这是因为它们的类型就是自己本身,并不是Object创建出来它们,所以返回了false。
3 .constructor
constructor是prototype对象上的属性,指向构造函数。根据实例对象寻找属性的顺序,若实例对象上没有实例属性或方法时,就去原型链上寻找,因此,实例对象也是能使用constructor属性的。
如果输出一个类型的实例的constructor,就如下所示:
console.log(new Number(123).constructor)
//ƒ Number() { [native code] }
可以看到它指向了Number的构造函数,因此,可以使用num.constructor==Number来判断一个变量是不是Number类型的。
var num = 123;
var str = 'abcdef';
var bool = true;
var arr = [1, 2, 3, 4];
var json = {name:'wenzi', age:25};
var func = function(){ console.log('this is function'); }
var und = undefined;
var nul = null;
var date = new Date();
var reg = /^[a-zA-Z]{5,20}$/;
var error= new Error();
function Person(){
}
var tom = new Person();
// undefined和null没有constructor属性
console.log(
tom.constructor==Person,
num.constructor==Number,
str.constructor==String,
bool.constructor==Boolean,
arr.constructor==Array,
json.constructor==Object,
func.constructor==Function,
date.constructor==Date,
reg.constructor==RegExp,
error.constructor==Error
);
//所有结果均为true
除了undefined和null之外,其他类型都可以通过constructor属性来判断类型。
4 . 使用Object.prototype.toString.call()检测对象类型
可以通过toString() 来获取每个对象的类型。为了每个对象都能通过 Object.prototype.toString() 来检测,需要以 Function.prototype.call() 或者 Function.prototype.apply() 的形式来调用,传递要检查的对象作为第一个参数,称为thisArg。
var toString = Object.prototype.toString;
toString.call(123); //"[object Number]"
toString.call('abcdef'); //"[object String]"
toString.call(true); //"[object Boolean]"
toString.call([1, 2, 3, 4]); //"[object Array]"
toString.call({name:'wenzi', age:25}); //"[object Object]"
toString.call(function(){ console.log('this is function'); }); //"[object Function]"
toString.call(undefined); //"[object Undefined]"
toString.call(null); //"[object Null]"
toString.call(new Date()); //"[object Date]"
toString.call(/^[a-zA-Z]{5,20}$/); //"[object RegExp]"
toString.call(new Error()); //"[object Error]"
这样可以看到使用Object.prototype.toString.call()
的方式来判断一个变量的类型是最准确的方法。
5 .无敌万能的方法:jquery.type()
如果对象是undefined或null,则返回相应的“undefined”或“null”。
jQuery.type( undefined ) === "undefined"
jQuery.type() === "undefined"
jQuery.type( window.notDefined ) === "undefined"
jQuery.type( null ) === "null"
如果对象有一个内部的[[Class]]和一个浏览器的内置对象的 [[Class]] 相同,我们返回相应的 [[Class]] 名字。 (有关此技术的更多细节。 )
jQuery.type( true ) === "boolean"
jQuery.type( 3 ) === "number"
jQuery.type( "test" ) === "string"
jQuery.type( function(){} ) === "function"
jQuery.type( [] ) === "array"
jQuery.type( new Date() ) === "date"
jQuery.type( new Error() ) === "error" // as of jQuery 1.9
jQuery.type( /test/ ) === "regexp"
其他一切都将返回它的类型“object”
6 . 自己也可以封装一个获取变量准确类型的函数
function gettype(obj) {
var type = typeof obj;
if (type !== 'object') {
return type;
}
//如果不是object类型的数据,直接用typeof就能判断出来
//如果是object类型数据,准确判断类型必须使用Object.prototype.toString.call(obj)的方式才能判断
return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1');
}
数组去重
将数组var arr = [1,1,‘true’,‘true’,true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,‘NaN’, 0, 0, ‘a’, ‘a’,{},{}]中重复的值过滤掉
1、 ES6-set
使用ES6中的set是最简单的去重方法
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,
undefined, null,null, NaN,NaN,'NaN', 0, 0, 'a', 'a',{},{}];
function arr_unique1(arr){
return [...new Set(arr)];
//或者
//return Array.from(new Set(arr));
}
arr_unique1(arr); // (13)[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}, {…}]
该方法可以说是最完美的方法,就是需要环境支持ES6
2、利用Map数据结构去重
创建一个空Map数据结构,遍历需要去重的数组,把数组的每一个元素作为key存到Map中。由于Map中不会出现相同的key值,所以最终得到的就是去重后的结果。
function arr_unique2(arr) {
let map = new Map();
let array = new Array(); // 数组用于返回结果
for (let i = 0; i < arr.length; i++) {
if(map .has(arr[i])) { // 如果有该key值
map .set(arr[i], true);
} else {
map .set(arr[i], false); // 如果没有该key值
array .push(arr[i]);
}
}
return array ;
}
console.log(arr_unique2(arr)); //(13) [1, "a", "true", true, 15, false, 1, {…}, null, NaN, NaN, "NaN", 0, "a", {…}, undefined]
3、 利用递归去重
function arr_unique3(arr) {
var array= arr;
var len = array.length;
array.sort(function(a,b){ //排序后更加方便去重
return a - b;
})
function loop(index){
if(index >= 1){
if(array[index] === array[index-1]){
array.splice(index,1);
}
loop(index - 1); //递归loop,然后数组去重
}
}
loop(len-1);
return array;
}
console.log(arr_unique3(arr)); //(14) [1, "a", "true", true, 15, false, 1, {…}, null, NaN, NaN, "NaN", 0, "a", {…}, undefined]
4、 forEach + indexOf
function arr_unique4(arr){
var res = [];
arr.forEach((val,index)=>{
if( res.indexOf(val) === -1 ){
res.push(val);
}
});
return res;
}
console.log(arr_unique4(arr)); // (14) [1, "true", true, 15, false, undefined, null, NaN, NaN, "NaN", 0, "a", {…}, {…}]
该方法的不足之处在于无法对NaN进行过滤,原因是var a = [1, NaN , 2]; a.indexOf(NaN) === -1;,改善的方法是使用includes方法
5、 filter+indexOf
function arr_unique5(arr){
return arr.filter((val,index,item)=>{
return item.indexOf(val) === index;
});
}
arr_unique5(arr); // (12) [1, "true", true, 15, false, undefined, null, "NaN", 0, "a", {…}, {…}]
6、 forEach + includes
function arr_unique6(arr){
var res = [];
arr.forEach((val)=>{
if( ! res.includes(val) ){
res.push(val);
}
});
return res;
}
arr_unique6(arr); // (13) [1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}, {…}]
该方法也算是比较完美,没有什么遗漏的地方
7、 reduce + includes
function arr_unique7(arr){
return arr.reduce( (prev, cur )=>{
if( ! prev.includes(cur) ){
prev.push(cur);
}
return prev;
} ,[]);
}
arr_unique7(arr); // (13)[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}, {…}]
8、 嵌套循环+splice
function arr_unique8(arr){
for(var i = 0 ; i < arr.length; i++){
for( var j = i + 1; j < arr.length; j++){
if( arr[i] === arr[j] ){
arr.splice(j,1);
}
}
}
return arr;
}
arr_unique8(arr); // (14) [1, "true", true, 15, false, undefined, null, NaN, NaN, "NaN", 0, "a", {…}, {…}]
这是最麻烦的方法,效率十分低下,每一个循环都会去动态获取数组的length。且该方法无法过滤掉NaN,因为NaN === NaN的结果为false。
9、 hash+hasOwnProperty+JSON.stringify(终级版)
function arr_unique9(arr){
var hash = {};
return arr.filter( (val)=>{
return hash.hasOwnProperty( typeof val + JSON.stringify(val) ) ? false : hash[typeof val + JSON.stringify(val)] = true ;
});
}
arr_unique9(arr); // (12) [1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}]
这种方法是终极版的,因为它可以进行数组中的对象元素的去重!前面的几种方法是不可以进行对象去重的。不过话说回来,JS中的对象是本身就是一个地址的引用,比如 {} == {} ;//false,两者是两个不同的对象,这里我将其进行JSON.stringify进行简化。
经亲测,使用ES6的Set和Map效率最高,reduce()和sort()效率还可以,双层循环效率最低。
for···in和for···of的区别:
首先一句话:(for···in取key,for··of取value)
①从遍历数组角度来说,for···in遍历出来的是key(即下标),for···of遍历出来的是value(即数组的值);
var arr = [99,88,66,77];
for(let i in arr){
console.log(i); //0,1,2,3
}
for(let i of arr){
consoel.log(i); //99,88,66,77
}
②从遍历字符串的角度来说,同数组一样。
③从遍历对象的角度来说,for···in会遍历出来的为对象的key,但for···of会直接报错。
var obj = {name:"Bob",age:25};
for(var i in obj){
console.log(i) // name age
}
for(var i of obj){
console.log(i) //报错
}
④如果要使用for…of遍历普通对象,需要配合Object.keys()一起使用。
var person={
name:'coco',
age:22,
locate:{
country:'China',
city:'beijing',
}
}
for(var key of Object.keys(person)){
//使用Object.keys()方法获取对象key的数组
console.log(key+": "+person[key]);//name: coco,age: 22,locate: [object Object]
}
null和undefined区别
相同性:
在JavaScript中,null 和 undefined 几乎相等
在 if 语句中 null 和 undefined 都会转为false两者用相等运算符比较也是相等
console.log(null==undefined); //true 因为两者都默认转换成了false
console.log(typeof undefined); //"undefined"
console.log(typeof null); //"object"
console.log(null===undefined); //false "==="表示绝对相等,null和undefined类型是不一样的,所以输出“false”
null和undefined区别:
null表示没有对象,可能将来要赋值一个对象,即该处不应该有值
1) 作为函数的参数,表示该函数的参数不是对象
2) 作为对象原型链的终点
Object.getPrototypeOf(Object.prototype)
// null
undefined表示缺少值,即此处应该有值,但没有定义
1)定义了形参,没有传实参,显示undefined
2)对象属性名不存在时,显示undefined
3)函数没有写返回值,即没有写return,拿到的是undefined
4)写了return,但没有赋值,拿到的是undefined
var i;
i // undefined
function f(x){console.log(x)}
f() // undefined
var o = new Object();
o.p // undefined
var x = f();
x // undefined
javascript中的this与call/apply/bind的6中关系
总结
- 在浏览器里,在全局范围内this 指向window对象;
- 在函数中,this永远指向最后调用他的那个对象;
- 构造函数中,this指向new出来的那个新的对象;
- call、apply、bind中的this被强绑定在指定的那个对象上;
- 箭头函数中this比较特殊,箭头函数this为父作用域的this,不是调用时的this.要知道前四种方式,都是调用时确定,也就是动态的,而箭头函数的this指向是静态的,声明的时候就确定了下来;
- apply、call、bind都是js给函数内置的一些API,调用他们可以为函数指定this的执行,同时也可以传参。
面试只需说出以上6点即可,如果追问,参考下面内容,针对性回答。
前言
在讲this之前,先得说说 环境 这个概念。一门语言在运行的时候,需要一个环境,叫做宿主环境。对于JavaScript,宿主环境最常见的是web浏览器,另一个最为常见的就是 Node 了,同样作为宿主环境,node 也有自己的 JavaScript 引擎:V8(目前最快JavaScript引擎、Google生产)。
this的初衷
this设计的初衷是在函数内部使用,用来指代当前的运行环境。为什么这么说呢?
JavaScript中的对象的赋值行为是将地址赋给一个变量,引擎在读取变量的时候其实就是要了个地址然后再从原始地址中读取对象。而JavaScript 允许函数体内部引用当前环境的其他变量,而这个变量是由运行环境提供的。由于函数又可以在不同的运行环境执行(如全局作用域内执行,对象内执行…),所以需要一个机制来表明代码到底在哪里执行!于是this出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境。
全局中的this
在浏览器里,在全局范围内:
this等价于window对象;
用var声明一个变量和给this或者window添加属性是等价的;
如果你在声明一个变量的时候没有使用var或者let、const(es6),你就是在给全局的this添加或者改变属性值。
// 1
console.log(this === window); //true
//2
var name = "Jake";
console.log(this.name ); // "Jake"
console.log(window.name ); // "Jake"
//3
age = 23;
function testThis() {
age = 18;
}
console.log(this.age ); // 23
testThis();
console.log(this.age ); // 18
总结起来就是:在全局范围内this是大哥大,它等价于window对象(即指向window),如果你声明一些全局变量(不管在任何地方),这些变量都会作为this的属性。
函数中的 this
对于函数中的this的指向问题,有一句话很好用:运行时this永远指向最后调用它的那个对象。
举一个例子
var name = "windowsName";
function sayName() {
var name = "Jake";
console.log(this.name); // windowsName
console.log(this); // Window
}
sayName();
console.log(this) // Window
我们看最后调用 sayName的地方 sayName();,前面没有调用的对象那么就是全局对象 window,这就相当于是 window.sayName()。
需要注意的是,对于严格模式来说,默认绑定全局对象是不合法的,this被置为undefined。会报错 Uncaught TypeError: Cannot read property 'name' of undefined。
再看下面这个栗子
function foo() {
console.log( this.age );
}
var obj1 = {
age : 23,
foo: foo
};
var obj2 = {
age : 18,
obj1: obj1
};
obj2.obj1.foo(); // 23
还是开头的那句话,最后调用foo()的是obj1,所以this指向obj1,输出23。
构造函数中的this
所谓构造函数,就是通过这个函数生成一个新对象(object)。当一个函数作为构造器使用时(通过 new 关键字), 它的 this 值绑定到新创建的那个对象。如果没使用 new 关键字, 那么他就只是一个普通的函数, this 将指向 window 对象。
这又是另一个经典话题:new 的过程
var a = new Foo("zhang","jake");
new Foo{
var obj = {};
obj.__proto__ = Foo.prototype;
var result = Foo.call(obj,"zhang","jake");
return typeof result === 'obj'? result : obj;
}
若执行 new Foo(),过程如下:
1) 创建新对象 obj;
2) 给新对象的内部属性赋值,构造原型链(将新对象的隐式原型指向其构造函数的显示原型);
3) 执行函数 Foo,执行过程中内部 this 指向新创建的对象 obj(这里使用了call改变this指向);
4) 如果 Foo 内部显式返回对象类型数据,则返回该数据,执行结束;否则返回新创建的对象 obj。
var name = "Jake";jiuzhixiang
function testThis(){
this.name = 'jakezhang';
this.sayName = function () {
return this.name;
}
}
console.log(this.name ); // Jake
new testThis();
console.log(this.name ); // Jake
var result = new testThis();
console.log(result.name ); // jakezhang
console.log(result.sayName()); // jakezhang
testThis();
console.log(this.name ); // jakezhang
很显然,谁被new了,this就指向谁。
class中的this
在es6中,类,是 JavaScript 应用程序中非常重要的一个部分。类通常包含一个 constructor , this可以指向任何新创建的对象。
不过在作为方法时,如果该方法作为普通函数被调用, this也可以指向任何其他值。与方法一样,类也可能失去对接收器的跟踪
class Hero {
constructor(heroName) {
this.heroName = heroName;
}
dialogue() {
console.log(`I am ${this.heroName}`)
}
}
const batman = new Hero("Batman");
batman.dialogue();