前言
首先广为所知的
“万物皆对象”是错误
的。
简单基本类型(string,number,boolean,null,undefined
)不是对象。
复杂基本类型(object
)才是对象;但是object有九个内置对象(String ,Number, Boolean,Object,Function, Array,Date, RegExp,Error
)
举个例子:
var num1 = 123;
typeof num1; // "Number"
num1 instanceof Number; // false
var num2 = new Number(123);
typeof num2; // "object"
num2 instanceof Number; // true
其实num1这种字面量形式,获取长度,访问值其实是不生效的。但是js引擎自动帮我们转成Number这种形式了。如num1.length
。
我们这里简单介绍了,不做深入探讨,毕竟js引擎帮我们做了,记住一句话,Object是所有对象的基类
。
接下来用最通俗易懂的方式给大家解释各种学术名称的原理:
一、__proto__
(指针)
Firefox和Chrome 提供__proto__
访问器,ECMA标准是[[Prototype]]
,它是对象内置属性无法访问,可以通过Object.getPrototypeOf()标准方法访问该属性。
从最简单代码开始:
var obj = {};
console.log(obj);
打印的样子:
只要是对象都有
__proto__
属性,最底层的constructor
是Object
,也印证了我那句Object是所有对象的基类
。
二、prototype
(原型)
定义
其中每个函数对象都有一个prototype 属性,这个属性指向函数的原型对象。
与__proto__
区别:
1:每个对象都有
__proto__
属性,但只有函数对象才有prototype
属性。
2:__proto__
指向的是当前对象的原型对象,而prototype
指向的,是以当前函数作为构造函数构造出来的对象的原型对象。
function Foo() {
var name = '小米';
}
Foo.prototype.sayHello = function() {
console.log('hello 靓仔');
};
console.log(Foo.prototype);
大家可以看到
Foo.prototype
有了sayHello
的函数,自然可以调用。这里有两个问题?
-
sayHello
方法想获取Foo
函数中的name
怎么办? - 图中
constructor的prototype
里面也有sayHello
函数是怎么回事?
2.1 解答问题1
这就是我们为何使用new
进行构造函数的原因,因为它有共用属性,函数共使用,还能继承。
function Foo() {
this.name = '小明';
}
Foo.prototype.sayHello = function() {
console.log('hello' + this.name);
};
// var obj = {};
// obj.__proto__ = Foo.prototype;
// Foo.call(obj);
// var f = obj;
var f = new Foo();
console.log(f.sayHello());
这里解释两件事情:(大厂面试必问)
-
new是什么
,就是我注释掉的代码。 -
为何用prototype创建函数sayHello
,为了避免每一次new Foo()就创建一次sayHello,占用内存,消耗性能。
2.2 解答问题2
直接引出constructor。
三、constructor
(构造函数)
function Foo() {
// ...
}
console.log(Foo.prototype.constructor === Foo); // true
从中可以看出constructor
是函数Foo的prototype内置属性
,指向函数本身。就是这么简单。
继续看代码:
function Foo() {
// ...
}
var a = new Foo();
console.log(a.constructor === Foo); // true
是不是说明构造函数a的constructor指向Foo,答案是否定的。
继续改代码
function Foo() {
// ...
}
Foo.prototype.constructor = {}
var a = new Foo();
console.log(a.constructor === Foo); // false
原因很简单,就是Foo.prototype的constructor被我们设置为{}了,也就是说a.constructor是{}了,说明这个constructor不安全,在某些情况,我们得让它恢复,所以你会看到如下代码:
function Person() {}
Person.prototype = {
constructor: Person, //这里指向Person,就不会丢失了
name: '欣雨',
age: 19,
sayName: function() {
console.log(this.name + '年龄' + this.age);
},
};
// console.log(Person.prototype);
var p = new Person();
p.sayName(); // 欣雨年龄19
constructor
介绍完了,我们来解释一下2.2的问题吧,上述注释代码的console.log(Person.prototype)
打开,打印一下看看:
只要你通过原型prototype
创建属性和方法时,那么Person.prototype
的constructor
下就会有prototype
属性包含你创建的。(这里不是很重要,知道就好)
就是我在介绍2prototype
(原型)所说:__proto__
指向的是当前对象的原型对象,而prototype
指向的,是以当前函数作为构造函数构造出来的对象的原型对象。
四、原型链
定义:
原型链就是通过proto属性从当前对象开始查找它的原型对象(通过prototype生成的),一直查找到null为止。
function Person() {}
Person.prototype = {
constructor: Person, //这里指向Person,就不会丢失了
name: '欣雨',
age: 19,
sayName: function() {
console.log(this.name + '年龄' + this.age);
},
};
var p = new Person();
console.log(p);
打印的p下面就是空对象,而需要的属性在proto下面。
类型:
默认的原型链结构就是:当前对象 -> 构造函数.prototype -> Object.prototype -> null
继承改变默认原型链结构,其实通过_proto查找即可。
五、函数的构造函数Function
普通函数:
function foo(num) {
console.log(num);
}
foo(2); // 2
构造函数:
var foo = new Function('num', 'console.log( num )');
foo(2); // 2
好处就是:
Function是使用字符串构建函数,那么就可以在程序运行过程中构建函数。
以前的函数必须一开始就写好,再经过预解析,一步一步的运行。
写一些底层代码,为了性能可以使用。
六 instanceof 原理
A instanceof B
的原理?(面试常考题)
查看对象B的prototype指向的对象是否在对象A的[[prototype]]链上
即
A.__proto__ === B.prototype