对象、类与面向对象编程

ECMA-262 将对象定义为一组属性的无序集合。严格来说,这意味着对象就是一组没有特定顺序的值。对象的每个属性或方法都由一个名称来标识,这个名称映射到一个值。正因为如此(以及其他还未 讨论的原因),可以把 ECMAScript 的对象想象成一张散列表,其中的内容就是一组键/值对,值可以是数据或者函数

理解对象

以前创建对象的方法通常是给Object创建一个新的实例,在给他添加属性和方法

javascript 复制代码
let obj = new Object()
obj.name = '张三'
obj.age = 18
obj.say = ()=>{
  consloe.log('hello world')
}

上面的代码我们给obj对象添加了属性和方法,现在我们一般使用对象字面量的方式创建对象

javascript 复制代码
let obj = {
  name:'张三',
  age:18,
  say(){
    consloe.log('hello world')
  }
}

这种方式创建的obj和上面的obj是一样, 它们的属性和方法都一样。

属性的类型

属性分两种:数据属性访问器属性

  1. 数据属性

数据属性包含一个保存数据值的位置。值会从这个位置读取,也会写入到这个位置。数据属性有 4 个特性描述它们的行为。

  • [[Configurable]]:表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特性,以及是否可以把它改为访问器属性。默认为true
  • [[Enumerable]]:表示属性是否可以通过for-in循环。 默认为true
  • [[Writable]]: 表示属性的值是否可以被修改 。默认为true
  • [[Value]]: 包含属性实际的值。默认为undefined

如同上面代码一样将属性显示的添加到对象,这里的4个属性都会被设置为默认值。如果我们想修改属性的默认值,可以通过Object.defineProperty()方法。 这个方法接收 3 个参数: 要给其添加属性的对象、属性的名称和一个描述符对象。最后一个参数,即描述符对象上的属性可以包 含:configurable、enumerable、writable 和 value,跟相关特性的名称一一对应。

注意:严格模式下才会抛出错误,非严格模式调用时无效果

javascript 复制代码
Object.defineProperty(person,'name',{
  configurable:false,
  enumerable:false,
  writable:false,
  value:'张三'
})
//delete person.name // Cannot delete property 'name' of #<Object>
//person.name = '法外狂徒' //Cannot assign to read only property 'name' of object '#<Object>'
for (const key in person) {
  console.log('key',key);// 未执行
}

注意:在使用Object.defineProperty()的时候,如果没有指定 ConfigurableEnumerable writable的值,那么都默认为false

访问器属性

访问器属性不包含数据值。相反,它们包含一个获取(getter)函数和一个设置(setter)函数,不 过这两个函数不是必需的。在读取访问器属性时,会调用获取函数,这个函数的责任就是返回一个有效 的值。在写入访问器属性时,会调用设置函数并传入新值,这个函数必须决定对数据做出什么修改。访问器属性有 4 个特性描述它们的行为。

  • [[Configurable]]:表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特性,以及是否可以把它改为访问器属性。默认为true
  • [[Enumerable]]:表示属性是否可以通过for-in循环。 默认为true
  • [[Get]]: 获取函数,在读取属性时调用。默认值为 undefined。
  • [[Set]]: 设置函数,在写入属性时调用。默认值为 undefined。
javascript 复制代码
let book = {
  year_: 2017,
  edition: 1
}
Object.defineProperty(book, 'year', {
  get() {
    return this.year_
  },
  set(newValue) {
    if (newValue > 2017) {
      this.year_ = newValue
      this.edition += newValue - 2017
    }
  }
})
book.year = 2018
console.log(book.edition) //2

这里的year被定义为一个访问器属性,这是访问器属性的典型使用场景,即设置一个属性 值会导致一些其他变化发生;获取函数和设置函数不一定都要定义。只定义获取函数意味着属性是只读的,尝试修改属性会被忽略。\

定义多个属性

在一个对象上同时定义多个属性的可能性是非常大的。为此,ECMAScript 提供了Object.defineProperties()方法。这个方法可以通过多个描述符一次性定义多个属性。它接收两个参数:要为之添加或修改属性的对象和另一个描述符对象,其属性与要添加或修改的属性一一对应

javascript 复制代码
let book = {}
Object.defineProperties(book, {
  year_: {
    value: 2017
  },
  edition: {
    value: 1
  },
  year: {
    get() {
      return this.year_
    },
    set(newValue) {
      if (newValue > 2017) {
        this.year_ = newValue
        this.edition += newValue - 2017
      }
    }
  }
})

在 book 对象上定义了两个数据属性 year_和 edition,还有一个访问器属性 year。 最终的对象跟上一个例子中的一样。唯一的区别是所有属性都是同时定义的,并且数据属性的 configurable、enumerable 和 writable 特性值都是 false

读取属性的特性

使用 Object.getOwnPropertyDescriptor()方法可以取得指定属性的属性描述符。这个方法接收两个参数:属性所在的对象和要取得其描述符的属性名。返回值是一个对象,对于访问器属性包含configurable、enumerable、get 和 set 属性,对于数据属性包含 configurable、enumerable、 writable 和 value 属性。

javascript 复制代码
let book = {}
Object.defineProperties(book, {
  year_: {
    value: 2017
  },
  edition: {
    value: 1
  },
  year: {
    get: function () {
      return this.year_
    },
    set: function (newValue) {
      if (newValue > 2017) {
        this.year_ = newValue
        this.edition += newValue - 2017
      }
    }
  }
})
let descriptor = Object.getOwnPropertyDescriptor(book, "year_");
console.log(descriptor.value); // 2017
console.log(descriptor.configurable); // false
console.log(typeof descriptor.get); // "undefined"
let descriptor = Object.getOwnPropertyDescriptor(book, "year");
console.log(descriptor.value); // undefined
console.log(descriptor.enumerable); // false
console.log(typeof descriptor.get); // "function" 

ECMAScript 2017 新增了 Object.getOwnPropertyDescriptors()静态方法。这个方法实际上 会在每个自有属性上调用 Object.getOwnPropertyDescriptor()并在一个新对象中返回它们。

javascript 复制代码
let book = {}
Object.defineProperties(book, {
  year_: {
    value: 2017
  },
  edition: {
    value: 1
  },
  year: {
    get: function () {
      return this.year_
    },
    set: function (newValue) {
      if (newValue > 2017) {
        this.year_ = newValue
        this.edition += newValue - 2017
      }
    }
  }
})
console.log(Object.getOwnPropertyDescriptors(book))
// {
// 	edition: {
// 		configurable: false,
// 		enumerable: false,
// 		value: 1,
// 		writable: false
// 	},
// 	year: {
// 		configurable: false,
// 		enumerable: false,
// 		get: f(),
// 		set: f(newValue),
// 	},
// 	year_: {
// 		configurable: false,
// 		enumerable: false,
// 		value: 2017,
// 		writable: false
// 	}
// }

对象合并

ECMAScript 6 专门为合并对象提供了 Object.assign()方法。此方法接受一个目标对象和一个或多个源对象参数,将源对象上的属性复制到目标对象

javascript 复制代码
let obj = {name:'张三'}
let obj1 = {}
let result = Object.assign(obj1,obj)
console.log(result) // {name:'张三'}
console.log(obj1) // {name:'张三'}
console.log(obj1===result) //true

Object.assign()实际上对每个源对象执行的是浅复制。如果多个源对象都有相同的属性,则使用最后一个复制的值。此外,从源对象访问器属性取得的值,比如获取函数,会作为一个静态值赋给目标对象。换句话说,不能在两个对象间转移获取函数和设置函数

set() 方法并没有合并到obj1对象上。

当有相同属性时,使用后一个复制的值

javascript 复制代码
dest = {};
src = { a: {} };
Object.assign(dest, src);
// 浅复制意味着只会复制对象的引用
console.log(dest); // { a :{} }
console.log(dest.a === src.a); // true 

Object.assign()实际上对每个源对象执行的是浅复制

增强的对象语法

  1. 属性简写
javascript 复制代码
let name = 'Matt';
let person = {
 name: name
};

//简写为 
let person = {
  name
}
  1. 可计算属性

在引入可计算属性之前,如果想使用变量的值作为属性,那么必须先声明对象,然后使用中括号语法来添加属性

javascript 复制代码
const nameKey = 'name';
const ageKey = 'age';
const jobKey = 'job';
let person = {};
person[nameKey] = 'Matt';
person[ageKey] = 27;
person[jobKey] = 'Software engineer';
console.log(person); // { name: 'Matt', age: 27, job: 'Software engineer' }

有了可计算属性,就可以在对象字面量中完成动态属性赋值

javascript 复制代码
const nameKey = 'name';
const ageKey = 'age';
const jobKey = 'job';
let person = {
 [nameKey]: 'Matt',
 [ageKey]: 27,
 [jobKey]: 'Software engineer'
};
console.log(person); // { name: 'Matt', age: 27, job: 'Software engineer' }

因为被当作 JavaScript 表达式求值,所以可计算属性本身可以是复杂的表达式,在实例化时再求值

javascript 复制代码
const nameKey = 'name';
const ageKey = 'age';
const jobKey = 'job';
let uniqueToken = 0;
function getUniqueKey(key) {
 return `${key}_${uniqueToken++}`;
}
let person = {
 [getUniqueKey(nameKey)]: 'Matt',
 [getUniqueKey(ageKey)]: 27,
 [getUniqueKey(jobKey)]: 'Software engineer'
};
console.log(person); // { name_0: 'Matt', age_1: 27, job_2: 'Software engineer' }
  1. 简写方法名
javascript 复制代码
let person = {
 sayName: function(name) {
 console.log(`My name is ${name}`);
 }
};
person.sayName('Matt'); // My name is Matt
//简写
let person = {
 sayName(name) {
 console.log(`My name is ${name}`);
 }
};
person.sayName('Matt'); // My name is Matt

简写方法名与可计算属性键相互兼容

javascript 复制代码
const methodKey = 'sayName';
let person = {
 [methodKey](name) {
 console.log(`My name is ${name}`);
 }
}
person.sayName('Matt'); // My name is Matt

对象解构

ECMAScript 6 新增了对象解构语法,可以在一条语句中使用嵌套数据实现一个或多个赋值操作。简 单地说,对象解构就是使用与对象匹配的结构来实现对象属性赋值。 解构赋值不一定与对象的属性匹配。赋值的时候可以忽略某些属性,而如果引用的属性不存在,则 该变量的值就是 undefined:

javascript 复制代码
// 不使用解构
let obj = {name:'张三',age:18,gender:'男'}
let name = obj.name
let age = obj.age
let gender = obj.gender
console.log(name,age,gender) // 张三  18  男

// 使用解构
let obj = {name:'张三',age:18,gender:'男'}
const {name,age,gender} = obj
console.log(name,age,gender) // 张三  18  男
// 解构重命名
const {name:n,age:a,gender:g} = obj
console.log(n,a,g) // 张三  18  男
// 引用的属性不存在
const {address} = obj
console.log(address) // undefined
// 定义默认值
const {address='龙华'} = obj
console.log(address) // 龙华
// 是给事先声明的变量赋值,则赋值表达式必须包含在一对括号中
let name, age;
({name,age}) = obj

null和 undefined 不能被解构

  1. 嵌套解构

解构对于引用嵌套的属性或赋值目标没有限制。为此,可以通过解构来复制对象属性

javascript 复制代码
let person = {
 name: '张三',
 age: 27,
 job: {
 title: '法外狂徒'
 }
};
let personCopy = {}
({name:personCopy.name,age:personCopy.age,} = person)
// 因为一个对象的引用被赋值给 personCopy,所以修改person.job 对象的属性也会影响 personCopy
person.job.title = '罗老师'
console.log(person) //{name: '张三',age: 27,job: {title: '罗老师'}}
console.log(personCopy) //{name: '张三',age: 27,job: {title: '罗老师'}}
// 解构赋值可以使用嵌套结构,以匹配嵌套的属性
const {obj:{title}} = person
console.log(title) //罗老师
// 在外层属性没有定义的情况下不能使用嵌套解构。无论源对象还是目标对象都一样
let person = {
 job: {
 title: 'Software engineer'
 }
};
let personCopy = {};
// foo 在源对象上是 undefined
({
 foo: {
 bar: personCopy.bar
 }
} = person);
// TypeError: Cannot destructure property 'bar' of 'undefined' or 'null'.
// job 在目标对象上是 undefined
({
 job: {
 title: personCopy.job.title
 }
} = person);
// TypeError: Cannot set property 'title' of undefined
  1. 部分解构

如果一个解构表达式涉及 多个赋值,开始的赋值成功而后面的赋值出错,则整个解构赋值只会完成一部分

javascript 复制代码
let person = {
 name: 'Matt',
 age: 27
};
let personName, personBar, personAge;
try {
 // person.foo 是 undefined,因此会抛出错误
 ({name: personName, foo: { bar: personBar }, age: personAge} = person);
} catch(e) {}
console.log(personName, personBar, personAge);
// Matt, undefined, undefined 
  1. 参数上下文匹配

在函数参数列表中也可以进行解构赋值。对参数的解构赋值不会影响 arguments 对象,但可以在函数签名中声明在函数体内使用局部变量

javascript 复制代码
let person = {
 name: 'Matt',
 age: 27
};
function printPerson(foo, {name, age}, bar) {
 console.log(arguments); // 123  { name: 'Matt', age: 27 } abc
 console.log(name, age); //Matt 27
} 
printPerson('123',person,'abc')

创建对象

虽然使用 Object 构造函数或对象字面量可以方便地创建对象,但这些方式也有明显不足:创建具有同样接口的多个对象需要重复编写很多代码。我们可以使用一种称为面向对象编程的编程范式来解决这个问题。

工厂模式

工厂模式是一种创建型设计模式,通过工厂函数来封装对象的创建过程,避免在代码中直接使用 new关键字创建对象。工厂模式简化了对象的创建过程,降低了代码的耦合性,提高了代码的可维护性和可读性。工厂模式可以返回不同类型的对象,具体的实现细节可以隐藏在工厂函数的内部,使得代码更加模块化。另外,通过工厂模式,我们可以在代码中使用抽象的接口而不是具体的对象类型,从而在代码重构时变得更加灵活

javascript 复制代码
function createPerson(name, age, job) {
 let o = new Object();
 o.name = name;
 o.age = age;
 o.job = job;
 o.sayName = function() {
 console.log(this.name);
 };
 return o;
}
let person1 = createPerson("张三", 29, "法外狂徒");
let person2 = createPerson("孙悟空", 2700, "弼马温")

函数 createPerson()接收 3 个参数,根据这几个参数构建了一个包含 Person 信息的对象。 可以用不同的参数多次调用这个函数,每次都会返回包含3个属性和1个方法的对象
工厂模式虽然可以解决创建多个类似对象的问题,但没有解决对象标识问题(不能保证对象之间的唯一性)

构造函数模式

构造函数是用于创建特定类型对象的。ObjectArray是原生的构造函数, 运行时可以直接在执行环境中使用。我们也可以自定义构造函数,以函数的形式为自己的对象类型定义属性和方法。

javascript 复制代码
function Person(name, age, job){
	this.name = name
  this.age = age
  this.job = job
  this.say=function(){
    console.log('hellow,world')
  }
}
let person1 = new Person("张三", 29, "法外狂徒")
let person2 = new Person("孙悟空", 2700, "弼马温")
person1.say(); // 张三
person2.say(); // 孙悟空

console.log(person1.constructor == Person); // true
console.log(person2.constructor == Person); // true 

在这个例子中,Person函数取代了工厂函数中的createPerson函数,实际上Person函数内部createPerson函数基本是相同的,只有以下几点不同

  1. 没有显式地创建对象。
  2. 属性和方法直接赋值给了 this。
  3. 没有 return。

constructor是用于标识对象类型的,不过我们一般使用 instanceof操作符来确定对象类型。

定义自定义构造函数可以确保实例被标识为特定类型,相比于工厂模式,这是一个很大的好处。
注意:构造函数首字母大写

要创建 Person 的实例,应使用 new 操作符。 以这种方式调用构造函数会执行如下操作

  1. 在内存中创建一个新对象。
  2. 这个新对象内部的[[Prototype]]特性被赋值为构造函数的 prototype 属性。
  3. 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)。
  4. 执行构造函数内部的代码(给新对象添加属性)。
  5. 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象
javascript 复制代码
// 手写new
function new(fun,...args){
  // 创建一个新对象,并将其 __proto__ 属性设置为构造函数的原型对象
  ler newObj = Object.create(fun.prototype)
  // 将构造函数的上下文绑定到新创建的对象上,并执行构造函数
  const result = fun.apply(fun,newObj)
	// 返回新创建的对象,如果构造函数显式返回了一个对象,则返回该对象,否则返回新对象
  return result instanceof Object ? result : obj; 
}
  • 构造函数也是函数

构造函数与普通函数唯一的区别就是调用方式不同。除此之外,构造函数也是函数。并没有把某个 函数定义为构造函数的特殊语法。任何函数只要使用 new 操作符调用就是构造函数,而不使用 new 操作符调用的函数就是普通函数。像上面的Person()函数可以调用就是一个普通函数,使用new 调用就是构造函数

javascript 复制代码
// 普通函数
Person("张三", 29, "法外狂徒")
window.say() // 张三
// 构造函数
let person = new Person("张三", 29, "法外狂徒")
person.say() // 张三
// 在另一个对象的作用域中调用
let o = new Object();
Person.call(o, "张三", 29, "法外狂徒");
o.say(); // 张三

在调用一个函数而没有明确设置this值的情况下(即没有作为对象的方法调用,或者没有使用 call()/apply()调用),this始终指向Global对象(浏览器里就是window对象),所以普通函数调用时,window对象上有了一个say()方法;在另一个函数的作用域调用的情况下,使用了call()(或 apply())方法,会改变this的指向,这里的this指向了o对象,所以所有属性和 say()方法都会添加到对象 o 上面。

原型模式

每个函数都会创建一个prototype 属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法。实际上,这个对象就是通过调用构造函数创建的对象的原型。使用原型对象的好处是,在它上面定义的属性和方法可以被对象实例共享。

javascript 复制代码
function Person(){}
Person.prototype.name = '张三'
Person.prototype.age = 18
Person.prototype.say=function(){
  console.log(this.name)
}
let p1 = new Person()
let p2 = new Person()
p1.say() //张三
p2.say() //张三
console.log(p1.say==p2.say) //true

这里的属性和方法都添加到了prototype上,构造函数体中什么都没有。但这样定义之后,调用构造函数创建的新对象仍然拥有相应的属性和方法。与构造函数模式不同,使用这种原型模式定义的属性和方法是由所有实例共享的。因此 p1 和 p2 访问的都是相同的属性和相同的 say()函数

  1. 理解原型

只要创建一个函数就会为这个函数创建一个prototype属性(指向原型对象),默认情况下,所有的原型对象都有一个constructor指回构造函数,例如 Person.prototype.constructor 指向Proson。

在自定义构造函数时,原型对象默认只会获得constructor属性,其他的方法都继承Object。每次调用构造函数都会创建一个实例,这个实例的内部Prototype指针会指向被赋值为构造函数的原型对象。我们可以通过__proto__属性访问对象的原型。

javascript 复制代码
console.log(p1.__proto__ == Person.prototype) //true
console.log(p1.__proto__.__proto__.constructor==Object) //true
console.log(p1.__proto__.__proto__.__proto__==null) //true

** 正常的原型链都会终止于 Object 的原型对象。 Object 原型的原型是 null**

Person构造函数、Person原型对象、Person实例之间的关系。

Person构造函数: Person.prototype指向原型对象,Person.prototype.contructor指回构造函数。

实例对象 p1.__proto__指回Person.prototype

不是所有的实现都对外暴露了[[Peototype]],所以js给我们提供了isPrototypeOf()方法来测试一个对象是否为另一个对象的原型

javascript 复制代码
console.log(Person.prototype.isPrototypeOf(p1)) // true 
console.log(Object.prototype.isPrototypeOf(Person)) // true  所有 JavaScript 对象都继承自 Object.prototype

在ES5中,我们可以使用Object.getPrototypeOf()来获取原型对象

javascript 复制代码
console.log(Object.getPrototypeOf(p1) == Person.prototype)

在ES6中,我们可以使用Object.setPrototypeOf()来设置原型对象

javascript 复制代码
const Person = {
  name:'',
  age:18,
  say(){
    console.log(`我是${this.name}`)
  }
}
let p = {
  name:'张三'
}
Object.setPrototypeOf(p,Person) //将 Person 设置为 p 的原型对象
console.log(p.say())

不过 Object.setPrototypeOf()方法可能会造成较大的性能影响,我们一般使用Object.create()来创建新对象,并为其指定原型

javascript 复制代码
const Person = {
  name:'',
  age:18,
  say(){
    console.log(`我是${this.name}`)
  }
}
let p = Object.create(Person)
p.name = '张三'
console.log(p.say())
  1. 原型层级

在通过对象访问属性时,会按照这个属性的名称开始搜索,先搜索对象实例本身,如果有就返回,没有就去原型对象上找,如果有就返回,没有就一直找到Object,还找不到就返回null。

如果在原型对象和实例对象上都有相同的属性,实例对象的属性会遮住原型对象的属性,优先返回实例对象的值

可以使用hasOwnProperty()方法确定属性是在实例上还是原型对象上。属性存在于调用它的对象实例上时返回true。

javascript 复制代码
function Person(){}
Person.prototype.name = '张三'
Person.prototype.age = 18
Person.prototype.say = function(){
  console.log(this.name)
}
let p1 = new Person()
console.log(p1.name) //张三  来自原型对象 
console.log(p1.hasOwnProperty('name')) //false 
console.log(Person.hasOwnProperty('name')) //true 
p1.name = '法外狂徒'
console.log(p1.name) // 法外狂徒 来自实例对象
console.log(p1.hasOwnProperty('name')) //true 
console.log(Person.hasOwnProperty('name')) //true 
  1. 原型和in操作符

有两种方式可以使用in操作符:单独使用和在for-in循坏中使用。

3.1 单独使用
in操作符会在可以通过对象访问指定属性时返回true,不管是在实例上还是原型上

javascript 复制代码
function Person(){}
Person.prototype.name = '张三'
Person.prototype.age = 18
Person.prototype.say = function(){
  console.log(this.name)
}
let p1 = new Person()
console.log('name' in p1) //true
console.log('name' in Person) //true

判断一个值是否存在原型上,可以通过inhasOwnproperty()来判断

javascript 复制代码
function hasPrototypeProperty(object, name){
 return !object.hasOwnProperty(name) && (name in object);
}

只要可以通过对象方法,in操作符就返回true,而hasOwnproperty()只有属性存在实例上时才返回true。所以当in返回true且hasOwnproperty()返回false时说明该属性是一个原型属性

3.2 for-in循环使用

for-in 循环中使用 in 操作符时,可以通过对象访问且可以被枚举的属性都会返回,包括实例属性和原型属性。

要获得对象上所有可枚举的实例属性,可以使用Object.keys()方法。这个方法接收一个对象作 为参数,返回包含该对象所有可枚举属性名称的字符串数组。

javascript 复制代码
function Person() {}
Person.prototype.name = "张三";
Person.prototype.age = 29;
Person.prototype.job = "法外狂徒";
Person.prototype.sayName = function() {
 console.log(this.name);
};
let keys = Object.keys(Person.prototype);
console.log(keys); // "[name,age,job,sayName]"
let p1 = new Person();
p1.name = "李四";
p1.age = 31;
let p1keys = Object.keys(p1);
console.log(p1keys); // "[name,age]"

如果想列出所有实例属性,无论是否可以枚举,都可以使用 Object.getOwnPropertyNames()

javascript 复制代码
let keys = Object.getOwnPropertyNames(Person.prototype);
console.log(keys); // "[constructor,name,age,job,sayName]"

constructor是一个不可枚举的属性。

  1. 属性枚举顺序

for-in 循环和 Object.keys() 的枚举顺序是不确定的 ,取决于 JavaScript 引擎,可能因浏览器而异 。

Object.getOwnPropertyNames()、Object.getOwnPropertySymbols()和 Object.assign() 的枚举顺序是确定性的。先以升序枚举数值键,然后以插入顺序枚举字符串和符号键

继承

继承是面向对象编程中讨论最多的话题。很多面向对象语言都支持两种继承:接口继承和实现继承。 前者只继承方法签名,后者继承实际的方法。接口继承在 ECMAScript 中是不可能的,因为函数没有签 名。实现继承是 ECMAScript 唯一支持的继承方式,而这主要是通过原型链实现的。

原型链

原型链继承

javascript 复制代码
function Parent() {
   this.name = 'Parent';
}

Parent.prototype.sayHello = function() {
   console.log('Hello, ' + this.name);
};

function Child() {
   this.name = 'Child';
}

Child.prototype = new Parent(); // 将父对象的实例指定为子对象的原型

var child = new Child();
child.sayHello(); // 输出 'Hello, Child'
相关推荐
小马哥编程1 小时前
Function.prototype和Object.prototype 的区别
javascript
小白学前端6661 小时前
React Router 深入指南:从入门到进阶
前端·react.js·react
web130933203981 小时前
前端下载后端文件流,文件可以下载,但是打不开,显示“文件已损坏”的问题分析与解决方案
前端
王小王和他的小伙伴1 小时前
解决 vue3 中 echarts图表在el-dialog中显示问题
javascript·vue.js·echarts
学前端的小朱1 小时前
处理字体图标、js、html及其他资源
开发语言·javascript·webpack·html·打包工具
outstanding木槿1 小时前
react+antd的Table组件编辑单元格
前端·javascript·react.js·前端框架
好名字08212 小时前
前端取Content-Disposition中的filename字段与解码(vue)
前端·javascript·vue.js·前端框架
摇光932 小时前
js高阶-async与事件循环
开发语言·javascript·事件循环·宏任务·微任务
隐形喷火龙2 小时前
element ui--下拉根据拼音首字母过滤
前端·vue.js·ui
m0_748241123 小时前
Selenium之Web元素定位
前端·selenium·测试工具