搜索
首页web前端js教程new操作符的详细用法介绍
new操作符的详细用法介绍Apr 13, 2019 am 10:51 AM
javascript

本篇文章给大家带来的内容是关于new操作符的详细用法介绍,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。

相信很多才接触前端的小伙伴甚至工作几年的前端小伙伴对new这个操作符的了解还停留在一知半解的地步,比较模糊。

就比如前不久接触到一个入职两年的前端小伙伴,他告诉我new是用来创建对象的,无可厚非,可能很多人都会这么答!

那这么答到底是错很是对呢?

下面我们全面来讨论一下这个问题:

我们要拿到一个对象,有很多方式,其中最常见的一种便是对象字面量:

var obj = {}

但是从语法上来看,这就是一个赋值语句,是把对面字面量赋值给了obj这个变量(这样说或许不是很准确,其实这里是得到了一个对象的实例!!)

很多时候,我们说要创建一个对象,很多小伙伴双手一摸键盘,啪啪几下就敲出了这句代码。

上面说了,这句话其实只是得到了一个对象的实例,那这句代码到底还能不能和创建对象画上等号呢?我们继续往下看。

要拿到一个对象的实例,还有一种和对象字面量等价的做法就是构造函数:

var obj = new Object()

这句代码一敲出来,相信小伙伴们对刚才我说的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()报错。

改一下代码构造函数的代码:

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

// ...

执行一下代码,发现所有字段终于如期输出。

这里做个小结:

  1. 当构造函数返回引用类型时,构造里面的属性不能使用,只能使用返回的对象;
  2. 当构造函数返回基本类型时,和没有返回值的情况相同,构造函数不受影响。

那我们现在来考虑下New函数要怎么改才能实现上面总结的两点功能呢?继续往下看:

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)
// ...

执行此代码,发现已经实现了上面总结的两点。

解决方案:使用变量接收构造函数的返回值,然后在New函数里面判断一下返回值类型,根据不同类型返回不同的值。

看到这里。又有小伙伴说,这下new已经完全实现了吧?!!答案肯定是否定的,下面我们继续看一段代码:

function Person(name, age) {
  this.name = name
  this.age = age
  this.gender = '男'
  
  // 返回引用类型
  // return {
  //   name: name,
  //   gender: '男'
  // }
  
  // 返回基本类型
  // return 1
  
  // 例外
  return null
}

再执行代码,发现又出问题了!!!

那为什么会出现这个问题呢?

刚才不是总结了返回基本类型时构造函数不受影响吗,而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()报错,这又是为什么呢?

微信截图_20190413105018.png

从上图我们可以清除地看到,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__属性,指向构造函数的原型对象。

以上是new操作符的详细用法介绍的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文转载于:segmentfault。如有侵权,请联系admin@php.cn删除
es6数组怎么去掉重复并且重新排序es6数组怎么去掉重复并且重新排序May 05, 2022 pm 07:08 PM

去掉重复并排序的方法:1、使用“Array.from(new Set(arr))”或者“[…new Set(arr)]”语句,去掉数组中的重复元素,返回去重后的新数组;2、利用sort()对去重数组进行排序,语法“去重数组.sort()”。

JavaScript的Symbol类型、隐藏属性及全局注册表详解JavaScript的Symbol类型、隐藏属性及全局注册表详解Jun 02, 2022 am 11:50 AM

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于Symbol类型、隐藏属性及全局注册表的相关问题,包括了Symbol类型的描述、Symbol不会隐式转字符串等问题,下面一起来看一下,希望对大家有帮助。

原来利用纯CSS也能实现文字轮播与图片轮播!原来利用纯CSS也能实现文字轮播与图片轮播!Jun 10, 2022 pm 01:00 PM

怎么制作文字轮播与图片轮播?大家第一想到的是不是利用js,其实利用纯CSS也能实现文字轮播与图片轮播,下面来看看实现方法,希望对大家有所帮助!

JavaScript对象的构造函数和new操作符(实例详解)JavaScript对象的构造函数和new操作符(实例详解)May 10, 2022 pm 06:16 PM

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于对象的构造函数和new操作符,构造函数是所有对象的成员方法中,最早被调用的那个,下面一起来看一下吧,希望对大家有帮助。

JavaScript面向对象详细解析之属性描述符JavaScript面向对象详细解析之属性描述符May 27, 2022 pm 05:29 PM

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于面向对象的相关问题,包括了属性描述符、数据描述符、存取描述符等等内容,下面一起来看一下,希望对大家有帮助。

javascript怎么移除元素点击事件javascript怎么移除元素点击事件Apr 11, 2022 pm 04:51 PM

方法:1、利用“点击元素对象.unbind("click");”方法,该方法可以移除被选元素的事件处理程序;2、利用“点击元素对象.off("click");”方法,该方法可以移除通过on()方法添加的事件处理程序。

整理总结JavaScript常见的BOM操作整理总结JavaScript常见的BOM操作Jun 01, 2022 am 11:43 AM

本篇文章给大家带来了关于JavaScript的相关知识,其中主要介绍了关于BOM操作的相关问题,包括了window对象的常见事件、JavaScript执行机制等等相关内容,下面一起来看一下,希望对大家有帮助。

foreach是es6里的吗foreach是es6里的吗May 05, 2022 pm 05:59 PM

foreach不是es6的方法。foreach是es3中一个遍历数组的方法,可以调用数组的每个元素,并将元素传给回调函数进行处理,语法“array.forEach(function(当前元素,索引,数组){...})”;该方法不处理空数组。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
2 周前By尊渡假赌尊渡假赌尊渡假赌
仓库:如何复兴队友
1 个月前By尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island冒险:如何获得巨型种子
4 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

功能强大的PHP集成开发环境

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

Dreamweaver Mac版

Dreamweaver Mac版

视觉化网页开发工具