javascript栏目介绍一些必会操作。
和其他“圈子”里的同学们不一样,前端圈子里的同学们都很热衷于“手写xxx方法”,基本上每天在掘金里都可以看到类似的文章。但是,很多文章(不代表全部,无意冒犯)大都是囫囵吞枣、依葫芦画瓢,经不起推敲和考究,很容易误导那些对javascript刚入门的新同学。
鉴于此,本文将基于《你不知道的javascript》(小黄书)里一些典型的知识点,结合一些经典的、高频的被“手写”的方法来逐一地原理和实现相结合,和同学们一起在搞懂原理的基础上再去手写代码。
一、操作符new
在讲解它之前我们首先需要澄清一个非常常见的关于 javascript 中函数和对象的误解:
在传统的面向类的语言中,“构造函数”是类中的一些特殊方法,使用 new 初始化类时会调用类中的构造函数。通常的形式是这样的:
something = new myclass(..);复制代码javascript 也有一个 new 操作符,使用方法看起来也和那些面向类的语言一样,绝大多数开发者都认为 javascript 中 new 的机制也和那些语言一样。然而,javascript 中 new 的机制实际上和面向类的语言完全不同。
首先我们重新定义一下 javascript 中的“构造函数”。在 javascript 中,构造函数只是一些使用 new 操作符时被调用的函数。它们并不会属于某个类,也不会实例化一个类。实际上,它们甚至都不能说是一种特殊的函数类型,它们只是被 new 操作符调用的普通函数而已。
实际上并不存在所谓的“构造函数”,只有对于函数的“构造调用”。
使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作:
创建(或者说构造)一个全新的对象;这个新对象会被执行 [[ 原型 ]] 连接;这个新对象会绑定到函数调用的 this ;如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。
因此,如果我们要想写出一个合乎理论的 new ,就必须严格按照上面的步骤,落实到代码上就是:
/* @param {fn} function(any) 构造函数* @param {arg1, arg2, ...} 指定的参数列表*/function mynew (fn, ...args) { // 创建一个新对象,并把它的原型链(__proto__)指向构造函数的原型对象 const instance = object.create(fn.prototype) // 把新对象作为thisargs和参数列表一起使用call或apply调用构造函数 const result = fn.apply(instance, args) 如果构造函数的执行结果返回了对象类型的数据(排除null),则返回该对象,否则返新对象 return (result && typeof instance === 'object') ? result : instance} 复制代码示例代码中,我们使用object.create(fn.prototype)创建空对象,使其的原型链__proto__指向构造函数的原型对象fn.prototype,后面我们也会自己手写一个object.create()方法搞清楚它是如何做到的。
二、操作符instanceof
在相当长的一段时间里,javascript 只有一些近似类的语法元素,如new 和 instanceof,不过在后来的 es6 中新增了一些元素,比如 class 关键字。
在不考虑class的前提下,new和instanceof之间的关系“暧昧不清”。之所以会出现new和instanceof这些操作符,其主要目的就是为了向“面向对象编程”靠拢。
因此,我们既然搞懂了new,就没有理由不去搞清楚instanceof。引用mdn上对于instanceof的描述:“instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上”。
看到这里,基本上明白了,instanceof的实现需要考验你对原型链和prototype的理解。在javascript中关于原型和原型链的内容需要大篇幅的内容才能讲述得清楚,而网上也有一些不错的总结博文,其中帮你彻底搞懂js中的prototype、__proto__与constructor(图解)就是一篇难得的精品文章,通透得梳理并总结了它们之间的关系和联系。
《你不知道的javascript上卷》第二部分-第5章则更基础、更全面地得介绍了原型相关的内容,值得一读。
以下instanceof代码的实现,虽然很简单,但是需要建立在你对原型和原型链有所了解的基础之上,建议你先把以上的博文或文章看懂了再继续。
/* @param {left} object 实例对象* @param {right} function 构造函数*/function myinstanceof (left, right) { // 保证运算符右侧是一个构造函数 if (typeof right !== 'function') { throw new error('运算符右侧必须是一个构造函数') return } // 如果运算符左侧是一个null或者基本数据类型的值,直接返回false if (left === null || !['function', 'object'].includes(typeof left)) { return false } // 只要该构造函数的原型对象出现在实例对象的原型链上,则返回true,否则返回false let proto = object.getprototypeof(left) while (true) { // 遍历完了目标对象的原型链都没找到那就是没有,即到了object.prototype if (proto === null) return false // 找到了 if (proto === right.prototype) return true // 沿着原型链继续向上找 proto = object.getprototypeof(proto) }}复制代码三、 object.create()
object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。
在《你不知道的javascript》中,多次用到了object.create()这个方法去模仿传统面向对象编程中的“继承”,其中也包括上面讲到了new操作符的实现过程。在mdn中对它的介绍也很简短,主要内容大都在描述可选参数propertiesobject的用法。
简单起见,为了和new、instanceof的知识串联起来,我们只着重关注object.create()的第一个参数proto,咱不讨论propertiesobject的实现和具体特性。
/* 基础版本* @param {object} proto* */object.prototype.create = function (proto) { // 利用new操作符的特性:创建一个对象,其原型链(__proto__)指向构造函数的原型对象 function f () {} f.prototype = proto return new f()}/* 改良版本* @param {object} proto/object.prototype.createx = function (proto) { const obj = {} // 一步到位,把一个空对象的原型链(__proto__)指向指定原型对象即可 object.setprototypeof(obj, proto) return obj}复制代码我们可以看到,object.create(proto)做的事情转换成其他方法实现很简单,就是创建一个空对象,再把这个对象的原型链属性(object.setprototype(obj, proto))指向指定的原型对象proto就可以了(不要采用直接赋值__proto__属性的方式,因为每个浏览器的实现不尽相同,而且在规范中也没有明确该属性名)。
四、function的原型方法:call、apply和bind
作为最经典的手写“劳模”们,call、apply和bind已经被手写了无数遍。也许本文中手写的版本是无数个前辈们写过的某个版本,但是有一点不同的是,本文会告诉你为什么要这样写,让你搞懂了再写。
在《你不知道的javascript上卷》第二部分的第1章和第2章,用了2章斤30页的篇幅中详细地介绍了this的内容,已经充分说明了this的重要性和应用场景的复杂性。
而我们要实现的call、apply和bind最为人所知的功能就是使用指定的thisarg去调用函数,使得函数可以使用我们指定的thisarg
2017年你最应该掌握的设计方法:谷歌产品设计的秘诀云存储服务器价格租赁费用.商标域名注册有什么要求?商标域名有什么作用?301重定向的主机重定向方法如何注册域名?购买了域名后如何使用?域名被墙的几种体现企业邮箱收费多少云服务器和物理服务器价格对比