>웹 프론트엔드 >JS 튜토리얼 >나를 따라와서 자바스크립트 프로토타입 사용 주의사항_javascript 기술을 배우세요.

나를 따라와서 자바스크립트 프로토타입 사용 주의사항_javascript 기술을 배우세요.

WBOY
WBOY원래의
2016-05-16 15:31:591201검색

1. 프로토타입 저장 방법

프로토타입을 사용하지 않고도 JavaScript를 코딩하는 것이 완전히 가능합니다. 예:

function User(name, passwordHash) { 
 this.name = name; 
 this.passwordHash = passwordHash; 
 this.toString = function() { 
  return "[User " + this.name + "]"; 
 }; 
 this.checkPassword = function(password) { 
  return hash(password) === this.passwordHash; 
 }; 
} 

var u1 = new User(/* ... */); 
var u2 = new User(/* ... */); 
var u3 = new User(/* ... */); 

User 유형의 인스턴스가 여러 개 생성되면 문제가 있습니다. 각 인스턴스에 name 및 PasswordHash 속성이 있을 뿐만 아니라 toString 및 checkPassword 메서드에도 각 인스턴스에 복사본이 있습니다. 아래 사진과 같습니다:

그러나 프로토타입에 toString과 checkPassword를 정의하면 위의 그림은 다음과 같습니다.

toString 및 checkPassword 메소드는 이제 User.prototype 객체에 정의됩니다. 즉, 이 두 메소드의 복사본은 하나만 있고 모든 User 인스턴스에서 공유됩니다.

각 인스턴스에 메소드를 복사본으로 배치하면 메소드 쿼리 시간이 절약될 것이라고 생각할 수도 있습니다. (프로토타입에 메소드가 정의되면 먼저 인스턴스 자체에서 메소드를 검색합니다. 찾지 못하면 프로토타입을 계속 검색합니다.)

그러나 최신 JavaScript 실행 엔진에서는 메소드 쿼리에 대한 최적화가 많이 이루어졌기 때문에 이 쿼리 시간을 거의 고려할 필요가 없으므로 프로토타입 객체에 메소드를 배치하면 많은 메모리가 절약됩니다.

2. 클로저를 사용하여 개인 데이터 저장

JavaScript의 객체 시스템은 구문에서 정보 은닉(Information Hiding)의 사용을 권장하지 않습니다. this.name, this.passwordHash 등을 사용할 때 이러한 속성의 기본 액세스 수준은 공개이고 이러한 속성은 어느 위치에서나 obj.name, obj.passwordHash를 통해 액세스할 수 있기 때문입니다.

ES5 환경에서는 객체의 모든 속성에 보다 편리하게 접근할 수 있도록 Object.keys(), Object.getOwnPropertyNames() 등 일부 메소드도 제공됩니다. 따라서 일부 개발자는 JavaScript 개체의 개인 속성을 정의하기 위해 몇 가지 규칙을 사용합니다. 예를 들어, 가장 일반적인 방법은 밑줄을 속성의 접두사로 사용하여 다른 개발자와 사용자에게 이 속성에 직접 액세스해서는 안 된다는 점을 알리는 것입니다.

그러나 이렇게 한다고 문제가 근본적으로 해결되는 것은 아닙니다. 다른 개발자와 사용자는 밑줄 친 속성에 계속 직접 액세스할 수 있습니다. 개인 속성이 정말로 필요한 경우에는 클로저를 사용하여 이를 구현할 수 있습니다.

어떤 의미에서 JavaScript에서는 변수에 대한 클로저의 액세스 전략과 객체의 액세스 전략이 양 극단입니다. 클로저의 모든 변수는 기본적으로 비공개이며 함수 내에서만 액세스할 수 있습니다. 예를 들어 User 유형은 다음과 같이 구현할 수 있습니다.

function User(name, passwordHash) { 
 this.toString = function() { 
  return "[User " + name + "]"; 
 }; 
 this.checkPassword = function(password) { 
  return hash(password) === passwordHash; 
 }; 
} 

이때 name,passwordHash 모두 인스턴스의 속성으로 저장되지 않고, 로컬 변수를 통해 저장됩니다. 그런 다음 클로저의 액세스 규칙에 따라 인스턴스의 메서드는 해당 인스턴스에 액세스할 수 있지만 다른 곳에서는 액세스할 수 없습니다.

이 모드를 사용할 때의 한 가지 단점은 지역 변수를 사용하는 메서드를 인스턴스 자체에서 정의해야 하며 이러한 메서드를 프로토타입 개체에서 정의할 수 없다는 것입니다. 항목 34에서 논의한 것처럼, 이것의 문제는 메모리 소비를 증가시킨다는 것입니다. 그러나 일부 특수한 경우에는 인스턴스에 메서드를 정의하는 것조차 가능합니다.

3. 인스턴스 상태는 인스턴스 객체에만 저장됩니다

유형의 프로토타입과 해당 유형의 인스턴스 간에는 '일대다' 관계가 있습니다. 그런 다음 인스턴스 관련 데이터가 실수로 프로토타입에 저장되지 않도록 해야 합니다. 예를 들어 트리 구조를 구현하는 유형의 경우 해당 유형의 프로토타입에 하위 노드를 저장하는 것은 올바르지 않습니다.

function Tree(x) { 
 this.value = x; 
} 
Tree.prototype = { 
 children: [], // should be instance state! 
 addChild: function(x) { 
  this.children.push(x); 
 } 
}; 

var left = new Tree(2); 
left.addChild(1); 
left.addChild(3); 

var right = new Tree(6); 
right.addChild(5); 
right.addChild(7); 

var top = new Tree(4); 
top.addChild(left); 
top.addChild(right); 

top.children; // [1, 3, 5, 7, left, right] 

상태가 프로토타입에 저장되면 모든 인스턴스의 상태가 중앙에 저장됩니다. 이는 위 시나리오에서 분명히 잘못된 것입니다. 원래 각 인스턴스에 속한 상태가 잘못 공유되었습니다. 아래와 같이:

올바른 구현은 다음과 같습니다.

function Tree(x) { 
 this.value = x; 
 this.children = []; // instance state 
} 
Tree.prototype = { 
 addChild: function(x) { 
  this.children.push(x); 
 } 
}; 

此时,实例状态的存储如下所示:

可见,当本属于实例的状态被共享到prototype上时,也许会产生问题。在需要在prototype上保存状态属性前,一定要确保该属性是能够被共享的。

总体而言,当一个属性是不可变(无状态)的属性时,就能将它保存在prototype对象上(比如方法能够被保存在prototype对象上就是因为这一点)。当然,有状态的属性也能够被放在prototype对象上,这要取决于具体的应用场景,典型的比如用来记录一个类型实例数量的变量。使用Java语言作为类比的话,这类能够存储在prototype对象上的变量就是Java中的类变量(使用static关键字修饰)。

四、避免继承标准类型

ECMAScript标准库不大,但是提供了一些重要的类型如Array,Function和Date。在一些场合下,你也许会考虑继承其中的某个类型来实现特定的功能,但是这种做法并不被鼓励。

比如为了操作一个目录,可以让目录类型继承Array类型如下:

function Dir(path, entries) { 
 this.path = path; 
 for (var i = 0, n = entries.length; i < n; i++) { 
  this[i] = entries[i]; 
 } 
} 
Dir.prototype = Object.create(Array.prototype); 
// extends Array 

var dir = new Dir("/tmp/mysite", ["index.html", "script.js", "style.css"]); 
dir.length; // 0 

但是可以发现,dir.length的值是0,而不是期待中的3。

发生这种现象的原因在于:只有当对象是真正的Array类型时,length属性才会起作用。

在ECMAScript标准中,定义了一个不可见的内部属性被称为 [[class]]。该属性的值只是一个字符串,所以不要被误导认为JavaScript也实现了自己的类型系统。所以,对于Array类型,这个属性的值就是“Array”;对于Function类型,这个属性的值就是“Function”。下表是ECMAScript定义的所有[[class]] 值:

那么当对象的类型确实是Array时,length属性的特别之处就在于:length的值会和该对象中被索引的属性个数保持一致。比如对于一个数组对象arr,arr[0]和arr[1]就表示该对象有两个被索引的属性,那么length的值就是2。当添加了arr[2]的时候,length的值会被自动同步成3。同样地,当设置length值为2时,arr[2]会被自动设置成undefined。

但是当继承Array类型并创建实例时,该实例的 [[class]] 属性并不是Array,而是Object。因此length属性不能正确的工作。

在JavaScript中,也提供了用于查询 [[class]] 属性的方法,即使用Object.prototype.toString方法:

var dir = new Dir("/", []); 
Object.prototype.toString.call(dir); // "[object Object]" 
Object.prototype.toString.call([]); // "[object Array]" 

因此,更好的实现方法是使用组合而不是继承:

function Dir(path, entries) { 
 this.path = path; 
 this.entries = entries; // array property 
} 
Dir.prototype.forEach = function(f, thisArg) { 
 if (typeof thisArg === "undefined") { 
  thisArg = this; 
 } 
 this.entries.forEach(f, thisArg); 
}; 

以上代码将不再使用继承,而是将一部分功能代理给内部的entries属性来实现,该属性的值是一个Array类型对象。

ECMAScript标准库中,大部分的构造函数都会依赖内部属性值如 [[class]] 来实现正确的行为。对于继承这些标准类型的子类型,无法保证它们的行为是正确的。因此,不要继承ECMAScript标准库中的类型如:
Array, Boolean, Date, Function, Number,RegExp,String

以上就是对使用prototype的几点注意事项进行总结,希望可以帮助大家正确的使用prototype。

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.