目录:
1、原型&原型链注意
2、js中继承的六种方式
3、new关键字
4、instance of
5、Object.create
6、复制实现继承:浅拷贝、深拷贝
7、继承理解DOM
8、创造对象的四种方式
1、原型&原型链注意
提到原型说对象(目录8),每一个对象都有一个__proto__隐式原型属性,指向构造改对象的构造函数的原型。
而函数比较特殊,它具备prototype这样一个指针属性,指向包含所有实例共享的属性和方法的对象,称为原型对象;原型对象有一个constructor属性,指向该函数。
因此具备:Object.\_\_proto\_\_ === Function.prototype;Function.\_\_proto\_\_ === Function.prototype。Object是对象的构造函数,对象的构造函数均指向“Function.prototype”,因为Function本身是一个构造函数,为不产生混乱,就将两个联系到一起。
几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。
function Person(){}
let p1 === new Person()
let obj === {}
p1.__proto__ === Person.prototype
Person.__proto__ === Function.prototype
Person.prototype
Person.prototype.__proto__ === Object.prototype
Person.prototype.constructor === Person
Function.__proto__ === Function.prototype
Function.prototype
Function.prototype.__proto__ === Object.prototype
Function.prototype.constructor === Function
obj.__proto__ === Object.prototype
Object.__proto__ === Function.prototype
Object.prototype
Object.prototype.__proto__ === null
Object.prototype.constructor === Object
JavaScript 并没有其他基于类的语言所定义的“方法”。在 JavaScript 里,任何函数都可以添加到对象上作为对象的属性。函数的继承与其他的属性继承没有差别,包括上面的“属性遮蔽”(这种情况相当于其他语言的方法重写)。
当继承的函数被调用时,this 指向的是当前继承的对象,而不是继承的函数所在的原型对象。
2、js中继承的六种方式
1、原型链继承
特点:通过原型链继承,引用类型会因为引用的特点变成公用属性;且无法在继承的时候传递参数;可以使用instanceof、isPropertyOf检测出来
function Company(){
this.address = '深圳总部';
this.projects = ['games']
}
Company.prototype.showAddress = function(){
console.log("address为" + this.address);
}
function SonCompany(address){
this.address = address;
}
SonCompany.prototype = new Company();
// 将SonCompany.prototype.__proto__由指向Object.prototype转为指向Company.prototype,继而SonCompany.prototype.__proto__.__proto__ === Object.prototype
var sonC1 = new SonCompany('广州分部');
var sonC2 = new SonCompany('上海分部');
sonC1.projects.push('movies');
console.log(sonC2.projects); // ["games", "movies"]
console.log(sonC1);
/*
SonCompany
address: "广州分部"
__proto__: Company
address: "深圳总部"
projects: (2) ["games", "movies"]
__proto__:
showAddress: ƒ ()
constructor: ƒ Company()
__proto__: Object
而没有使用prototype继承的时候,sonC1为:
SonCompany {address: "广州分部"}
address: "广州分部"
__proto__:
constructor: ƒ SonCompany(address)
__proto__: Object
*/
/*
此时一般还是需要将sonCompany的constructor换回来的,即应该在SonCompany.prototype = new Company();之后操作SonCompany.prototype.constructor = SonCompany;
这样操作之后,sonC1打印出来如下:
SonCompany {address: "广州分部"}
address: "广州分部"
__proto__: Company
address: "深圳总部"
constructor: ƒ SonCompany(address)
projects: ["games"]
__proto__:
showAddress: ƒ ()
constructor: ƒ Company()
__proto__: Object
*/
2、构造函数继承
思想:在子类型构造函数的内部call、apply调用父类构造函数
特点:解决了原型链“引用类型私有属性变公有”、“不可传递参数”两个问题;
但是没有使用new生成一个父类实例的时候,方法写在prototype上无法使用,所有方法需要在函数中定义。这样的话就导致无法复用,每次构建实例的时候都会在每一个实例中保留方法函数,造成内容浪费,无法同步更新。写在prototype上,就只有一份,更新可以同步更新。
function Company (address) {
this.address = address;
this.projects = ['games'];
this.getAddress = function () {
return this.address;
}
}
Company.prototype.showAddress = function(){ console.log(this.address) }
// 子类
function SonCompany (address) {
// 继承了Company,同时还传递了参数
Company.call(this, address);
// 实例属性
this.staff = 20;
}
var com1 = new Company('深圳总部');
com1.getAddress(); // 深圳总部
com1.showAddress(); // 深圳总部
var sonC1 = new SonCompany('广州分部');
sonC1.getAddress(); // 广州分部
sonC1.projects.push('movies');
var sonC2 = new SonCompany('上海分部');
console.log(sonC2.projects) // ["games"]
sonC2.getAddress(); // 上海分部
sonC2.showAddress(); // Uncaught TypeError: sonC2.showAddress is not a function
/*
sonC1的打印: getAddress成为sonC1的一个函数
SonCompany
address: "sonC1Address"
staff: 20
getAddress: ƒ ()
projects: (2) ["games", "movies"]
__proto__:
constructor: ƒ SonCompany(address)
__proto__: Object
com1的打印:可以查到showName()
Company
address: "SuperAddress"
getAddress: ƒ ()
projects: ["games"]
__proto__:
showAddress: ƒ ()
constructor: ƒ Company(address)
__proto__: Object
*/
3、组合继承(1、2组合)
思想:将上述两种结合,发挥各自优势;是目前广泛使用的继承方式;但是对父类构造函数调用了两次;
function Company (address, staff) {
this.address = address;
this.staffs = staff;
this.projects = ['games'];
this.getAddress = function () {
return this.address;
}
}
Company.prototype.showAddress = function(){ console.log(this.address) }
// 子类
function SonCompany (address, staff) {
// 继承了Company,同时还传递了参数
Company.call(this, address);
// 实例属性
this.staffs = staff;
}
SonCompany.prototype = new Company();
// 将SonCompany.prototype.__proto__由指向Object.prototype转为指向Company.prototype,继而SonCompany.prototype.__proto__.__proto__ === Object.prototype
SonCompany.prototype.constructor = SonCompany;
// 将构造函数转回来,因为上一步操作之后:SonCompany.prototype.constructor === Company;转之后即可在SonCompany的prototype上正常操作
SonCompany.prototype.getStaff = function(){
return this.staffs
}
var com1 = new Company('深圳总部', 100);
com1.getAddress(); // 深圳总部
com1.showAddress(); // 深圳总部
var sonC1 = new SonCompany('广州分部', 20);
sonC1.getAddress(); // 广州分部
sonC1.projects.push('movies'); // ['games', 'movies']
var sonC2 = new SonCompany('上海分部', 40);
console.log(sonC2.projects) // ['games']
sonC2.getAddress(); // 上海分部
sonC2.showAddress(); // 上海分部
sonC2.getStaff(); // 40
/*
SonCompany
address: "广州分部"
getAddress: ƒ ()
projects: ["games"]
staffs: 20
__proto__: Company
address: undefined
constructor: ƒ SonCompany(address, staff)
getAddress: ƒ ()
getStaff: ƒ ()
projects: ["games"]
staffs: undefined
__proto__:
showAddress: ƒ ()
constructor: ƒ Company(address, staff)
__proto__: Object
*/
4、原型式继承
思想:不适用严格意义上的构造函数,借助原型基于已有对象创建新对象,同时不必创建自定义类型;
一般是仅仅模拟一个对象的时候使用; es5新增了一个方法Object.create(prototype, descripter)接收两个参数:
prototype(必选),用作新对象的原型对象
descripter(可选),为新对象定义额外属性的对象
在传入一个参数的情况下,Object.create()与前面写的object()方法的行为相同。
使用一个函数对传入其中的对象执行一次浅复制,复制到的副本还需要进一步改造;引用类型属性还是会被共享
function objectCopy(o){
function F(){} // 创建一个临时构造函数
F.prototype = o; // 将传入的对象作为构造函数的原型
return new F(); // 返回临时类型的新实例
}
var company = {
address: "深圳分部",
projects: ['games'],
getAddress: function(){
console.log(this.address);
}
}
var c1 = objectCopy(company);
var c2 = objectCopy(company);
c1.projects.push('movies');
console.log(c2.projects); // ["games", "movies"]
c1.getAddress(); // "深圳分部"
5、寄生式继承
思想:创建一个仅用于封装继承过程的函数,改函数内部以某种形式来做增强对象,最后返回对象。
function objectCopy(o){
function F(){}
F.prototype = o;
return new F();
}
function createAnother (original) {
var clone = objectCopy(original) // 通过调用函数创建一个新对象
clone.sayHi = function () { // 以某种方式来增强这个对象
alert("hi")
}
return clone // 返回这个对象
}
var company = {
address: "深圳分部",
projects: ['games'],
getAddress: function(){
console.log(this.address);
}
}
var c1 = createAnother(company);
var c2 = createAnother(company);
c1.projects.push('movies');
console.log(c2.projects); // ["games", "movies"]
c1.sayHi(); // hi
/*
c1打印
F {sayHi: ƒ}
sayHi: ƒ ()
__proto__:
address: "深圳分部"
getAddress: ƒ ()
projects: (2) ["games", "movies"]
__proto__: Object
*/
6、组合寄生式继承(3、5)
思想:使用构造函数继承属性,原型链混成形式继承方法;只调用一次构造函数
被认为最理想的继承方式;
function objectCopy(o){
function F(){}
F.prototype = o;
return new F();
}
function inheritPrototype(s, f){
var prototype = objectCopy(f.prototype); // 创建对象
prototype.constructor = s; // 增强对象
s.prototype = prototype; // 指定对象
}
function Company (address, staff) {
this.address = address;
this.staffs = staff;
this.projects = ['games'];
this.getAddress = function () {
return this.address;
}
}
Company.prototype.showAddress = function(){ console.log(this.address) }
// 子类
function SonCompany (address, staff) {
// 继承了Company,同时还传递了参数
Company.call(this, address);
// 实例属性
this.staffs = staff;
}
inheritPrototype(SonCompany, Company); // 继承
SonCompany.prototype.getStaff = function(){
return this.staffs
}
var com1 = new Company('深圳总部', 100);
com1.getAddress();
com1.showAddress();
var sonC1 = new SonCompany('广州分部', 20);
sonC1.getAddress();
sonC1.projects.push('movies');
var sonC2 = new SonCompany('上海分部', 40);
console.log(sonC2.projects)
sonC2.getAddress();
sonC2.showAddress();
sonC2.getStaff();
/*
sonC1打印如下
SonCompany
address: "广州分部"
getAddress: ƒ ()
projects: (2) ["games", "movies"]
staffs: 20
__proto__: Company
constructor: ƒ SonCompany(address, staff)
getStaff: ƒ ()
__proto__:
showAddress: ƒ ()
constructor: ƒ Company(address, staff)
__proto__: Object
*/
7、es6的class实现继承
底层使用的是组合寄生式继承;写法简单易懂,如下:
class Company{
constructor(address){
this.address = address;
this.projects = ['games']
}
getAddress(){
console.log(this.address)
}
}
class SonCompany extends Company{
constructor(address, staffs){
super(address) // 继承父类实例属性和prototype上的方法
this.staffs = staffs
}
getStaffs(){
console.log(this.staffs)
}
}
var sonC1 = new SonCompany('广州分部', 40);
var sonC2 = new SonCompany('上海分部', 20);
sonC1.getAddress(); // 广州分部
sonC1.getStaffs(); // 40
sonC1.projects.push('movies');
sonC2.getAddress(); // 上海分部
sonC2.getStaffs(); // 20
sonC2.projects; // ['games']
3、new关键字
new做了四件事er:
1、创建一个新对象
2、将__proto__属性指向构造器函数的prototype
3、将this指向新创建对象,使用新创建的对象执行构造函数
4、返回新建对象
function _new(fn){
return function(){
let obj = { // 创建新obj
__proto__: fn.prototype // 原型链 - 由指向Object.prototype转为fn.prototype
}
// 执行构造函数,传递参数
fn.call(obj, ...arguments); // 构造函数改变参数的this指向obj,apply也是OK的,不过参数问题这样方便;所以根据这个,fn中的this指的是new的对象
// 返回obj
return obj
}
}
function A(a){
this.a = a
}
var obj = _new(A)('aa'); // {a: 'aa}
// obj.__proto__ === A.prototype true
// obj.constructor === A.constructor true
// obj2.__proto__.__proto__ === Function.prototype false
// obj.__proto__.__proto__ === Object.prototype true
// A.__proto__ === Function.prototype
函数科里化: fun里return一个fun
4、instance of
会根据原型链一直找下去
function instance_of(L, R) {//L 表示左表达式,R 表示右表达式
var O = R.prototype;// 取 R 的显示原型
L = L.__proto__;// 取 L 的隐式原型
while (true) {
if (L === null) //L是Object.prototype
return false;
if (O === L)// 这里重点:当 O 严格等于 L 时,返回 true
return true;
L = L.__proto__;
}
}
对比之:typeof
typeof是判断基本类型的,基本类型有7种: Undefined, null, Boolean, Number, String, bigint, symbol(es6),还能判断出function 然后其他都是object(null是object)
判断类型:
function myTypeof(obj){
return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
}
5、Object.create
判断第一个参数是否符合继承的特点,即是否为具有__proto__属性,是否为对象,函数也是对象;
判断第二个参数是否符合条件,即不为undefined;将对象赋值给第一个参数
var _create = function (proto, propertyObject = undefined) {
// 先判断proto是否符合条件
if (typeof proto !== 'object' && typeof proto !== 'function') {
throw new TypeError('Object prototype may only be an Object: ' + proto);
}
/*
else if (proto === null) {
throw new Error("This browser's implementation of Object.create is a shim and doesn't support 'null' as the first argument.");
}
*/
if (propertyObject === null) {
throw 'TypeError'
} else {
function Fn() { }
Fn.prototype = proto
const obj = new Fn()
if (propertyObject !== undefined) {
Object.defineProperties(obj, propertyObject)
}
if (proto === null) {
// 创建一个没有原型对象的对象,Object.create(null)
obj.__proto__ = null
}
return obj
}
}
var proto = {name:'allen'};
var p = _create(proto,{age:{value:21}});
p.hasOwnProperty('age');//true
p.hasOwnProperty('name');//false
Object.create(null) vs {}
前者就是一个单纯的{},不具备properties的属性;{} 相当于 Object.create(Object.ptototype),存在n.toString(),且可以用for…in循环
6、复制实现继承:浅拷贝、深拷贝
浅拷贝可以使用Object.assign()
或展开运算符...
,例如:
// 浅拷贝
let shallowCopy = Object.assign({}, originalObject);
// 或
let shallowCopy = {...originalObject};
深拷贝可以使用JSON.parse()
和JSON.stringify()
,但它无法拷贝函数等非JSON数据类型。更完整的深拷贝可以使用递归实现,例如:
// 深拷贝
function deepCopy(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
let copy = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepCopy(obj[key]);
}
}
return copy;
}
这样可以创建原对象的完整拷贝,包括对象内部的所有嵌套对象。
7、继承理解DOM
DOM上是具备原型链的;
文档对象模型 (DOM) 将 web 页面与到脚本或编程语言连接起来。通常是指 JavaScript,但将 HTML、SVG 或 XML 文档建模为对象并不是 JavaScript 语言的一部分。DOM模型用一个逻辑树来表示一个文档,树的每个分支的终点都是一个节点(node),每个节点都包含着对象(objects)。DOM的方法(methods)让你可以用特定方式操作这个树,用这些方法你可以改变文档的结构、样式或者内容。节点可以关联上事件处理器,一旦某一事件被触发了,那些事件处理器就会被执行。
8、创建对象的4种方式
来看看使用不同方法来创建对象:
1.语法结构
var o = {a: 1};
// o ---> Object.prototype ---> null ; o 这个对象继承了 Object.prototype 上面的所有属性
var a = [1, 2, 3]
// a ---> Array.prototype ---> Object.prototype ---> null;数组都继承于 Array.prototype
function f() { return true}
// f ---> Function.prototype ---> Object.prototype ---> null;函数都继承于 Function.prototype;(Function.prototype 中包含 call, bind等方法)
2.构造器(即普通函数),即使用new操作符
function testF(a){ this.a = a }
testF.prototype = {
funA: function(){
console.log(this.a)
}
}
var t = new testF('aaa');
// t.__proto__ === testF.prototype
3.使用Object.create创建对象(ES5方法),新对象的原型就是调用create时传入的第一个参数
var a = {a: 1};
// a ---> Object.prototype ---> null
var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (继承而来)
var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null
var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined, 因为d没有继承Object.prototype
手写:
function createObject(proto) {
function F() {}
F.prototype = proto;
return new F();
}
// 使用方法
var newObj = createObject(existingObject);
4.使用class关键字(ES6引入的语法糖,虽然有class,extends的概念,单js还是基于原型)
class A { static geta(){ console.log( 'A' ); } }
class B extends A { static getb(){ console.log( 'B' ); } }
B.geta(); // A
B.getb(); // B
要检查对象是否具有自己定义的属性,而不是其原型链上的某个属性,则必须使用所有对象从 Object.prototype 继承的 hasOwnProperty 方法。(在原型链上查找属性比较耗时,对性能有副作用,这在性能要求苛刻的情况下很重要。另外,试图访问不存在的属性时会遍历整个原型链。)遍历对象的属性时,原型链上的每个可枚举属性都会被枚举出来。Object.keys()也是跟 hasOwnProperty 一样的效果;(注意:检查属性是否为 undefined 是不能够检查其是否存在的。该属性可能已存在,但其值恰好被设置成了 undefined。)
原型链是可扩展的,但是不建议随意扩展【原生对象】的原型,这样会影响到很多继承原生对象的使用;
扩展原型链大概有一下4种:new、Object.create、Object.setPrototypeOf、proto 越靠后越要求版本等,还是建议new