JavaScript中提供两种类型检测语法——typeof、instanceof。但是这两种都有一定的缺陷。
var num = 1;
console.log(typeof num);//number
var obj = null;
console.log(typeof obj);//object
var arr = [];
console.log(typeof arr);//object
var obj1 = {};
console.log(typeof obj1);//object
var a = 1;
console.log(a instanceof Array);//false
console.log(a instanceof Number);//true
//优势是能够确定是属于数组Array还是属于对象Object
var b = [];
console.log(b instanceof Array);//true
console.log(b instanceof Object);//false
这种方式的原理就是:构造函数的原型的toString方法,会返回一个类似”[object type]”的字符串。
function getType(value) {
if(arguments.length !== 1){
throw new TypeError('参数必须仅仅一个!');
}
if(Object.prototype.toString.call(value) == '[object Object]'){
return 'object';
} else if(Object.prototype.toString.call(value) == '[object Array]'){
return 'array';
} else if(Object.prototype.toString.call(value) == '[object Number]'){
return 'number';
} else if(Object.prototype.toString.call(value) == '[object String]'){
return 'string';
} else if(Object.prototype.toString.call(value) == '[object Function]'){
return 'function';
} else if(Object.prototype.toString.call(value) == '[object Boolean]'){
return 'boolean';
} else if(Object.prototype.toString.call(value) == '[object Null]'){
return 'null';
} else if(Object.prototype.toString.call(value) == '[object Undefined]'){
return 'undefined';
} else {
throw new Error('这是一个未知的类型!');
}
}
var a = undefined;
console.log(Object.prototype.toString.call(a));
构造函数,是面向对象的核心,可是,在使用构造函数的过程中,会有什么样的坑呢?
function Cat(name){
this.name = name;
}
一般而言,new Cat(‘name’)是它的使用方式!!
但是,如果我执行 Cat(“name”) 呢?
导致的结果就是给window对象添加了一个name属性。
function Cat(name){
if(this instanceof Cat){
this.name = name;
} else {
return new Cat(name);
}
}
上面的构造函数,就被称为作用域安全的构造函数。(本质是判断this的作用域,或者是判断this的指向问题。)
惰性函数载入,在我看来,可以认为是一种函数的再次定义。
function add(a,b){
return function(){
return a + b;
}
}
这样的方式有什么用呢?其实个人觉得,它最大的用处在于浏览器兼容。一个非常经典的示例就是ajax的封装!
function ajax(){
if(XMLHttpRequest){
var xhr = new XMLHttpRequest();
//other code
} else {
//code
}
}
//下面是惰性载入的方式
function ajax(){
if(XMLHttpRequest){
return function(){
xhr = new XMLHttpRequest();
//code
}
} else {
return function() {}
}
}
相对而言,第二种函数的使用方式是很有优势的,第一种函数的方式每次都要判断浏览器是否支持XMLHttpRequest对象,而第二种方式,其实我们只需要判断一次,这就是惰性函数载入的好处。
函数的柯里化,其实就是用已有的函数,传入适当的参数,得到一个新的函数的过程。
函数柯里化(function currying),官方概念是这样的:用于创建已经设置好了一个或多个参数的函数。
一般使用闭包的方式进行创建柯里化函数。(下面是一个通用的柯里化函数的创建方式)。
function curry(fn){
var arg1 = Array.prototype.slice.call(arguments, 1);
return function(){
var arg2 = Array.prototype.slice.call(arguments);
return fn.apply(null,arg1.concat(arg2));
}
}
个人认为,函数绑定的问题,重点是this的指向问题。
函数绑定中,Function.prototype.bind()是一个重要的实现方式。
一般有下面这样的使用:
var obj = document.getElementById('root');
obj.onclick = function(){
console.log(this);//这里代表obj对象。
}
//也有这样的方式
obj.onclick = function(){
console.log(this);//这里代表window
}.bind(this);
其实bind方法和call、apply有点类似,都是改变了this的指向问题。
有人说,bind()方法的内部实现原理是什么?我自己用下面的方式写一个简单的demo。
Function.prototype.bind = function(obj){
var self = this;
return function(){
self.apply(obj,arguments);
}
}
考虑到函数的柯里化,我们需要给bind方法进行传递参数。bind函数的封装应该如下:
Function.prototype.bind = function(context){
var self = this;
var arg1 = Array.prototype.slice.call(arguments,1);
return function(){
var arg2= Array.prototype.slice.call(arguments);
self.apply(context,arg1.concat(arg2));
}
}
最后,考虑到原型链的继承,如果想在构造函数中正确的使用bind,可能我们还需要这样:
Function.prototype.bind = function(context){
var self = this;
var F = function(){};
var arg1 = Array.prototype.slice.call(arguments,1);
var bound = function(){
var arg2= Array.prototype.slice.call(arguments);
self.apply(context,arg1.concat(arg2));
}
F.prototype = self.prototype;
bound.prototype = new F();
return bound;
}
这样可能就是最完美的bind方法的实现了。