1、函数防抖和函数节流
【《javascript高级程序设计》里,函数节流是这里讲的函数防抖。】
函数防抖: 在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
典型的例子:用户输入验证用户名是否有效。假如用户不再输入了,2秒后就验证用户名是否有效,而如果还没到2秒,就重新开始计算2秒。
/**
* 函数防抖方法
* @param Function fn 延时调用函数
* @param Number interval 间隔多长时间
*/
let debounce = function(fn, delay){
let timer = null
return function() {
clearTimeout(timer)
timer = setTimeout(() => {
fn()
}, delay)
}
}
函数节流:规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。
function throttle(fn, interval) {
let prev = 0
return function(){
let now = + new Date()
if(now - prev > interval){
fn()
prev = now
}
}
}
2、js判断数据类型
-
typeof
可以判断 undefined、string、number、Boolean、function。
但不能判断 null,typeof null
返回Object
。
也不能判断 Array,typeof 数组
返回Object
。 -
instanceof
用于测试构造函数的 prototype 属性是否出现在对象的原型链中。
可以用它判断 Function、Array。
但不要用它判断Object
,因为数组/函数 instanceof Object
为 true。 -
constructor
目前运算最快的判断变量类型的方式。
可以判断除了null
或undefined
类型。
当检测null
或undefined
类型的constructor
属性时会报错。 - Object.prototype.toString.call()
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(false); // "[object Boolean]"
Object.prototype.toString.call(123); // "[object Number]"
Object.prototype.toString.call('abc'); // "[object String]"
Object.prototype.toString.call([]); // "[object Array]"
Object.prototype.toString.call({}); // "[object Object]"
Object.prototype.toString.call(function(){}); // "[object Function]"
PS:在ES5中的判断数组还可以用Array.isArray()。
3、null 和 undefined 的区别
- null 在其他语言中与对象有关,
typeof null
返回Object
。
undefined 是后来设计的。 - 在JS最初设计的时候,
null 是一个表示"无"的对象,转为数值时为0,例如:Number(null)
为0
。
undefined 是一个表示"无"的原始值,转为数值时为NaN。Number(undefined)
为NaN
。 - 后来,修改为:
null 表示"没有对象",即该处不应该有值。例如:null 作为对象原型链的终点。
undefined 表示"缺少值",就是此处应该有一个值,但是还没有定义。
- undefined 希望表示一个变量最原始的状态,而非人为操作的结果 。
typeof 之所以会判定 null 为 Object 类型,是因为JavaScript 数据类型在底层都是以二进制的形式表示的,二进制的前三位为 0 会被 typeof 判断为对象类型,而 null 的二进制位恰好都是 0 ,因此,null 被误判断为 Object 类型。 - null 希望表示 一个对象被人为的重置为空对象。
当一个对象被赋值了null 以后,原来的对象在内存中就处于游离状态,GC 会择机回收该对象并释放内存。因此,如果需要释放某个对象,就将变量设置为 null,即表示该对象已经被清空,目前无效状态。
- 《重学前端》:undefined 可以作为一个变量来用,程序不会报错,但是如果想把 null 作为变量用程序会报错。所以,通常我们写一些代码防止undefined被污染。
- JS类型值是存在 32 BIT 单元里,32位有1-3位表示 TYPE TAG,其它位表示真实值,而表示 Object 的标记位正好是低三位都是0。
- 曾经有提案 typeof null === 'null',但提案被拒绝。
4、拷贝对象(浅拷贝,深拷贝)
- Object.assign()
- es6中的对象展开运算符
-
for in
缺点:- 属性描述符不能被复制。
Object.defineProperty()
中定义的描述符不会被拷贝,新对象通过Object.getOwnPropertyDescriptor()
获取到的值为undefined
。 - 只能拷贝可枚举属性。
通过Object.defineProperty()
定义了enumerable
为false
的属性不会被拷贝。 - 若属性的某个值为对象,原对象和拷贝后对象的该属性共享这个对象。
- 属性描述符不能被复制。
-
JSON.parse(JSON.stringify())
缺点: - 不能拷贝值为函数、正则等的属性。
- 若对象有循环引用,会报错。
数组的深拷贝:
(这里待补充……)
5、闭包
什么是闭包?
MDN:闭包是函数和声明该函数的词法环境的组合。
winter:闭包是绑定了执行环境的函数。它与普通函数的区别是,它携带了执行环境。闭包包含2个部分:
1、环境部分
环境:函数的词法环境(执行上下文的一部分)。
标识符列表:函数中用到的未声明的变量。
2、表达式部分
闭包有什么好处?
闭包常常用来「间接访问一个变量」。换句话说,「隐藏一个变量」。
项目中用过闭包?
有时候写组件的时候,组件里面的内容不想要被外部访问到,就需要创建一个封闭的环境,不能让别人「直接访问」这个变量。那怎么办呢?用局部变量。但是用局部变量别人又访问不到,怎么办呢?暴露一个访问器(函数),让别人可以「间接访问」。
比如写签约支付组件:
// 组件
(function(){
var XYS = window.XYS = window.XYS || {}
XYS.Payment = function(options){
// 这里是支付组件的内容
}
})()
// 组件调用
XYS.Payment({})
闭包什么情况下会造成内存泄漏?
闭包不会造成内存泄漏,说内存泄漏是因为 IE 有 bug,IE 在我们使用完闭包之后,依然回收不了闭包里面引用的变量。
6、原型链和继承
参考资料1:JS高级程序设计
理解函数:
函数是对象,函数名是指针。函数名仅仅是指向函数的指针,因此函数名与包含对象指针的其他变量没有什么不同。
ECMAScript 只支持实现继承,而且其实现继承主要是依靠原型链来实现的。
构造函数、原型和实例的关系:
构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。
可以这么理解:
每个函数被创建出来后,它都会有一个prototype属性(即原型),这个属性是一个对象,这个对象包含一个指向该构造函数的指针。而实例都有一个指向这个属性对象的内部指针。
敲重点:构造函数是一个对象,函数的prototype属性也是一个对象,prototype属性有一个指针指向构造函数。
假如我们让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。这就是所谓的原型链。
下图中,有5个对象,分别是:superType、superType.prototype、subType、subType.prototype、instance。(你不用去理会这5个对象是实例还是构造函数,也不管是构造函数对象还是字面量对象,总之它们5个都是对象)。
实例有__proto__
属性。
构造函数有prototype
属性。
分析:
实例 instance
的 __proto__
属性是一个指针,指向 SubType.prototype
这个对象。
构造函数SubType
的prototype
属性是一个指针,指向 SubType.prototype
这个对象。
SubType.prototype
这个对象是 SuperType
的一个实例,所以它也有__proto__
属性,指向 SuperType.prototype
这个对象。
构造函数SuperType
的prototype
属性是一个指针,指向 SuperType.prototype
这个对象。
如何确定原型和实例的关系?
- instanceof 操作符:
instance instanceof parentType
- isPrototypeOf():
parentType.isPrototypeOf(instance)
原型链的问题:
1、包含引用类型值的原型属性会被所有实例共享。
function Parent(){
this.number = [1,2,3]
}
function Children(){}
Children.prototype = new Parent()
var c1 = new Children()
var c2 = new Children()
console.log(c1.number === c2.number) // true
2、在创建子类型的实例时,不能向父类型的构造函数中传递参数。
原型是一个对象。每个函数都有一个属性叫做原型,这个属性指向一个对象。
原型是函数对象的属性,并不是所有对象的属性,对象经过构造函数new出来,那么这个new出来的对象,它的构造函数有一个属性叫原型。
每次你定义一个函数的时候,这个函数的原型属性也就被定义出来了,也就可以使用了,如果不对它进行显示赋值的话,那么它的初始值就是一个空的Object对象。
记住:原型只是函数的一个属性!
function Fn(){}
console.log(Fn.prototype)
所有函数的构造函数都是Function。
function Fn(){}
console.log(Fn.constructor) // ƒ Function() { [native code] }
继承
借用构造函数(经典继承 / 伪造对象)
优点:
1、子类型构造函数中向超类型构造函数传递参数。
2、包含引用类型值的原型属性是独立的,不会被所有实例共享。
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
function SubType(){
//继承了 SuperType
SuperType.call(this, 'Yumi');
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"
ES6里的extends是哪种继承方式?