一、typeof 对于基本数据类型(boolean、null、undefined、number、string、symbol)来讲,除了 null 均可以显示正确的类型;对于对象来讲,除了函数都会显示 object。
二、instanceof 是经过原型链来判断的。能够判断一个对象的正确类型,可是对于基本数据类型的没法判断。
三、instanceof能正确判断对象的原理:
经过判断对象的原型链中是否是能找到类型的原型 [].__proto__ == Array.prototypejavascript
function myInstanceof(left, right) { let prototype = right.prototype left = left.__proto__ while (true) { if (left === null || left === undefined) return false if (prototype === left) return true left = left.__proto__ } }
instanceof 主要的实现原理就是只要右边变量的 prototype 在左边变量的原型链上便可。所以,instanceof 在查找的过程当中会遍历左边变量的原型链,直到找到右边变量的 prototype,若是查找失败,则会返回 false,告诉咱们左边变量并不是是右边变量的实例。
四、相关笔试题:
1)JavaScript中如何检测一个变量是一个String类型?请写出函数实现。java
var str = 'ssssssss'; typeof str === 'string' str.constructor === String
一、闭包是指有权访问另外一个函数做用域中的变量的函数。
建立闭包的常见方式,就是在一个函数内部建立另外一个函数。面试
function a(){ var num = 100; function b(){ console.log(num); } return b; } var c = a(); c(); //100
如上所示:函数b能够访问到函数a中的变量,函数b就是闭包。正则表达式
二、闭包的用途:1)读取函数内部的变量;2)让这些变量始终保存在内存中
因为闭包会携带包含它的函数的做用域,所以会比其余函数占用更多的内存。过分使用闭包可能会致使内存占用过多,建议只在绝对必要时再考虑使用闭包。数组
三、相关面试题
1)循环中使用闭包解决‘var’定义函数的问题闭包
for(var i = 1; i <= 5; i++){ setTimeout(function timer(){ console.log(i); },2000) }
这里由于setTimeout是异步执行,循环会先执行完毕,这时i等于6,而后就会输出5个6。解决方法以下所示:
第一种是使用letapp
for(let i = 1; i <= 5; i++){ setTimeout(function timer(){ console.log(i); },2000) }
第二种是使用闭包异步
for(var i = 1; i <= 5; i++){ (function(j){ setTimeout(function timer(){ console.log(j); },2000) })(i) }
这里首先使用了当即执行函数将i传入函数内部,这个时候值就被固定在了参数j上面不会改变,当下次执行timer这个闭包的时候,就可使用外部函数的变量j。函数
一、咱们建立的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象。而这个对象的用途是包含能够由特定类型的全部实例共享的属性和方法。prototype就是经过调动构造函数而建立的那个对象实例的原型对象。
使用原型对象的好处是可让全部对象实例共享它所包含的属性和方法。
示例:this
function Person(){} var p1 = new Person(); var p2 = new Person(); Person.prototype.name = 'CoCo'; console.log(p1.name); //CoCo console.log(p2.name); //CoCo
Person构造函数下有一个prototype属性。Person.prototype就是原型对象,也就是实例p一、p2的原型。
二、在默认状况下,全部原型对象都会自动得到一个constructor属性,这个属性包含一个指向prototype属性所在函数的指针。
function Person(){} console.log(Person.prototype.constructor === Person); //true
三、Firefox、Safari和Chrome在每一个对象上都支持一个属性__proto__,这个属性对脚本是彻底不可见的。__proto__用于将实例与构造函数的原型对象相连。
链接实例与构造函数的原型对象之间。
function Person(){} var p1 = new Person(); console.log(p1.__proto__ === Person.prototype); //true //isPrototypeOf:检测一个对象是不是另外一个对象的原型。或者说一个对象是否被包含在另外一个对象的原型链中 console.log(Person.prototype.isPrototypeOf(p1)); //true //getPrototypeOf:返回对象__proto__指向的原型prototype console.log(Object.getPrototypeOf(p1) == Person.prototype); //true
每一个函数都包含两个非继承而来的方法: appy()和call()。这两个方法的用途都是在特定的做用域中调用函数,而后能够设置调用函数的this指向。
一、apply()第一个参数是this所要指向的那个对象,若是设为null或undefined或者this,则等同于指定全局对象。二是参数(能够是数组也能够是arguments对象)。
var a = 1; function fn1(){ console.log(this.a); } var obj = { a:2 }; fn1(); //1 fn1.apply(obj); //2 fn1.call(obj);//2 fn1.bind(obj)();//2
二、call()方法能够传递两个参数。第一个参数是指定函数内部中this的指向(也就是函数执行时所在的做用域),第二个参数是函数调用时须要传递的参数。第二个参数必须一个个添加。
三、bind()方法能够传递两个参数。第一个参数是指定函数内部中this的指向,第二个参数是函数调用时须要传递的参数。第二个参数必须一个个添加。
四、三者区别:
1)均可以在函数调用时传递参数,call、bind方法须要直接传入,而apply方法能够以数组或者arguments的形式传入。
2)call、apply方法是在调用以后当即执行函数,而bind方法没有当即执行,须要将函数再执行一遍。
五、手写三种函数
1)apply
Function.prototype.myApply = function(context){ if(typeof this !== 'function'){ throw new TypeError('Error'); } context = context || window; context.fn = this; let result; if(arguments[1]){ result = context.fn(...arguments[1]); }else{ result = context.fn(); } delete context.fn; return result; };
2)call
Function.prototype.myCall = function(context){ if(typeof this !== 'function'){ throw new TypeError('Error'); } context = context || window; context.fn = this; const args = [...arguments].slice(1); const result = context.fn(...args); delete context.fn; return result; };
3)bind
Function.prototype.myBind = function(context){ if(typeof this !== 'function'){ throw new TypeError('Error'); } const _this = this; const args = [...arguments].slice(1); return function F(){ if(this instanceof F){ return new _this(...args,...arguments); } return _this.apply(context,args.concat(...arguments)); } };
bind返回了一个函数,对于函数来讲有两种方式调用,一种是直接调用,一种是经过new的方式:
直接调用,这里选择了apply的方式实现,由于由于 bind 能够实现相似这样的代码 f.bind(obj, 1)(2),因此咱们须要将两边的参数拼接起来,因而就有了这样的实现 args.concat(...arguments)。
最后来讲经过 new 的方式,对于 new 的状况来讲,不会被任何方式改变 this,因此对于这种状况咱们须要忽略传入的 this
一、浅拷贝
let a = { age: 1 } let b = a a.age = 2 console.log(b.age) // 2
基本数据类型是数据拷贝,会从新开辟一个空间存放拷贝的值。对象类型在赋值的过程当中实际上是复制了地址,从而会致使改变了一方其余也都被改变的状况。一般在开发中咱们不但愿出现这样的问题,咱们可使用浅拷贝来解决这个状况。
1)概念:对于对象类型,浅拷贝就是对对象地址的拷贝,拷贝的结果是两个对象指向同一个地址,并无开辟新的栈,修改其中一个对象的属性,另外一个对象的属性也会改变。
2)实现:
a、经过Object.assign来实现。Object.assign()方法的第一个参数是目标对象,后面的参数都是源对象。用于对象的合并,将源对象的全部可枚举属性复制到目标对象。
Object.assign 只会拷贝全部的属性值到新的对象中,若是属性值是对象的话,拷贝的是地址,因此并非深拷贝。
let a = { age: 1 } let b = Object.assign({}, a) a.age = 2 console.log(b.age) // 1
b、经过展开运算符 ... 来实现浅拷贝
let a = { age: 1 } let b = { ...a } a.age = 2 console.log(b.age) // 1
c、循环实现
这里是浅拷贝,由于对象仍是拷贝的地址。
var a = { name:'coco', age:33, fn:function(){ console.log("111") }, children:{ age:66 } }; var b = {}; for(var i in a){ b[i] = a[i]; } b.children.age = 100; console.log(a); console.log(b);
二、深拷贝
let a = { age: 1, jobs: { first: 'FE' } } let b = { ...a } a.jobs.first = 'native' console.log(b.jobs.first) // native
浅拷贝只解决了第一层的问题,若是接下去的值中还有对象的话,那么就又回到最开始的话题了,二者享有相同的地址。要解决这个问题,咱们就得使用深拷贝了。
1)概念:对于对象类型,深拷贝会开辟新的栈,两个对象对应两个不一样的地址,修改其中一个对象的属性,另外一个对象的属性不会改变。
2)原理:深复制--->实现原理,先新建一个空对象,内存中新开辟一块地址,把被复制对象的全部可枚举的(注意可枚举的对象)属性方法一一复制过来,注意要用递归来复制子对象里面的全部属性和方法,直到子子.....属性为基本数据类型。
3)实现:
a、经过 JSON.parse(JSON.stringify(object)) 来解决
缺点:能够知足基本的深拷贝,可是对于正则表达式、函数类型则没法进行拷贝。它还会抛弃对象的constructor。
let a = { age: 1, jobs: { first: 'FE' } } let b = JSON.parse(JSON.stringify(a)) a.jobs.first = 'native' console.log(b.jobs.first) // FE
b、递归
var a = { name:'kiki', age:25, children:{ name:'CoCo', age:12 }, arr:['111','222'], fn:function(){ console.log(1); } }; function copy(obj){ if(obj instanceof Array){ var newObj = []; }else{ var newObj = {}; } for(var i in obj){ if(typeof obj[i] == 'object'){ newObj[i] = copy(obj[i]); }else{ newObj[i] = obj[i]; } } return newObj; } var b = copy(a); console.log(a); console.log(b);
c、jQuery.extend() jQuery.extend([deep],target,object1,object2...),deep位Boolean类型,若是为true则进行深拷贝。