>웹 프론트엔드 >JS 튜토리얼 >신규 사업자의 자세한 이용방법 소개

신규 사업자의 자세한 이용방법 소개

不言
不言앞으로
2019-04-13 10:51:492598검색

이 글은 새로운 연산자 사용에 대한 자세한 소개를 담고 있습니다. 도움이 필요한 친구들이 참고할 수 있기를 바랍니다.

프론트엔드를 이제 막 접한 많은 프론트엔드 파트너나 심지어 몇 년 동안 일해 온 파트너들조차도 아직 새로운 오퍼레이터에 대해 막연하게 이해하고 있다고 생각합니다.

예를 들어, 저는 최근 2년 동안 일해 온 프론트엔드 파트너를 만났습니다. 그는 new가 객체를 만드는 데 사용된다고 말했습니다. 많은 사람들이 이렇게 대답할 것입니다.

그럼 이 답변은 틀린건가요, 맞는건가요?

이 문제를 포괄적으로 논의해 보겠습니다.

객체를 가져오는 방법에는 여러 가지가 있으며 그 중 가장 일반적인 것은 객체 리터럴입니다.

var obj = {}

그러나 문법적 관점에서 보면 이는 반대 리터럴 값을 할당하는 할당문입니다. obj 변수에 추가합니다. (사실 여기에서 객체의 인스턴스를 얻습니다. 정확하지 않을 수 있습니다!!)

객체를 만들고 싶다고 하면 많은 친구들이 양손으로 터치합니다. 키보드를 몇 번만 클릭하면 이 코드가 입력되었습니다.

위에서 언급했듯이 이 문장은 실제로 개체의 인스턴스만 가져옵니다. 그러면 이 코드가 개체를 생성하는 것과 동일할 수 있습니까? 계속해서 아래를 살펴보겠습니다.

객체의 인스턴스를 얻으려면 객체 리터럴과 동등한 또 다른 방법은 생성자입니다.

var obj = new Object()

이 코드가 입력되자마자 모든 사람들이 제가 방금 말한 내용에 관심을 가질 것이라고 믿습니다. obj Code>는 단지 인스턴스 객체이므로 이의가 없습니다! 그러면 많은 친구들이 다시 묻습니다. 이것은 단지 새로운 객체를 만드는 <code>new가 아닌가요? obj只是一个实例对象没有异议了吧!那很多小伙伴又会问了:这不就是new了一个新对象出来嘛!

没错,这确实是new了一个新对象出来,因为javascript之中,万物解释对象,obj是一个对象,而且是通过new运算符得到的,所以说很多小伙伴就肯定的说:new就是用来创建对象的!

这就不难解释很多人把创建对象和实例化对象混为一谈!!

我们在换个思路看看:既然js一切皆为对象,那为什么还需要创建对象呢?本身就是对象,我们何来创建一说?那我们可不可以把这是一种继承呢?

说了这么多,相信不少伙伴已经看晕了,但是我们的目的就是一个:理清new是来做继承的而不是所谓的创建对象!!

那继承得到的实例对象有什么特点呢?

  1. 访问构造函数里面的属性
  2. 访问原型链上的属性

下面是一段经典的继承,通过这段代码来热热身,好戏马上开始:

function Person(name, age) {
  this.name = name
  this.age = age
  this.gender = '男'
}

Person.prototype.nation = '汉'

Person.prototype.say = function() {
  console.log(`My name is ${this.age}`)
}

var person = new Person('小明', 25)

console.log(person.name)
console.log(person.age)
console.log(person.gender)
console.log(person.nation)

person.say()
现在我们来解决第一个问题:我们可以通过什么方式实现访问到构造函数里面的属性呢?答案是callapply
function Parent() {
  this.name = ['A', 'B']
}

function Child() {
  Parent.call(this)
}

var child = new Child()
console.log(child.name) // ['A', 'B']

child.name.push('C')
console.log(child.name) // ['A', 'B', 'C']
第一个问题解决了,那我们又来解决第二个:那又怎么访问原型链上的属性呢?答案是__proto__

现在我们把上面那段热身代码稍加改造,不使用new来创建实例:

function Person(name, age) {
  this.name = name
  this.age = age
  this.gender = '男'
}

Person.prototype.nation = '汉'

Person.prototype.say = function() {
  console.log(`My name is ${this.age}`)
}

// var person = new Person('小明', 25)
var person = New(Person, '小明', 25)

console.log(person.name)
console.log(person.age)
console.log(person.gender)
console.log(person.nation)

person.say()

function New() {
  var obj = {}
  Constructor = [].shift.call(arguments) // 获取arguments第一个参数:构造函数
  // 注意:此时的arguments参数在shift()方法的截取后只剩下两个元素
  obj.__proto__ = Constructor.prototype // 把构造函数的原型赋值给obj对象
  Constructor.apply(obj, arguments) // 改变够着函数指针,指向obj,这是刚才上面说到的访问构造函数里面的属性和方法的方式
  return obj
}

以上代码中的New函数,就是new操作符的实现

主要步骤:

  1. 创建一个空对象
  2. 获取arguments第一个参数
  3. 将构造函数的原型链赋给obj
  4. 使用apply改变构造函数this指向,指向obj对象,其后,obj就可以访问到构造函数中的属性以及原型上的属性和方法了
  5. 返回obj对象

可能很多小伙伴看到这里觉得new不就是做了这些事情吗,然而~~

然而我们却忽略了一点,js里面的函数是有返回值的,即使构造函数也不例外。

如果我们在构造函数里面返回一个对象或一个基本值,上面的New函数会怎样?

我们再来看一段代码:

function Person(name, age) {
  this.name = name
  this.age = age
  this.gender = '男'
  
  return {
    name: name,
    gender: '男'
  }
}

Person.prototype.nation = '汉'

Person.prototype.say = function() {
  console.log(`My name is ${this.age}`)
}

var person = new Person('小明', 25)

console.log(person.name)
console.log(person.age)
console.log(person.gender)
console.log(person.nation)

person.say()

执行代码,发现只有namegender这两个字段如期输出,agenation为undefined,say()

예, 이것은 실제로 새로운 객체를 생성하는 것입니다. 왜냐하면 JavaScript에서 obj는 객체이고 new 연산자를 통해 얻어지기 때문입니다. 그래서 많은 친구들은 확실히 다음과 같이 말합니다. new는 객체를 생성하는 데 사용됩니다!

많은 사람들이 객체 생성과 객체 인스턴스화를 혼동한다는 것을 설명하는 것은 어렵지 않습니다!!

다르게 생각해보자: js의 모든 것이 객체인데 왜 객체를 생성해야 할까요? 그것은 그 자체로 객체입니다. 어떻게 생성할 수 있습니까? 그럼 이것을 일종의 상속이라고 생각하면 될까요?

너무 많이 말했지만 많은 파트너가 이에 놀랐다고 생각합니다. 하지만 우리의 목적은 하나입니다. new가 소위 객체 생성이 아닌 상속에 사용된다는 점을 분명히 하는 것입니다! !
  1. 상속된 인스턴스 객체의 특징은 무엇인가요?
  2. 생성자의 속성에 액세스

프로토타입 체인의 속성에 액세스

다음은 고전적인 상속입니다. 이 코드를 사용하여 준비하면 바로 재미가 시작됩니다.

function Person(name, age) {
  this.name = name
  this.age = age
  this.gender = '男'
  
  // return {
  //   name: name,
  //   gender: '男'
  // }
  return 1
}

// ...
이제 첫 번째 문제를 해결해 보겠습니다. : 생성자의 속성에 어떻게 접근할 수 있나요? 대답은 call 또는 apply
function Person(name, age) {
  // ...
}

function New() {
  var obj = {}
  Constructor = [].shift.call(arguments)
  obj.__proto__ = Constructor.prototype
  
  // Constructor.apply(obj, arguments)
  var result = Constructor.apply(obj, arguments)
  
  // return obj
  return typeof result === 'object' ? result : obj
}

var person = New(Person, '小明', 25)

console.log(person.name)
// ...
입니다. 이제 첫 번째 문제가 해결되었으므로 두 번째 문제인 프로토타입 체인의 속성에 액세스하는 방법을 해결해 보겠습니다. 답은 __proto__입니다이제 위의 준비 코드를 약간 변형하여 new를 사용하지 않고 인스턴스를 생성해 보겠습니다.

function Person(name, age) {
  this.name = name
  this.age = age
  this.gender = '男'
  
  // 返回引用类型
  // return {
  //   name: name,
  //   gender: '男'
  // }
  
  // 返回基本类型
  // return 1
  
  // 例外
  return null
}
위 코드의 New 함수는 new 연산자의 구현입니다

The 주요 단계:

빈 개체 만들기

인수의 첫 번째 매개 변수 가져오기

🎜생성자의 프로토타입 체인을 obj에 할당🎜🎜적용을 사용하여 생성자의 this 지점을 obj 개체를 가리키도록 변경합니다. obj는 생성자에 접근할 수 있습니다. 함수의 속성과 프로토타입의 속성과 메소드🎜🎜return obj object🎜🎜🎜아마도 많은 친구들이 이것을 보고 new가 이런 일을 한다고 생각하겠지만~~🎜🎜그러나 우리는 하나를 무시했습니다. point, js 내부 함수에는 반환 값이 있으며 생성자도 예외는 아닙니다. 🎜🎜생성자에서 객체나 기본 값을 반환하면 위의 New 함수는 어떻게 되나요? 🎜🎜코드를 다시 살펴보겠습니다. 🎜
typeof null === 'object' // true
🎜코드를 실행하여 namegender 두 필드만 예상대로 출력되고 age, nation는 정의되지 않았으며 say()는 오류를 보고합니다. 🎜🎜코드 생성자의 코드를 변경합니다. 🎜
function Person(name, age) {
  // ...
}

function New() {
  var obj = {}
  Constructor = [].shift.call(arguments)
  obj.__proto__ = Constructor.prototype
  // Constructor.apply(obj, arguments)
  var result = Constructor.apply(obj, arguments)
  // return obj
  // return typeof result === 'object' ? result : obj
  return typeof result === 'object' ? result || obj : obj
}

var person = New(Person, '小明', 25)

console.log(person.name)
// ...
🎜코드를 실행하고 모든 필드가 최종적으로 예상대로 출력되는지 확인합니다. 🎜🎜다음은 요약입니다. 🎜🎜🎜생성자가 참조 유형을 반환하면 구성의 속성을 사용할 수 없으며 반환된 개체만 사용할 수 있습니다. 🎜🎜생성자가 기본 유형을 반환하는 경우는 다음과 같습니다. 반환 값이 없으면 생성자는 영향을 받지 않습니다. 🎜🎜🎜이제 위에 요약된 두 가지 기능을 달성하기 위해 New 기능을 수정하는 방법을 살펴보겠습니다. 계속 읽기: 🎜
var obj = {}
🎜이 코드를 실행하고 위에 요약된 두 가지 사항이 달성되었음을 확인하세요. 🎜🎜해결 방법: 변수를 사용하여 생성자의 반환 값을 받은 다음 New 함수에서 반환 값 유형을 결정하고 유형에 따라 다른 값을 반환합니다. 🎜🎜여기를 참조하세요. 또 다른 친구는 '이제 New가 완전히 구현됐죠? ! ! 대답은 '아니오'입니다. 계속해서 코드를 살펴보겠습니다. 🎜
var obj = new Object()
🎜코드를 다시 실행하여 문제가 다시 발생했음을 확인하세요! ! ! 🎜🎜그럼 이런 문제는 왜 발생하는 걸까요? 🎜🎜기본형을 반환할 때 생성자는 영향을 받지 않고 null이 기본형이라고 방금 정리하지 않았나요? 🎜🎜지금 이 순간 내 마음속에는 만 마리의 풀말과 진흙말이 질주하고 있는 것이 가능할까요? ! ! 🎜

解惑:null是基本类型没错,但是使用操作符typeof后我们不难发现:

typeof null === 'object' // true

特例:typeof null返回为'object',因为特殊值null被认为是一个空的对象引用

明白了这一点,那问题就好解决了:

function Person(name, age) {
  // ...
}

function New() {
  var obj = {}
  Constructor = [].shift.call(arguments)
  obj.__proto__ = Constructor.prototype
  // Constructor.apply(obj, arguments)
  var result = Constructor.apply(obj, arguments)
  // return obj
  // return typeof result === 'object' ? result : obj
  return typeof result === 'object' ? result || obj : obj
}

var person = New(Person, '小明', 25)

console.log(person.name)
// ...

解决方案:判断一下构造函数返回值result,如果result是一个引用(引用类型和null),就返回result,但如果此时result为false(null),就使用操作符||之后的obj

好了,到现在应该又有小伙伴发问了,这下New函数是彻彻底底实现了吧!!!

答案是,离完成不远了!!

别急,在功能上,New函数基本完成了,但是在代码严谨度上,我们还需要做一点工作,继续往下看:

这里,我们在文章开篇做的铺垫要派上用场了:

var obj = {}

实际上等价于

var obj = new Object()

前面说了,以上两段代码其实只是获取了object对象的一个实例。再者,我们本来就是要实现new,但是我们在实现new的过程中却使用了new

这个问题把我们引入到了到底是先有鸡还是先有蛋的问题上!

这里,我们就要考虑到ECMAScript底层的API了————Object.create(null)

这句代码的意思才是真真切切地创建了一个对象!!

function Person(name, age) {
  // ...
}

function New() {
  // var obj = {}
  // var obj = new Object()
  var obj = Object.create(null)
  Constructor = [].shift.call(arguments)
  obj.__proto__ = Constructor.prototype
  // Constructor.apply(obj, arguments)
  var result = Constructor.apply(obj, arguments)
  // return obj
  // return typeof result === 'object' ? result : obj
  return typeof result === 'object' ? result || obj : obj
}

var person = New(Person, '小明', 25)

console.log(person.name)
console.log(person.age)
console.log(person.gender)
// 这样改了之后,以下两句先注释掉,原因后面再讨论
// console.log(person.nation)
// person.say()

好了好了,小伙伴常常舒了一口气,这样总算完成了!!

但是,这样写,新的问题又来了。

小伙伴:啥?还有完没完?

function Person(name, age) {
  this.name = name
  this.age = age
  this.gender = '男'
}

Person.prototype.nation = '汉'

Person.prototype.say = function() {
  console.log(`My name is ${this.age}`)
}

function New() {
  // var obj = {}
  // var obj = new Object()
  var obj = Object.create(null)
  Constructor = [].shift.call(arguments)
  obj.__proto__ = Constructor.prototype
  // Constructor.apply(obj, arguments)
  var result = Constructor.apply(obj, arguments)
  // return obj
  // return typeof result === 'object' ? result : obj
  return typeof result === 'object' ? result || obj : obj
}

var person = New(Person, '小明', 25)

console.log(person.name)
console.log(person.age)
console.log(person.gender)
// 这里解开刚才的注释
console.log(person.nation)
person.say()

别急,我们执行一下修改后的代码,发现原型链上的属性nation和方法say()报错,这又是为什么呢?

신규 사업자의 자세한 이용방법 소개

从上图我们可以清除地看到,Object.create(null)创建的对象是没有原型链的,而后两个对象则是拥有__proto__属性,拥有原型链,这也证明了后两个对象是通过继承得来的。

那既然通过Object.create(null)创建的对象没有原型链(原型链断了),那我们在创建对象的时候把原型链加上不就行了,那怎么加呢?

function Person(name, age) {
  this.name = name
  this.age = age
  this.gender = '男'
}

Person.prototype.nation = '汉'

Person.prototype.say = function() {
  console.log(`My name is ${this.age}`)
}

function New() {
  Constructor = [].shift.call(arguments)
  
  // var obj = {}
  // var obj = new Object()
  // var obj = Object.create(null)
  var obj = Object.create(Constructor.prototype)
  
  // obj.__proto__ = Constructor.prototype
  // Constructor.apply(obj, arguments)
  var result = Constructor.apply(obj, arguments)
  // return obj
  // return typeof result === 'object' ? result : obj
  return typeof result === 'object' ? result || obj : obj
}

var person = New(Person, '小明', 25)

console.log(person.name)
console.log(person.age)
console.log(person.gender)
console.log(person.nation)

person.say()

这样创建的对象就拥有了它初始的原型链了,这个原型链是我们传进来的构造函数赋予它的。

也就是说,我们在创建新对象的时候,就为它指定了原型链了,新创建的对象继承自传进来的构造函数!

现在,我们来梳理下最终的New函数做了什么事,也就是本文讨论的结果————new操作符到底做了什么?

  1. 获取实参中的第一个参数(构造函数),就是调用New函数传进来的第一个参数,暂时记为Constructor
  2. 使用Constructor的原型链结合Object.create创建一个对象,此时新对象的原型链为Constructor函数的原型对象;(结合我们上面讨论的,要访问原型链上面的属性和方法,要使用实例对象的__proto__属性)
  3. 改变Constructor函数的this指向,指向新创建的实例对象,然后call方法再调用Constructor函数,为新对象赋予属性和方法;(结合我们上面讨论的,要访问构造函数的属性和方法,要使用call或apply)
  4. 返回新创建的对象,为Constructor函数的一个实例对象。

现在我,我们来回答文章开始时提出的问题,new是用来创建对象的吗?

现在我们可以勇敢的回答,new是用来做继承的,而创建对象的其实是Object.create(null)。
在new操作符的作用下,我们使用新创建的对象去继承了他的构造函数上的属性和方法、以及他的原型链上的属性和方法!

写在最后:

补充一点关于原型链的知识:

  1. JavaScript中的函数也是对象,而且对象除了使用字面量定义外,都需要通过函数来创建对象
  2. prototype属性可以给函数和对象添加可共享(继承)的方法、属性,而__proto__是查找某函数或对象的原型链方式
  3. prototype和__proto__都指向原型对象
  4. 任意一个函数(包括构造函数)都有一个prototype属性,指向该函数的原型对象
  5. 任意一个实例化的对象,都有一个__proto__属性,指向构造函数的原型对象。

위 내용은 신규 사업자의 자세한 이용방법 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 segmentfault.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제