ECMAScript에는 두 가지 개발 모델이 있습니다. 1. 기능적(절차적) 2. 객체 지향(OOP)
1 객체 생성
1. 일반적인 객체 생성
// 创建一个对象,然后给这个对象新的属性和方法; var box = new Object(); // 创建一个Object对象; box.name = 'lee'; // 创建一个name属性并赋值; box.age = 100; box.run = function(){ // 创建一个run()方法并返回值; return this.name+this.age+'运行中...'; } console.log(box.run()); // 输入属性和方法的值; // 缺点:想创建类似的对象,就会产生大量的代码;
2. 팩토리 모드에서 객체 생성
// 这种方法就是为了解决实例化对象产生大量代码重复的问题; function createObject(name,age){ // 集中创建函数体; var obj = new Object; // 函数体内创建Object; obj.name = name; obj.age = age; obj.run = function(){ return this.name+this.age+"运行中..."; }; return obj; } var box1 = createObject("lee",100); // 实例化;调用函数并传参; var box2 = createObject("jack",200); // 实例二; console.log(box1.run()+box2.run()); // 实例保持相对独立; // 缺点:对象与实例的识别问题;无法搞清楚它们到底是那个对象的实例; console.log(typeof box1); // Object;
3. 생성자가 객체를 생성합니다
// ECMAScript采用构造函数(构造方法)可用来创建特定的对象; function Box(name,age){ // 构造函数模式; this.name = name; // this代表对象Box; this.age = age; this.run = function(){ return this.name+this.age+"运行中..."; }; } var box1 = new Box("lee",100); // 要创建对象的实例必须用new操作符; var box2 = new Box("jack",200); // box1和box2都是Box对象的实例; console.log(box1 instanceof Box); // true;很清晰的识别box1从属于Box; // 使用构造函数,即解决了重复实例化的问题,有解决了对象识别的问题;
생성자 패턴과 팩토리 패턴 사용의 차이점:
(1) 생성자 메서드는 생성된 객체(새 객체)를 표시하지 않습니다.
(2) 이 개체에 속성과 메서드를 직접 할당합니다.
(3) 반환문 없음;1 //생성자 사양:
(1) 함수 이름(함수 Box)과 인스턴스화 구성 이름(new Box)이 동일하며 대문자로 표시됩니다.
(2) 생성자를 통해 인스턴스 객체를 생성하려면 new 연산자를 사용해야 합니다.
// 构造函数和普通函数的区别: var box = new Box('lee',100); // 构造模式调用; Box('lee',200); // 普通模式调用,无效; var o = new Object(); Box.call(o,'jack',200); // 对象冒充调用; // 将Box对象作用域扩充到对象o;Box()方法的运行环境已经变成了对象o里;
생성자를 사용하여 각 인스턴스를 생성하는 경우 각 인스턴스에서 생성자의 메서드를 다시 생성해야 합니다.
ECMAScript의 함수는 객체이기 때문에 함수가 정의될 때마다 객체가 인스턴스화됩니다.
이러한 방식으로 함수를 생성하면 범위 체인과 식별자 확인이 달라집니다.
두 가지 프로토타입
//우리가 만드는 모든 함수에는 객체인 프로토타입 속성이 있습니다.
// 목적: 특정 유형의 모든 인스턴스에서 공유할 수 있는 속성과 메서드를 포함합니다.
// 프로토타입 사용의 장점은 모든 객체 인스턴스가 포함된 속성과 메서드를 공유할 수 있다는 것입니다.
// 즉, 생성자에서 객체 정보(속성/메서드)를 정의할 필요는 없지만 이 정보를 프로토타입에 직접 추가할 수 있습니다.
1. 프로토타입 모드(프로토타입에 속성과 메소드 추가)
1.原型模式 function Box(){} // 声明构造函数; Box.prototype.name = 'Lee'; // 在原型里添加属性和方法; Box.prototype.age = 100; Box.prototype.run = function() { return this.name+this.age+'运行中...'; }; var box1 = new Box(); var box2 = new Box(); console.log(box1.run==box2.run); // =>true;方法引用的地址保持一致; // 在原型中多了两个属性,这两个原型属性都是创建对象时自动生成的; // 1.__proto__:构造函数指向原型对象的一个指针;它的作用:指向构造函数的原型的属性constructor; 14// IE浏览器在脚本访问__proto__会不能识别; 15 // 判断一个实例对象是否指向了该构造函数的原型对象,可以使用isPrototypeOf()方法来测试; console.log(Box.prototype.isPrototypeOf(box)); // =>true; 只要实例化对象,即都会指向; // 原型模式的执行流程: // 1.先查找构造函数对象的实例里的属性或方法,若有,立刻返回; // 2.若构造函数对象的实例里没有,则去它的原型对象里找,若有,就返回; // 虽然我们可以通过对象实例访问保存在原型中的值,但却不能访问通过对象实例重写原型中的值; var box1 = new Box(); console.log(box1.name); // Lee; 原型里的值; bo1.name = 'jack'; console.log(box1.name); // Jack;实例自己赋的值; var box2 = new Box(); console.log(box2.name); // Lee;原型里的值;没有被box1修改; // 如果想要box1继续访问原型里的值,可以把构造函数里的属性删除即可; delete box1.name; // 删除实例自己的属性; console.log(box1.name); // Lee; 原型里原来的值;
2. 프로토타입과 연산자
속성이 생성자의 인스턴스에 있는지 아니면 프로토타입에 있는지 확인하는 방법은 무엇입니까? hasOwnProperty() 함수를 사용하여 확인할 수 있습니다. console.log(box.hasOwnProperty('name')); // 인스턴스가 있으면 true를 반환하고, 그렇지 않으면 false를 반환합니다. in 연산자는 속성이 인스턴스에 있든 프로토타입에 있든 상관없이 객체를 통해 해당 속성에 액세스할 수 있으면 true를 반환합니다. console.log('name' in box); // =>true, 인스턴스 또는 프로토타입에 존재합니다. 3. 더 간단한 프로토타입 구문(프로토타입 리터럴 모드)
3. 더 간단한 프로토타입 구문(프로토타입 리터럴 모드)
function Box(){}; Box.prototype = { // 以字面量形式创建包含属性和方法的新对象; name:'Lee', age:100, run:function(){ return this.name+this.age+'运行中...'; } }; // 使用构造函数创建原型对象和使用字面量创建原型对象在使用上基本相同; // 但是,使用字面量创建的原型对象使用constructor属性不会指向实例,而是指向原型对象Object;构造函数的方式则相反; var box = new Box(); console.log(box instanceof Box); console.log(box instanceof Object); console.log(box.constructor == Box); // 字面量方式,返回false; console.log(box.constructor == Object); // 字面量方式,返回true; // 如果想让字面量方式的constructor指向实例对象: Box.prototype = { constructor:Box, // 直接强制指向即可; } // PS:字面量方式为什么constructor会指向Object? // 因为Box.prototype={}这种字面量写法就是创建一个新对象; // 而每创建一个函数,就会同时创建它的prototype,这个对象也会自动获取constructor属性; // 所以,新对象的constructor重写了Box原来的constructor,因此指向了新对象, // 那个新对象没有指定构造函数,那么就默认为是Object;
// 原型的声明是有先后顺序的,所以,重写的原型会切断之前的原型; function Box(){}; Box.prototype = { constructor:Box, name:'Lee', age:100, run:function(){ return this.age+'运行中...'; } }; Box.prototype = { // 原型重写了,覆盖了之前的原型; age:200, run:function(){ return this.age+'运行中...'; } } var box = new Box(); console.log(box.run()); // =>200运行中...; // 重写原型对象切断了现有原型与任何之前已经存在的对象实例之间的联系;对象实例引用的仍然是最初的原型;
// 그리고 내장 참조 유형 자체도 프로토타입을 사용합니다. console.log(Array.prototype.sort); // =>function sort() { [네이티브 코드] }; console.log(String.prototype.substring); // =>function substring() { [네이티브 코드] };
6. 프로토타입 객체 문제
// 原型模式创建对象缺点:省略了构造函数传参初始化这一过程,带来的缺点就是初始化的值都是一致的; // 而原型最大的有点就是共享,属性共享; // 但是,如果原型中的属性包含引用类型(对象),共享就会存在一定问题; function Box(){}; Box.prototype = { constructor:Box, name:'Lee', age:100, family:['father','mother'], run:function(){ return this.name+this.age+this.family; } }; var box1 = new Box(); box1.family.push('sister'); // 为box1的family属性添加了sister;而这个属性被共享到原型了; console.log(box1.run()); // =>Lee100father,mother,sister; var box2 = new Box(); console.log(box2.run()); // =>Lee100father,mother,sister; // 数据共享导致实例化出的数据不能保存自己的特性;
// 为了解决构造传参和共享问题,组合构造函数+原型模式: function Box(name,age){ // 不共享的使用构造函数; this.name = name; this.age = age; this.family = ['father','moter']; }; Box.prototype = { // 共享的使用原型模式; constructor:Box, run:function(){ return this.name+this.age+this.family; } }; // PS:这种混合模式很好的解决了传参和引用共享的大难题;是创建对象比较好的方法;
// 原型模式,不管是否调用了原型中的共享方法,它都会初始化原型中的方法; // 并且在声明一个对象时,构造函数+原型让人感觉怪异;最好把构造函数和原型封装到一起; function Box(name,age){ // 将所有信息封装到构造函数体内; this.name = name; this.age = age; // 当第一次调用构造函数时,run()方法不存在,然后执行初始化原型; // 当第二次调用,就不会初始化,并且第二次创建新对象,原型也不会载初始化; // 这样既得到了封装,又实现了原型方法共享,并且属性都保持独立; if(typeof this.run != 'function'){ // 仅在第一次调用时初始化; Box.prototype.run = function (){ return this.name+this.age+'运行中...'; }; } }; var box = new Box('lee',10); console.log(box.run()); // PS:使用动态原型模式,要注意一点,不可以再使用字面量的方式重写原型,因为会切断实例和新原型之间的联系;
// 寄生构造函数,其实就是工厂模式+构造模式;这种模式比较通用,但不能确定对象关系; function Box(name,age){ var obj = new Object(); obj.name = name; obj.age = age; obj.run = function (){ return this.name+this.age+'运行中...'; }; return obj; }
2. 프로토타입과 인스턴스의 관계
// 继承是面向对象中一个比较核心的概念; // 其他正统面向对象语言都会用两种方式实现继承:一个是接口实现,一个是继承; // 而ECMAScript只支持继承,不支持接口实现,而实现继承的方式依靠原型链完成; // 实质:利用原型让一个引用类型继承另一个引用类型的属性和方法; // 原型继承链:Box ==>> Desk ==>> Table; function Box(){ // Box构造; this.name = 'Lee'; } function Desk(){ // Desk构造; this.age = 100; } Desk.prototype = new Box(); // 通过创建Box实例,并赋值给Desk.prototype实现的;通过原型,形成链条; // 实质是:重写了Desk的原型对象,取而代之的是一个新类型Box的实例; // 也就是说原来存在于Box实例中的属性和方法,现在也存在与Desk.prototype中了; var desk = new Desk(); console.log(desk.age); // 100; console.log(desk.name); // =>Lee; function Table(){ this.level = 'AAA'; } Table.prototype = new Desk(); // 继续原型链继承;Table继承了Desk; var table = new Table(); console.log(table.name); // Lee;
3. 생성자 차용(객체 가장)
// PS:以上原型链继承缺少一环,那就是Object,所有的构造函数都继承自Object; // 而继承Object是自动完成的,并不需要手动继承; console.log(table instanceof Object); // =>true; console.log(desk instanceof Table); // =>false;Desk是Table的超类; console.log(table instanceof Desk); // =>true; console.log(table instanceof Box); // =>true; // 在JS中,被继承的函数称为超类型(父类,基类); // 继承的函数称为子类型(子类,派生类); // 继承问题: // 字面量重写原型会中断关系; // 子类型无法给超类型传递参数;
4. 결합 상속(프로토타입 체인, 빌린 생성자)
// 在子类型构造函数的内部调用超类型构造函数; function Box(age){ this.name = ['Lee','Jack','Hello']; this.age = age; } function Desk(age){ // 继承了Box;同时还传递了参数; // 这样一来,就会在新Desk对象上执行Box()函数中定义的所有对象初始化代码; Box.call(this,age); // 对象冒充,Desk继承Box,并可以给超类型传参; // 为了确保Box构造函数不会重写子类型的属性,可以在超类型构造函数后,再添加应该在子类型中定义的属性; this.height = 175; } var desk = new Desk(200); // 向Desk()函数传参,再通过函数冒用向Box()函数传参; console.log(desk.age); // =>200; console.log(desk.name); // =>['Lee','Jack','Hello']; desk.name.push('AAA'); // =>添加的新数据,只添加给desk; console.log(desk.name); // =>['Lee','Jack','Hello','AAA'];
5. 프로토타입 상속?
// 使用原型链实现对原型属性和方法的继承; // 通过借用构造函数来实现对实例属性的继承; // 这样,既通过在原型上定义方法实现了函数复用,又能保证每个实例都有他自己的属性; function Box(age){ // 构造函数; this.name = ['Lee','Jack','Hello']; this.age = age; } Box.prototype.run = function(){ // 原型; return this.name+this.age; } function Desk(age){ Box.call(this,age); // 继承属性; 对象冒充; 将Box对象的作用域扩充到Desk中,Desk就会继承Box里的属性和方法; } Desk.prototype = new Box(); // 继承方法; 原型链继承; var desk = new Desk(100); console.log(desk.run()); // =>Lee,Jack,Hello100 // 最常用的继承模式;
6. 기생 상속?
// 这种继承借助原型并基于已有的对象创建对象,同时还不必因此创建自定义类型; function obj(o){ // 传递一个字面量函数; function F(){}; // 创建一个构造函数; F.prototype = o; // 把字面量函数赋值给构造函数的原型; return new F(); // 返回实例化的构造函数; } var box = { // 字面量对象; name:'Lee', arr:['brother','sisiter'] }; var box1 = obj(box); console.log(box1.name); // =>Lee; box1.name = 'Jack'; console.log(box1.name); // =>Jack; console.log(box1.arr); // =>brother,sister; box1.arr.push('father'); // console.log(box1.arr); // =>brother,sister,father; var box2 = obj(box); console.log(box2.name); // =>Lee; console.log(box2.arr); // =>brother,sister,father;引用类型共享了;
7. 기생 결합 상속?
// 把原型式+工厂模式结合而来,目的是为了封装创建对象的过程; // 创建一个仅用于封装继承过程的函数, function create(o){ // 封装创建过程; var f = obj(o); f.run = function(){ return this.arr; // 同样会共享引用; }; return f; }
네 가지 요약
1. 객체 생성
객체는 코드 실행 중에 생성되고 향상될 수 있으므로 엄격하게 정의된 엔터티가 아니라 동적입니다.
클래스가 없을 경우 다음 패턴을 사용하여 객체를 생성할 수 있습니다.
(1) 팩토리 패턴: 간단한 함수를 사용하여 객체를 생성하고 객체에 속성과 메서드를 추가한 다음 객체를 반환합니다.
이 패턴은 나중에 생성자 패턴으로 대체되었습니다.
(2) 생성자 모드: 참조 유형을 사용자 정의하고 new 연산자를 사용하여 내장 객체 인스턴스를 한눈에 생성할 수 있습니다.
단점: 함수를 포함하여 각 멤버를 재사용할 수 없습니다. 함수는 어떤 개체로 제한될 수 없으므로 여러 개체 간에 기능을 공유하지 않을 이유가 없습니다.
(3) 프로토타입 모드: 공유해야 하는 속성과 메서드를 지정하려면 함수의 프로토타입 속성을 사용하세요.
생성자 패턴과 프로토타입 패턴을 조합하여 사용할 경우 생성자를 사용하여 인스턴스 속성을 정의하고 프로토타입을 사용하여 공유 속성 및 메서드를 정의합니다.
프로토타입 체인의 구성은 한 유형의 인스턴스를 다른 생성자의 프로토타입에 할당하여 이루어집니다. 하위 유형은 상위 유형의 모든 속성과 메서드에 액세스할 수 있습니다. 프로토타입 체인의 문제점은 객체 인스턴스가 상속된 모든 속성과 메서드를 공유하므로 단독으로 사용하기에는 적합하지 않다는 것입니다.
해결책: 생성자를 빌리십시오. 즉, 하위 유형 생성자 내부에서 상위 유형 생성자를 호출하십시오.
이러한 방식으로 각 인스턴스는 고유한 속성을 가질 수 있으며 유형을 정의하는 데 생성자만 사용되도록 할 수도 있습니다.
가장 많이 사용되는 상속 패턴은 구성 상속입니다. 프로토타입 체인을 사용하여 공유 속성과 메서드를 상속하고 생성자를 빌려 인스턴스 속성을 상속합니다.
3. 상속 모드
(1). 프로토타입 상속: 생성자를 미리 정의하지 않고도 상속을 얻을 수 있습니다. 복사된 사본은 추가로 변형될 수 있습니다. (2) 기생 상속: 객체 또는 일부 정보를 기반으로 객체를 생성한 다음 객체를 강화하고 마지막으로 객체를 반환합니다. 상위 유형 생성자에 대한 다중 호출로 인해 결합 상속 패턴으로 인해 발생하는 비효율성 문제를 해결하기 위해 이 패턴을 결합 상속과 함께 사용할 수 있습니다.
(3) 기생 결합 상속: 기생 상속과 결합 상속의 장점을 결합하며 유형 기반 상속을 구현하는 가장 효과적인 방법입니다.