5条原型规则
原型规则是学习原型链的基础。
- 所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展属性(除了"null"以外)
- 所有的引用类型(数组、对象、函数),都有一个
__proto__属性,属性只是一个普通对象。__proto__是隐式原型。 - 所有的函数,都有一个
prototype属性,属性值也是一个普通的对象。prototype是显式原型。 - 所有的引用类型(数组、对象、函数),
__proto__属性值指向它的构造函数的prototype属性值。 - 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的
__proto__(即它的构造函数的prototype)中寻找。
var obj = {}; obj.a = 100;
var arr = []; arr.a = 100;
function fn () {}
fn.a = 100;
console.log(obj.__proto__)
console.log(arr.__proto__)
console.log(fn.__proto__)
//函数都有prototype属性
console.log(fn.prototype)
console.log(obj.__proto__ === Object.prototype)
- this 指向
// 构造函数
function Foo(name, age) {
this.name = name
}
Foo.prototype.alertName = function () {
console.log(this.name)
}
// 创建示例
var f = new Foo('zhangsan')
f.printName = function () {
console.log(this.name)
}
// 测试
f.printName();
f.alertName();
- hasOwnProperty 前面alertName也是构造函数Foo的属性,但是alertName是在原型链上的,所以遍历不到。
var item
for (item in f) {
// 高级浏览器已经在for in中屏蔽了来自原型的属性
// 但是建议加上这个判断属性,保持程序的健壮性
if(f.hasOwnProperty(item)){
console.log(item)
}
}
原型链
// 构造函数
function Foo(name, age) {
this.name = name
}
Foo.prototype.alertName = function () {
console.log(this.name)
}
// 创建示例
var f = new Foo('zhangsan')
f.printName = function () {
console.log(this.name)
}
// 测试
f.printName();
f.alertName();
f.toString(); // 要去f.__protp__.__proptp__中查找
f.printName()获取的是f的本身一个属性。
f.alertName()是f的隐式原型也就是f的构造函数Foo显式原型里面定义的一个属性。
f.toString()这个我们看下第5条规则,当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的__proto__(即它的构造函数的prototype)中寻找。
f中自身没这个属性=》去f的隐式原型也就是f的构造函数Foo的显式原型(Foo.prototype)去找=》Foo.prototype中没有toString这个属性=》Foo的显示原型(Foo.prototype)它也是个对象,寻找toString的话,没有找到也要它的隐式原型上去找。所有的函数,都有一个prototype属性,属性值也是一个普通的对象。prototype是显式原型。
普通对象的构造函数是Object。也就是说构造函数Foo的prototype属性(Foo.prototype)是一个普通的对象,其构造函数是Object。
也就是说Foo.prototype找toString,去其隐式原型也就是Object的显式原型中去找。=》Object有toString的属性
instanceof
- 用于判断引用类型属于哪个构造函数的方法。
- f instanceof Foo 的判断逻辑:f的__proto__一层一层往上,能否对应到Foo.prototype
- 再试着判断f instanceof Object 也是true
面试题目解析
如何准确判断一个变量是数组类型
var arr = []
arr instanceof Array // true
typeof arr // object // typeof是无法判断是否是数组的
写一个原型链继承的例子
原型链继承思想:利用原型让一个引用类型继承另一个引用类型的属性和方法
- 执行hashiqi.eat()会从Dog的显式原型中(Dog.prototype)去取,Dog的显式原型就是new Animal(),new Animal()是一个已经有了eat属性的对象。
// 动物
function Animal() {
this.eat = function() {
console.log('animal eat')
}
}
// 狗
function Dog() {
this.bark = function() {
console.log('dog batk')
}
}
Dog.prototype = new Animal()
// 哈士奇
hashiqi = new Dog()
hashiqi.eat()
- 写一个封装DOM查询的例子
function Elem(id) {
this.elem = document.getElementById(id)
}
Elem.prototype.html = function(val) {
let elem = this.elem;
if(val) {
elem.innerHTML = val;
return this // 链式操作
} else{
return elem.innerHTML
}
}
Elem.prototype.on = function(type, fn) {
let elem = this.elem
elem.addEventListener(type, fn)
}
let div1 = new Elem('div1')
console.log(div1)
div1.html('<p>Hello, Erina</p>')
div1.on('click',function () {alert('clicked'})
html方法里,return this可以做链式操作,具体怎么写,看下面代码演示。
// 链式操作
div1.html('<p>Hello, Erina</p>').on('click',function () { alert('clicked') })
如果on方法里return this,后面还可以继续做链式操作
Elem.prototype.on = function(type, fn) {
let elem = this.elem
elem.addEventListener(type, fn)
return this
}
div1.html('<p>Hello, Erina!!!</p>').on('click',function () { alert('clicked') }).html('<p>Hello, Javescript!!!</p>')
描述new一个对象的过程
优秀解答思路参考这个小哥的文章
- 创建一个新对象obj
- 把obj的__proto__指向构造函数.prototype 实现继承
- 执行构造函数,传递参数,改变this指向
- 最后把obj返回
伪代码:new Person("John") = {
var obj = {};
obj.__proto__ = Person.prototype;
var result = Person.call(obj,"John");
return typeof result === 'object' ? result : obj; // 如果无返回值或者返回一个非对象值,则将obj返回作为新对象
}
- 我们来看下new 过程中的
return this
function Foo(name, age) {
this.name = name;
this.age = age;
}
var f = new Foo('zhangsan', 20)
function Foo(name, age) {
this.name = name;
this.age = age;
return this
}
var f = new Foo('zhangsan', 20)
return this写和不写的结果是一样的,不写会默认添加上。如果不返回
this,而是返回{x:10},会是什么效果呢?
function Foo(name, age) {
this.name = name;
this.age = age;
return {x:10}
}
var f = new Foo('zhangsan', 20) // {x:10}
fn是函数,Object也是函数,都有显示原型属性。
构造函数Object
在JvaScript中,万物皆Object(除了基本类型),几乎所有的对象都是Object类型的实例,它们都会从Objet.prototype继承属性和方法。
Object 构造函数为给定值创建一个对象包装器。Object构造函数,会根据给定的参数创建对象,具体有以下情况:
- 如果给定值是 null 或 undefined,将会创建并返回一个空对象
- 如果传进去的是一个基本类型的值,则会构造其包装类型的对象
- 如果传进去的是引用类型的值,仍然会返回这个值,经他们复制的变量保有和源对象相同的引用地址
当以非构造函数形式被调用时,Object 的行为等同于 new Object()。
任何一个实例对象都有.valueOf这个方法,实际上是引用的Object.prototype这个原型对象里面的方法。
typeof(Object)返回的是"function",说明Object应该是一个构造函数,Object.prototype是对象。
"函数"是Object
var fn = function() {}
fn.__proto__ === Function.prototype // true
Function instanceof Object // true
fn instanceof Object // true
people.__proto__ === Object.prototype // false
"类"也是Object
class People {}
People.__proto__ === Function.prototype // true
People instanceof Function // true
People instanceof Object // true
People.__proto__ === Object.prototype // false
People.__proto__.__proto__ === Object.prototype //true
people.__proto__.__proto__.__proto__ // null
es6的class概念class People {}转换成es5的话,就是构造函数Function People () {}。函数也是对象,所以函数Function也有__proto__,就是浏览器给它分配的对象,且满足People.__proto__ === Object.prototype。
People instanceof Object,instanceof工作原理,它基于浏览器提供的__proto__,去递归People的原型链,一直沿着原型链去查找。
第一步People.__proto__去查找Function的prototype,所以People.__proto__ === Function.prototype为true,第二步再__proto__去查找Object.prototype,所以
People.__proto__.__proto__ === Object.prototype为true。当然如果再继续.__proto__,就问null了。
类是一个“函数”。构造函数一般是用来初始化类成员等动态数据的,“类”应该还包含了静态方法和实例方法,这些“方法”不属于“构造函数”的一部分,当然如果你使用动态声明实例方法的写法的话,那这些“方法”就确实属于“构造函数”的一部分了。
function People (name) {
this.name = name; //实例属性
this.getAge = function () {} ; //实例方法
}
People.age = 20; //静态属性
People.getName = function () {} ; //静态方法
People.prototype.getAddress = function() {} ;//原型方法