【JavaScript 漫游】【016】面向对象编程

粉色_蓝天_小溪
文章简介

JavaScript 中一切皆对象!

面向对象编程和异步操作可以说是 JavaScript 语言中最重要的两大知识点,本篇文章作为【JavaScript 漫游】专栏的第 016 篇文章,对 JavaScript 语言中面向对象编程的基础知识、this 关键字、对象的继承等重要知识点进行了记录。

  1. 面向对象编程
  2. this 关键字
  3. 对象的继承
  4. Object 对象的相关方法

面向对象编程

概述

面向对象编程(Object Oriented Programming,缩写为 OOP)是目前主流的编程范式。它将真实世界各种复杂的关系,抽象为一个个对象,然后由对象之间的分工与合作,完成对真实世界的模拟。

每一个对象都是功能中心,是对单个事物的抽象,也是一个封装了属性(property)和方法(method)的容器,具有明确分工,可以完成接受信息、处理数据、发出信息等任务。

对象可以复用,通过继承机制还可以定制。因此,面向对象编程具有灵活、代码可复用、高度模块化等特点,易于维护和开发,比起由一系列函数或指令组成的传统的过程式编程(procedural programming),更适合多人合作的大型软件项目。

构造函数

典型的面向对象编程语言,比如 C++ 和 Java,都有类(class)的概念。类是对象的模板,对象是类的实例。

JavaScript 语言的对象体系,不是基于类的,而是基于构造函数constructor)和原型链prototype)的。

所谓构造函数,就是专门用来生成实例对象的函数。它就是一个普通的函数,但是有自己的特征和用法。

var Person = function () {
  this.age = 18;
};

构造函数的特点有两个

  • 函数体内部使用了this关键字,代表了所要生成的对象实例
  • 生成对象的时候,必须使用 new 关键字

new 命令

基本用法

new 命令的作用,就是执行构造函数,返回一个实例对象。

var Person = function () {
  this.age = 18;
};

var person = new Person();
person.age; // 18

使用 new 命令时,根据需要,构造函数可以接受参数。

var Person = function (num) {
  this.age = num;
};

var person = new Person(35);
person.age = 35;

原理

使用new关键字时,它后面的函数依次执行下面的步骤。

  1. 创建一个空对象,作为将要返回的对象实例
  2. 将这个空对象的原型,指向构造函数的 prototype 属性
  3. 将这个空对象赋值给函数内部的this关键字
  4. 开始执行构造函数内部的代码

构造函数内部,this 指的是一个新生成的空对象,所有针对this的操作,都会发生在这个空对象上。构造函数之所以叫构造函数,就是说这个函数的目的,就是操作一个空对象(即 this 对象),将其构造为需要的样子。

如果构造函数内部有 return 语句,而且 return 后面跟着一个对象,new 命令会返回 return 语句指定的对象;否则,就会不管 return 语句,返回 this 对象。

var Person = function (num) {
  this.age = num;
  return 80;
};

(new Person(80)) === 80; // false
var Person = function (num) {
  this.age = num;
  return { age: 80 };
};
(new Person(35)).age; // 80

如果对普通函数(内部没有this关键字的函数)使用new命令,则会返回一个空对象。

function getMessage(){
  return 'this is a message';
};

var msg = new getMessage();

msg // {}
typeof msg; // "object"

new 命令简化的内部流程,可以用下面的代码表示。

function _new(/* 构造函数 */ constructor, /* 构造函数参数 */ params) {
  // 将 arguments 对象转为数组
  var args = [].slice.call(arguments);
  // 取出构造函数
  var constructor = args.shift();
  // 创建一个空对象,继承构造函数的 prototype 属性
  var context = Object.create(constructor.prototype);
  // 执行构造函数
  var result = contructor.apply(context, args);
  // 如果返回结果是对象,就直接返回,否则返回 context 对象
  return (typeof result === 'object' && result != null) ? result : context;
};

// 实例
var person = _new(Person, 18);

new.target

函数内部可以使用 new.target 属性。如果当前函数是 new 命令调用,new.target 指向当前函数,否则为 undefined

function f(){
  console.log(new.target === f);
};
f(); // false
new f(); // true

this 关键字

涵义

this 关键字是一个非常重要的语法点。不理解它的含义,大部分开发任务都无法完成。

this 就是属性或方法当前所在的对象。

var person = {
  name: '张三',
  describe: function (){
    return '姓名:' + this.name;
  };
}; 

person.describe(); // "姓名:张三"

由于对象的属性可以赋给另一个对象,所以属性所在的对象是可以变的,即 this 的指向是可变的。

var A = {
  name: '张三',
  describe: function () {
 	return '姓名:' + this.name;
  };
};

var B = {
  name: '李四',
};

B.describe = A.describe;
B.describe(); // '姓名:李四'

只要 describe 函数被赋给另一个对象,this 的指向就会变。

JS 语言之中,一切皆对象,运行环境也是对象,所以函数都是在某个对象之中运行,this 就是函数运行时所在的对象(环境)。这本来并不会让用户糊涂,但是 JS 支持运行环境动态切换,也就是说,this 的指向是动态的,没有办法事先确定到底指向哪个对象,这才是最让初学者感到困惑的地方。

实质

JavaScript 语言之所以有 this 的设计,跟内存里面的数据结构有关系。

var obj = { foo: 5 };

上面的代码将一个对象赋值给变量 obj。JS 引擎会先在内存里面,生成一个对象 { foo: 5 },然后把这个对象的内存地址赋值给变量 obj。也就是说,变量 obj 是一个地址(reference)。后面如果要读取 obj.foo,引擎先从 obj 拿到内存地址,然后再从该地址读出原始的对象,返回它的 foo 属性。

原始的对象以字典结构保存,每一个属性名都对应一个属性描述对象。举例来说,上面例子的 foo 属性,实际上是以下面的形式保存的。

{
  foo: {
    [[value]]: 5,
    [[writable]]: true,
    [[enumerable]]: true,
    [[configurable]]: true
  }
}

注意,foo 属性的值保存在属性描述对象的value属性里面。

这样的结构是很清晰的,问题在于属性的值可能是一个函数。

var obj = { foo: function () {}};

这时,引擎会将函数单独保存在内存中,然后再将函数的地址赋值给 foo 属性的 value 属性

{
  foo: {
	[[value]]: 函数的地址
	...
  }
}

由于函数是一个单独的值,所以它可以在不同的环境(上下文)执行。

var f = function () {};
var obj = { f: f };

// 单独执行
f();

// obj 环境执行
f();

JavaScript 运行在函数体内部,引用当前环境的其他变量。

var f = function (){
  console.log(x);
};

上面代码中,函数体里面使用了变量x。该变量由运行环境提供。

现在问题就来了,由于函数可以在不同的运行环境执行,所以需要有一种机制,能够在函数体内部获得当前的运行环境(context)。所以,this 就出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境。

var f = function (){
  console.log(this.x);
};

var x = 1;
var obj = {
  f: f,
  x: 2,
};

// 单独执行
f(); // 1

// obj 环境执行
obj.f); // 2

使用场合

全局环境

全局环境下使用 this,它指的就是顶层对象 window

this === window; // true

function f() {
  console.log(this === window);
}
f(); // true

构造函数

构造函数中的 this,指的是实例对象。

var Obj = function (p) {
  this.p = p;
};

var o = new Obj('Hello World!');
o.p; // 'Hello World!'

对象的方法

如果对象的方法里面包含 thisthis 的指向就是方法运行时所在的对象。该方法赋值给另一个对象,就会改变 this 的指向。

var obj = {
  foo: function () {
    console.log(this);
  }
};

obj.foo(); // obj

(obj.foo = obj.foo)() // window
(false || obj.foo)() // window
(1, obj.foo)() // window

上面代码中,obj.foo 就是一个值。这个值真正调用的时候,运行环境已经不是 obj 了,而是全局环境,所以 this 不再指向 obj

可以这样理解,JS 引擎内部,objobj.foo 储存在两个内存地址,称为地址一和地址二。obj.foo() 这样调用时,是从地址一和地址二,因此地址二的运行环境是地址一,this 指向 obj。但是,上面三种情况,都是直接取出地址二进行调用,这样的话,运行环境都是全局环境,因此 this 指向全局环境。

使用注意点

避免多层 this

由于 this 的指向是不确定的,所以切勿在函数中包含多层的 this

var o = {
  f1: function () {
    console.log(this);
    var f2 = function () {
      console.log(this);
    }();
  };
};

o.f1();
// Object
// Window

实际执行的是下面的代码

var temp = function () {
  console.log(this);
};

var o = {
  f1: function () {
    console.log(this);
    var f2 = temp();
  };
};

一个解决方法是在第二层改用一个指向外层 this 的变量。

var o = {
  f1: function (){
    console.log(this);
    var that = this;
    var f2 = function () {
      console.log(that);
    }();
  }
};

o.f1();
// Object
// Object

使用一个变量固定 this 的值,然后内层函数调用这个变量,是非常常见的做法,务必要掌握

避免数组处理方法中的 this

数组的 mapforeach 方法,允许提供一个函数作为参数。这个函数内部不应该使用 this

var o = {
  v: 'hello',
  p: ['a1', 'a2'],
  f: function f() {
    this.p.forEach(function (item) {
      console.log(this.v + ' ' + item);
    });
  };
};

o.f();
// undefined a1
// undefined a2

foreach 方法的回调函数中的 this ,其实是指向 window 对象,因此取不到 o.v 的值。原因就是内层的 this 不指向外部,而指向顶层对象。

解决这个问题的一种方法,使用中间变量固定 this

var o = {
  v: 'hello',
  p: [ 'a1', 'a2' ],
  f: function f() {
    var that = this;
    this.p.forEach(function (item) {
      console.log(that.v+' '+item);
    });
  }
}

o.f()
// hello a1
// hello a2

另一种方法是将 this 当作 foreach 方法的第二个参数,固定它的运行环境。

var o = {
  v: 'hello',
  p: [ 'a1', 'a2' ],
  f: function f() {
    this.p.forEach(function (item) {
      console.log(this.v + ' ' + item);
    }, this);
  }
}

o.f()
// hello a1
// hello a2

避免回调函数中的 this

回调函数中的 this 往往会改变指向,最好避免使用。

var o = new Object();
o.f = function (){
  console.log(this === o);
};

// jQuery 的写法
$('#button').on('click', o.f);

点击按钮以后,控制台会显示 false。原因是此时 this 不再指向 o 对象,而是指向按钮的 DOM 对象,因为 f 方法是在按钮对象的环境中被调用的。这种细微的差别,很容易在编程中忽视,导致难以察觉的错误。

绑定 this 的方法

this 的动态切换,固然为 JS 创造了巨大的灵活性,但也使得编程变得困难和模糊。有时,需要把 this 固定下来,避免出现意想不到的情况。JS 提供了 callapplybind 这三个方法,来切换/固定 this 的指向。

Function.prototype.call()

函数实例的 call 方法,可以指定函数内部的 this 的指向(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数。

var obj = {};

var f = function () {
  return this;
};

f() === window // true
f.call(obj) === obj // true

call 方法的参数,应该是一个对象。如果参数为空,nullundefined,则默认传入全局对象。

如果call方法的参数是一个原始值,那么这个原始值会自动转成对应的包装对象,然后传入 call 方法。

var f = function (){
  return this;
};

f.call(s);
// Number {[[PrimitiveValue]]: 5}

call 方法还可以接受多个参数。

func.call(thisValue, arg1, arg2, ...);

call 的第一个参数就是 this 所要指向的那个对象,后面的参数则是函数调用时所需的参数。

function add(a, b) {
  return a + b;
}

add.call(this, 1, 2) // 3

call 方法的一个应用是调用对象的原生方法。

var obj = {};
obj.hasOwnProperty('toString') // false

// 覆盖掉继承的 hasOwnProperty 方法
obj.hasOwnProperty = function () {
  return true;
};
obj.hasOwnProperty('toString') // true

Object.prototype.hasOwnProperty.call(obj, 'toString') // false

Function.prototype.apply()

apply 方法的作用与 call 方法类似,也是改变 this 指向,然后再调用该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数,使用格式如下。

func.apply(thisValue, [arg1, arg2, ...]);

apply 方法的第一个参数也是 this 所要指向的那个对象,如果设为 nullundefined,则等同于指定全局对象。第二个参数则是一个数组,该数组的所有成员依次作为参数,传入原函数。原函数的参数,在 call 方法中必须一个个添加,但是在 apply 方法中,必须以数组形式添加。

function f(x, y) {
  console.log(x + y);
};

f.call(null, 1, 1); // 2
f.apply(null, [1, 1]); // 2

Function.prototype.bind()

bind() 方法用于函数体内的 this 绑定到某个对象,然后返回一个新函数。

var d = new Date();
d.getTime(); // 1707614036126

var print = d.getTime;
print(); // Uncaught TypeError: this is not a Date object.

getTime() 方法内部的 this,绑定 Date 对象的实例,赋给变量 print 以后,内部的 this 已经不指向 Date 对象的实例了。

bind() 方法可以解决这个问题。

var counter = {
  count: 0,
  inc: function () {
    this.count++;
  };
};

var func = counter.inc.bind(counter);
func();
counter.count; // 1

this 绑定到其他对象也是可以的。

var counter - {
  count: 0,
  inc: function () {
    this.count++;
  };
};

var obj = {
  count: 100
};
var func = counter.inc.bind(obj);
func();
obj.count; // 101

bind() 可以接受更多的参数,将这些参数绑定原函数的参数。

var add = function(x, y) {
  return x * this.m + y * this.n;
};

var obj = {
  m: 2,
  n: 2
};

var newAdd = add.bind(obj, 5);
newAdd(5); // 20

如果 bind() 方法的第一个参数是 nullundefined,等于将 this 绑定到全局对象,函数运行时 this 指向顶层对象(浏览器为 window)。

function add(x, y) {
  return x + y;
};

var plus5 = add.bind(null, 5);
plus5(10); // 15

对象的继承

JavaScript 语言通过原型对象(prototype)实现对象的继承。

原型对象概述

构造函数的缺点

JavaScript 通过构造函数生成新对象,因此构造函数可以视为对象的模板。实例对象的属性和方法,可以定义在构造函数内部。

function Cat(name, color) {
  this.name = name;
  this.color = color;
};

var cat1 = new Cat('大毛', '白色');

cat1.name; // '大毛'
cat1.color; // '白色'

通过构造函数为实例对象定义属性,虽然很方便,但是有一个缺点。同一个构造函数的多个实例之间,无法共享属性,从而造成对系统资源的浪费。

function Cat(name, color) {
  this.name = name;
  this.color = color;
  this.meow = function (){
    console.log('喵喵喵~');
  };
}

var cat1 = new Cat('大毛', '白色');
var cat2 = new Cat('二毛', '黑色');

cat1.meow == cat2.meow; // false

cat1cat2 是同一个构造函数的两个实例,它们都具有 meow 方法。由于 meow 方法是生成在每个实例对象上面,所以两个实例就生成了两次。也就是说,每新建一个实例,就会新建一个 meow 方法。这既没有必要,又浪费系统资源,因为所有 meow 方法都是同样的行为,完全应该共享。

这个问题的解决方法,就是 JS 的原型对象(prototype)。

prototype 属性的作用

JavaScript 继承机制的设计思想就是,原型对象的所有属性和方法,都能被实例对象共享。也就是说,如果属性和方法定义在原型上,那么所有实例对象都能共享,不仅节省了内存,还体现了实例对象之间的联系。

JavaScript 规定,每个函数都有一个 prototype 属性,指向一个对象。

function f() {};
typeof f.prototype // "object"

对于普通函数来说,该属性基本无用。但是,对于构造函数来说,生成实例的时候,该属性会自动成为实例对象的原型。

function Animal (name) {
  this.name = name;
};
Animal.prototype.color = 'white';

var cat1 = new Animal('大毛');
var cat2 = new Animal('二毛');

cat1.color; // 'white'
cat2.color; // 'white'
function Animal(name) {
  this.name = name;
};
Animal.prototype.color = 'white';

var cat1 = new Animal('大毛');
var cat2 = new Animal('二毛');

cat1.color; // "white"
cat2.color; // "white"

构造函数 Animalprototype 属性,就是实例对象 cat1cat2 的原型对象。原型对象上添加一个 color 属性,实例对象就都共享了该属性。

原型对象的属性不是实例对象自身的属性。只要修改原型对象,变动就立刻会体现在所有实例对象上。

Animal.prototype.color = 'yellow';

cat1.color; // "yellow"
cat2.color; // "yellow"

当实例对象本身没有某个属性或方法时,它会到原型对象去寻找该属性或方法。这就是原型对象的特殊之处。

如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法。

cat1.color = 'black';

cat1.color; // 'black'
cat2.color; // 'yellow'
Animal.prototype.color; // 'yellow'

总结来说,原型对象的作用,就是定义所有实例对象共享的属性和方法。

原型链

JavaScript 规定,所有对象都有自己的原型对象(prototype)。一方面,任何一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个原型链(prototype chain):对象到原型,再到原型的原型。

如果一层层地上溯,所有对象的原型最终都可以上溯到 Object.prototype,即 Object 构造函数的 prototype 属性。也就是说,所有对象都继承了 Object.prototype 的属性。这就是所有对象都有 valueOftoString 方法的原因,因为这是从 Object.prototype 继承的。

Object.prototype 的原型是 nullnull 没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是 null

Object.getPrototypeOf(Object.prototype);
// null

读取对象的某个属性时,JS 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的 Object.prototype 还是找不到,则返回 undefined。如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做覆盖(overriding)。

constructor 属性

prototype 对象有一个 constructor 属性,默认指向 prototype 对象所在的构造函数。

function P (){};
P.prototype.constructor === P; // true

由于constructor 属性定义在 prototype 对象上面,意味着可以被所有实例对象继承。

function P (){};
var p = new P();

p.constructor === P; // true
p.constructor === p.prototype.constructor; // true
p.hasOwnProperty('constructor'); // false

constructor 属性的作用是,可以得知某个实例对象,到底是哪一个构造函数产生的。

function F(){};
var f = new F();

f.constructor === F; // true
f.constructor === RegExp; // false

另一方面,有了 constructor 属性,就可以从一个实例对象新建另一个实例。

function Constr(){};
var x = new Constr();

var y = new x.constructor();
y instanceof Constr; // true

constructor属性表示原型对象与构造函数之间的关系。如果修改了原型对象,一般会同时修改 constructor 属性,防止引用的时候出错。

function Person(name) {
  this.name = name;
}

Person.prototype.constructor === Person; // true

Person.prototype = {
  method: function(){};
};

Person.prototype.constructor === Person; // false
Person.prototype.constructor === Object; // true

所以,修改原型对象时,一般要同时修改 constructor 属性的指向。

// 坏的写法
C.prototype = {
  method1: function (...){...};
  // ...
};

// 好的写法
C.prototype = {
  constructor: C,
  method1: function(...){...},
  // ...
};

// 更好的写法
C.prototype.method1 = function (...){...};

如果不能确定 constructor 属性是什么函数,还有一个办法:通过 name 属性,从实例得到构造函数的名称。

function Foo(){};
var f = new Foo();
f.constructor.name; // "Foo"

instanceof 运算符

instanceof 运算符返回一个布尔值,表示对象是否为某个构造函数的实例。

var v = new Vehicle();
v instanceof Vehicle(); // true

instanceof 运算符的左边是实例对象,右边是构造函数。它会检查右键构造函数的原型对象(prototype),是否在左边对象的原型链上。

由于 instanceof 会检查整个原型链,因此同一个实例对象,可能会对多个构造函数都返回 true

var d = new Date();
d instanceof Date; // true
d instanceof Object; // true

instanceof 运算符的一个用处,是判断值的类型。

var x = [1, 2, 3];
var y = {};
x instanceof Array; // true
y instanceof Object; // true

注意,instanceof 运算符只能用于对象,不适用原始类型的值。对于 undefinednullinstanceof 运算符总是返回 false

构造函数的继承

让一个构造函数继承另一个构造函数,是非常常见的需求。这可以分成两步实现。第一步是在子类的构造函数中,调用父类的构造函数。

function Sub(value) {
  Super.call(this);
  this.prop = value;
};

第二步,是让子类的原型指向父类的原型,这样子类就可以继承父类原型。

Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.prototype.method = '...';

Sub.prototype 是子类的原型,要将它赋值为 Object.create(Super.prototype),而不是直接等于Super.prototype。否则后面两行对Sub.prototype的操作,会连父类的原型Super.prototype一起修改掉。

另外一种写法是 Sub.prototype 等于一个父类实例。

Sub.prototype = new Super();

上面这种写法也有继承的效果,但是子类会具有父类实例的方法。有时,这可能不是我们需要的,所以不推荐使用这种写法。

function Shape(){
  this.x = 0;
  this.y = 0;
};

Shape.prototype.name = function (x, y) {
  this.x += x;
  this.y += y;
  console.log('Shape moved.');
};

// 第一步,子类继承父类的实例
function Rectangle() {
  Shape.call(this); // 调用父类构造函数
};
// 另一种写法
function Rectangle() {
  this.base = Shape;
  this.base();
};

// 第二步,子类继承父类的原型
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

采用这样的写法以后,instanceof 运算符会对子类和父类的构造函数,都返回 true

var rect = new Rectangle();

rect instanceof Rectangle; // true
rect instanceof Shape; // true

多重继承

JS 不提供多重继承功能,即不允许一个对象同时继承多个对象。但是,可以通过变通方法,实现这个功能。

function M1(){
  this.hello = 'hello';
};

function M2(){
  this.world = 'world';
};

function S(){
  M1.call(this);
  M2.call(this);
}

// 继承 M1
S.prototype = Object.create(M1.prototype);
// 继承链上加入 M2
Object.assign(S.prototype, M2.prototype);
// 指定构造函数
S.prototype.constructor = S;
var s = new S();
s.hello // 'hello'
s.world // 'world'

Object 对象的相关方法

Object.getProtypeOf()

返回参数对象的原型。这是获取原型对象的标准方法。

var F = function () {};
var f = new F();
Object.getPrototypeOf(f) === F.prototype; // true

下面是几种特殊对象的原型。

// 空对象的原型是 Object.prototype
Object.getPrototypeOf({}) === Object.prototype; // true

// Object.prototype 的原型是 null
Object.getPrototype(Object.prototype) === null; // true

// 函数的原型是 Function.prototype
function f() {};
Object.getPrototypeOf(f) === Function.prototype; // true

Object.setPrototypeOf()

为参数设置原型对象,返回该参数对象。它接受两个参数,第一个是现有对象,第二个是原型对象。

var a = {};
var b = { x: 1 };
Object.setPrototypeOf(a, b);

Object.getPrototypeOf(a) === b; // true
a.x; // 1

Object.create()

生成实例对象的常用方法是,使用 new 命令让构造函数返回一个实例。但是很多时候,只能拿到一个实例对象,它可能根本不是由构建函数生成的,那么能不能从一个实例对象,生成另一个实例对象呢?

JS 提供了 Object.create 方法,用来满足这种需求。该方法接受一个对象作为参数,然后以它为原型,返回一个实例对象。该实例完全继承原型对象的属性。

// 原型对象
var A = {
  print: function () {
    console.log('hello');
  };
};

// 实例对象
var B = Object.create(A);

Object.getPrototypeOf(B) === A; // true
B.print(); // hello
B.print === A.print; // true

Object.prototype.isPrototypeOf()

实例对象的 isPrototypeOf 方法,用来判断对象是否为参数对象的原型。

var o1 = {};
var o2 = Object.create(o1);
var o3 = Object.create(o2);
o2.isPrototypeOf(o3) // true
o1.isPrototypeOf(o3) // true

Object.prototype.__proto__

实例对象的__proto__属性,返回该对象的原型,该属性可读写。

var obj = {};
var p = {};
obj.__proto__ = p;
Object.getPrototypeOf(obj) === p // true

根据语言标准,__proto__ 属性只有浏览器才需要部署,其他环境可以没有这个属性。它前后的两根下划线,表名它本质是一个内部属性,不应该对使用者暴露。因此,应该尽量少用这个属性,而是用 Object.getPrototypeOf()Object.setPrototypeOf() ,进行原型对象的读写操作。

  • 19
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值