前言

介绍原型的概念,和相关属性,以及jquery判断纯净对象的实现,不小心点进来的直接 ctrl+f 搜你想找的属性。

  1. 什么是原型

  2. isPrototypeOf() || Object.getPrototypeOf()

  3. hasOwnProperty() || in

  4. jQuery.isPlainObject() 源码解读

什么是原型

prototype(原型,雏形,蓝本) 说新上市的一部手机的原型机,就可以用这个单词。
每一个函数默认都有一个prototype(原型)属性,这个属性指向函数的原型对象。就是说函数的原型是一个对象。先来打印一个函数的这个prototype属性,来看看他是什么样的。

function Zoom(){};
var proto = Zoom.prototype;
console.log(proto);

图片描述

眼见为实,这就是Zoom函数的原型对象,其中还有一个constructor 属性,我们并未对prototype原型对象进行修改,但却有一个constructor属性。默认情况下,函数的原型对象都会获取到一个constructor属性。
constructor(构造器)英文中的解释为构造器。图中constructor的属性值为Zoom函数。便于记忆,也可以理解为,函数的原型是由函数产生的,那构造出原型的东西,就是函数本身。也就是:

Zoom.prototype.constructor = Zoom;
//语言描述就是:zoom函数的原型对象的constructor属性,指向函数本身。

当我们通过new操作符,获取了一个构造函数的实例后(就是产生了一个对象)。先来看一个这样的对象:

function Zoom(){
  this.bird = function(){
    console.log('bird');
  }
};
//在函数的原型上扩展了一个方法
Zoom.prototype.cat = function(){
  console.log("cat");
}
var zoom = new Zoom();
console.log(zoom);

图片描述

可以看到实例化的zoom对象下有一个__proto__属性,而这个属性就指向构造函数Zoom的原型对象。重点是,__proto__连接的是实例对象与构造函数原型对象,而不是,实例对象和构造函数。

isPrototypeOf() || getPrototypeOf()

function Zoom(){
  this.bird = function(){
    console.log('bird');
  }
};
//在函数的原型上扩展了一个方法
Zoom.prototype.cat = function(){
  console.log("cat");
}
Zoom.prototype.fish= function(){
  console.log("fish");
}
var zoom1 = new Zoom();
var zoom2 = new Zoom();
zoom1.cat ();//cat
zoom2.fish();//fish
console.log(zoom1);
console.log(zoom2);

图片描述

根据上一节说的,zoom1,zoom2实例对象都有一个属性__proto__指向构造函数的原型对象,换句话说,就是实例对象和构造函数没什么直接的联系。
但是我们发现,这两个实例都不包含方法,却能够使用a,b 方法,这就是通过查找对象属性的过程来实现的。
当我们在调用一个对象的属性值是,首先会从实例对象本身开始搜索,如果找到了就返回属性值,没有找到就在原型对象上查找。这也是原型对象的属性或方法可以共享的原因。
那如何知道一个对象,例如两个实例的原型链中,是否有构造函数的原型对象(Zoom)的方法呢。这就是isPrototypeOf()。用来确定一个对象是否存在于另一个对象的原型链中。

console.log(Zoom.prototype.isPrototypeOf(zoom1));//true

虽然原型可以共享,但是不能通过实例对象修改原型:

zom1.cat = function (){
  console.log('zom1 输出的 cat');
}
zom1.cat ();//z1 输出的 cat
zom2.cat ();//原型输出的cat

这个其实很好理解,因为对象属性查找是从实例向原型上查找,所以写在实例上的方法如果和原型上的方法同名的话,会屏蔽原型上的方法,可以简单理解为就近原则。

hasOwnProperty() || in

既然同一个方法可以出现在实例中,也可以出现在原型中,如何可以判断是否在实例中呢。
hasOwnProperty() 方法会返回一个布尔值,指示对象是否具有指定的属性作为自身(不继承)属性。
如果判断在zoom1对象自身是否有a属性,就可以:

zoom1.hasOwnProperty(bird); // true
zoom1.hasOwnProperty(fish); // false

因为bird 是zoom1 自身的属性,所以返回true,而fish是zoom1原型的的属性,所以返回false。
另一个要介绍的in方法,它比hasOwnProperty判断的范围更大,无论在原型上或者是在实例上,如果存在要检测的属性,都会返回true。

'bird' in zoom1; // true
'fish' in zoom1; // false

那就可以理解为,在in方法的判断范围中中排除hasOwnProperty的判断范围,剩下的不就是属性只出现在原型中的可能。
转为简单逻辑就是,如果in为真则可能在实例中也可能在原型中,如果hasOwnProperty方法为假,就肯定不是在实例中。所以in为真,hasOwnProperty为假,就一定是在原型中:

function hasProtoTypeProto(attr,obj){
   return !obj.hasOwnProperty(attr) && (attr in obj)
}
hasProtoTypeProto('fish',zoom1) //true
hasProtoTypeProto('bird',zoom1) //false

isPlainObject()

是jquery提供的外部可以直接使用的方法,这个方法是用来判断一个对象是否是纯粹的对象,即对象字面量,而不是一个构造函数的实例。其中涵盖了大部分原型相关的方法。我们先来看一下如何使用。

var log = console.log.bind(console);
log(jQuery.isPlainObject({x:0}));//true
log(jQuery.isPlainObject(new Object({})))//true
log(jQuery.isPlainObject([0]))//false
function Zoom(){
  this.fish = function(){
    return "Im a fish";
  }
}
var zoom = new Zoom();
log(jQuery.isPlainObject(zoom))//false

可以看到只有纯粹的对象才会返回真,typeof类型检测出数组为object,或是构造函数的实例都是返回假。我们看一看在jquery 3.1版本中是如何实现这个方法的。分析过程请见代码:

//传入需要判断的对象
function isPlainObject(obj) {
//没有具体的意义只是保存其他执行的结果。
var proto, Ctor;
if (!obj || toString.call(obj) !== "[object Object]") {
  // toString 在之前的代码中已被定义为 Object.prototype.toString,代码等价于Object.prototype.toString.call( obj ) !== "[object Object]"
  //在任何值上调用Object 原生的toString()方法,都会返回一个[object NativeConstructorName] 格式的字符串,
  //[[Class]]是一个内部属性,所有的对象(原生对象和宿主对象)都拥有该属性,这个属性中就指定了上述字符串中的构造函数名(NativeConstructorName)
  // 类似的 [object Array] [object Function] [object RegExp]
  //但是这个判断不能排除一个实例对象,因为上[[Class]] 属性默认保存对象的类型,所以也会返回Object;
  //所以除了对象的其他类型,都会返回false
  return false;
}
proto = getProto(obj);
// getProto 之前已经被定义为 getProto = Object.getPrototypeOf 返回对象构造函数的原型
// Objects with no prototype (e.g., `Object.create( null )`) are plain
// 上一行是坐着的注释,即为通过Object.create( null ) 方法,可以创建一个没有原型的对象null
// 所以如果proto 为false,表示对象没有原型,只会是null,而null也是一个纯粹的对象,所以返回真
if (!proto) {
  return true;
}

Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
// Objects with prototype are plain iff they were constructed by a global Object function
// hasOwn之前被定义为,hasOwn = {}.hasOwnProperty 
// hasOwn.call( proto, "constructor" ) 传入对象构造函数的原型,判断原型上面有没有constructor属性(而不是在原型链的其他位置),因为constructor属性只会出现在Object函数的原型上,其他函数原型的constructor属性,都是从Object原型上继承来的
// 所以有constructor属性表示是通过Object对象得到的,但是还不能确定为是Object的实例,或是字面量方式得到。最后保存proto.constructor 即是传入对象的构造函数


return typeof Ctor === "function" && fnToString.call(Ctor) === ObjectFunctionString;
// fnToString被定义为hasOwn.toString 即是{}.hasOwnProperty.toString.call(Ctor);
// ObjectFunctionString定义为fnToString.call( Object ) 即是{}.hasOwnProperty.toString.call(Object);
// 所以 typeof Ctor = 'fuction" 为函数就是需要判断对象的原型是函数类型,{}.hasOwnProperty目的是得到一个函数,一个函数的toString方法会以字符串的格式显示函数
// 如果两个字符串相等,表示两个函数相等。也就表示传入对象的构造函数就是Object,所以他是一个纯净的对象。

}

至此就是全文的所有内容,有疑问尽情留言,一定回复。


supreme
2.3k 声望242 粉丝