再谈构造函数、原型、原型链之间的关系
前言
构造函数、原型、原型链作为ES5的内容,已经是老生常谈的问题了。首先说说为什么要再次拿起这个话题去说呢?这几天有空我会看一些源码,这些源码的底层实现考虑到兼容性还是来源于ES5
,很多方法的封装以及实现(不管是按照模块封装还是统一实现)都是面向对象的思想,而且webpack
以及rollup
打包之后解析出来的代码利用@babel/core
和@babel/preset-env
转化之后也都是ES5的代码,所以有想再次谈起这个话题,回顾回顾旧知识,温故而知新。
构造函数
什么是构造函数?构造函数就是使用关键字new
创建对象时调用的函数。构造函数的属性可分为两种:1.实例上的属性 2.公用属性
//实例上的属性
function Animal(){
this.name=name;
this.age=18;
}
原型
原型是每个构造函数都有的,在JS规定,每一个构造函数都有一个 prototype
属性,指向另一个对象,注意这个prototype就是一个对象,这个对象的所有属性和方法都会被构造函数所拥有。原型的作用是共享方法,一般情况下,我们的公共属性定义在构造函数里面,公共的方法放到原型对象上。
Animal.prototype.address={location:"野外"};
实例
使用关键字new调用构造函数创建实例
let a1 = new Animal("猴子");
let a2 = new Animal("小鸡");
原型链
在JavaScript中万物都是对象,对象和对象之间也有关系,并不是孤立存在的。对象之间的继承关系,在JavaScript中是通过prototype对象指向父类对象,直到指向Object对象为止,这样就形成了一个原型指向的链条,专业术语称之为原型链[1]。
举例说明:person → Person → Object ,普通人继承人类,人类继承对象类。
当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如果找到则直接使用。如果没有则去原型的原型中寻找,直到找到Object对象的原型,Object对象的原型没有原型,如果在Object原型中依然没有找到,则返回null。每个实例都有__proto__ 指向所属类的原型
下面看几个题,思考一下结果(答案在文末):
console.log(a1.age === a2.age);
console.log(a1.address === a2.address);
console.log(a1.__proto__ === a2.__proto__);
console.log(a1.constructor === Animal);
console.log(Animal.__proto__ === Function.prototype);
console.log(a1.__proto__.__proto__ === Object.prototype);
console.log(Object.prototype.__proto__);
继承
首先定义一个Animal
的父类构造函数和一个子类Tiger
构造函数.
function Animal(name){
this.name = name;
this.eat = "吃肉";
}
Animal.prototype.address={location:"山里"};
function Tiger(name){
this.name = name;
this.age = 10;
}
Tiger.prototype.say = function(){
console.log("说话");
}
补充知识:call,apply,bind的可以改变this的指向。call和apply会立刻执行,bind调用函数时才会执行。call和bind第一个参数传入的是对象或者null或者不传,后面参数是字符串。apply第一个参数对象或者null或者不传,后面的参数是数组。为什么vue的
methods
方法中的this总是指向Vue实例vm呢? 就是因为使用bind方法把this绑死了
。
function polyfillBind (fn, ctx) {
function boundFn (a) {
var l = arguments.length;
return l
? l > 1
? fn.apply(ctx, arguments)
: fn.call(ctx, a)
: fn.call(ctx)
}
boundFn._length = fn.length;
return boundFn
}
function nativeBind (fn, ctx) {
return fn.bind(ctx)
}
var bind = Function.prototype.bind
? nativeBind
: polyfillBind;
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm);
言归正传,在子类Tiger
构造函数中使用call方法改变
this`的指向实现父类实例上的属性继承。
Animal.call(this)
let tigger = new Tiger();
console.log(tigger.eat); //吃肉
如果要继承父类的公共属性或者方法可以使用下面的方法:Object.setPrototypeOf[2]
Tiger.prototype.__proto__ = Animal.prototype 等价于下面的方法
Object.setPrototypeOf(Tiger.prototype,Animal.prototype) //es7
谈到继承我们再谈谈ES5中Object.create()
方法,在面试中有可能被问到哦!Object.create()
和直接new Object()
的区别 我们先看一个例子
let obj = Object.create({a:1})
console.log(obj.a) //1
console.log(obj.__proto__.a) //1
let obj = new Object({a:1})
console.log(obj.a) //1
console.log(obj.__proto__.a) //undefined
显然:Object.create()
是将对象继承到原型链上,可以通过原型链访问,new Object()
不可以在原型链上访问。
思考一个问题:继承父类的公共属性或者方法能不能使用Object.create()
Tiger.prototype= Object.create(Animal.prototype)
问题又来了tigger.constructor
指向了父级Animal
,解决方法如下
Tiger.prototype= Object.create(Animal.prototype,{constructor:{value:Tiger}})
原理如下:
function create(parentPrototype){
let Fn = function(){};
Fn.prototype = parentPrototype; //当前函数的原型 只有父类的原型
let fn = new Fn();
fn.constructor = Tiger
return fn //当前的实例可以拿到 Animal.prototype
}
Tiger.prototype = create(Animal.prototype)
为什么不直接使用Tiger.prototype = new Animal()
直接把say
方法都覆盖了,不能这么用
总结:
在使用继承的时候可以用call + Object.create()
或者call + setPrototypeOf
方法
思考答案
console.log(a1.arr === a2.arr); //true
console.log(a1.address === a2.address);//true
console.log(a1.__proto__ === a2.__proto__);//true
console.log(a1.constructor === Animal);//true
console.log(Animal.__proto__ === Function.prototype);//true
console.log(a1.__proto__.__proto__ === Object.prototype);//true
console.log(Object.prototype.__proto__);//null
参考资料
[1]原型与原型链详解: https://www.jianshu.com/p/ddaa5179cda6
[2]Object.setPrototypeOf: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf
- 建立可扩展的silverlight 应用框架 step-6
- .NET4.0下网站应用程序用UrlRewriter.dll重写无后缀路径 (在IIS7.5中的配置方法)
- 苹果为你的心跳开发一个读者
- 建立可扩展的silverlight 应用框架 step-5
- 安卓 iOS 版双双更新!还带来一大波小游戏
- 建立可扩展的silverlight 应用框架 step-4
- 全自动驾驶,吹牛容易实现难!有90%的人都不了解这些细节
- .NET4.0下web应用程序用UrlRewriter.dll重写无后缀路径
- Silverlight中摄像头的运用—part2
- 区块链小白投资入门操作指南(上)
- 《我的WCF之旅》博文系列汇总
- 网站出现“Service Unavailable”提示该如何解决
- Silverlight 4 中摄像头的运用—part1
- Silverlight 4 中摄像头的运用—part1
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- C#自定义控件的创建
- Oracle参数解析(processor_group_name)
- [Oracle故障处理]记一次PX msg pool 4031错误的处理
- WPF的布局-Grid(表格布局)
- C# 通过遍历设置控件属性
- C# 软件开机启动
- Java之映射
- C# 武汉肺炎全国疫情实时信息图
- brew报错:`initialize': Version value must be a string; got a NilClass () (TypeError)
- gitlab和gitlab项目迁移
- Qt5+VS2017点击按钮打开新窗口
- C#连接MySQL数据库
- 了不起的 IoC 与 DI
- QT之UDP通信
- [Oracle 日常管理]bbed的安装