目次
1. 演算子 new
__プロト__
四、Function的原型方法:call、apply和bind
4.1 call和apply
4.2 bind
五、函数柯里化
六、文末总结

あなたの知らないJavaScript

Nov 17, 2020 pm 04:30 PM
javascript

JavaScript 列では、知っておくべき操作をいくつか紹介します。

あなたの知らないJavaScript

他の「サークル」の学生とは異なり、フロントエンドサークルの学生は「手書きxxxメソッド」に非常に熱心で、基本的に毎日ナゲッツで過ごすことができます。同様の記事を参照してください。ただし、多くの記事 (すべてを代表しているわけではなく、攻撃を意図したものではありません) は、ほとんどが真実を鵜呑みにし、以前のものをコピーしているため、精査や研究に耐えることができず、JavaScript を始めたばかりの新入生を簡単に誤解させる可能性があります。

これを考慮して、この記事は、『あなたが知らない JavaScript』 (リトル イエロー ブック) のいくつかの典型的な知識ポイントに基づいて、いくつかの古典的で使用頻度の高い「手書き」の方法を組み合わせて説明します。原則と原則を 1 つずつ説明し、実装を組み合わせて、コードを手で書く前に学生が協力して原理を理解します。

1. 演算子 new

説明する前に、まず JavaScript の関数とオブジェクトに関するよくある誤解を明確にする必要があります。 , 「コンストラクター」はクラス内の特別なメソッドです。

new

を使用してクラスを初期化すると、クラス内のコンストラクターが呼び出されます。通常の形式は次のようになります: <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">something = new MyClass(..);复制代码</pre><div class="contentsignin">ログイン後にコピー</div></div>JavaScript にも

new

演算子があり、使用方法はクラス指向言語と同じように見えます。 ##new の仕組みもそれらの言語と同じです。ただし、JavaScript の new の仕組みは、実際にはクラス指向言語の仕組みとはまったく異なります。 まず、JavaScript の「コンストラクター」を再定義しましょう。 JavaScript では、コンストラクターは、new

演算子を使用するときに呼び出される単なる関数です。これらはクラスに属しておらず、クラスをインスタンス化することもありません。実際、これらは特別な関数タイプであるとさえ言えず、

new 演算子によって呼び出される単なる通常の関数です。 実際には、いわゆる「コンストラクター」はなく、関数には「コンストラクター呼び出し」があるだけです。 new

を使用して関数を呼び出すか、コンストラクター呼び出しが発生すると、次の操作が自動的に実行されます:

新しいオブジェクトを作成 (または構築) します。

この新しいオブジェクトは [[ プロトタイプ]] 接続で実行されます;
  1. この新しいオブジェクトは関数呼び出しの this にバインドされます;
  2. 関数が他のオブジェクトを返さない場合、次に、新しい式 式内の関数呼び出しにより、この新しいオブジェクトが自動的に返されます。
  3. したがって、理論的な
  4. new
  5. を記述したい場合は、上記の手順に厳密に従い、それをコードに実装する必要があります。
/**
* @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() メソッドも手書きして、その方法を理解します。 2. 演算子instanceof

長い間、JavaScriptには、
new

instanceof ##など、クラスに似たいくつかの構文要素しかありませんでした。 # ただし、

class キーワードなど、後の ES6 ではいくつかの新しい要素が追加されました。 class を考慮しないと、new

instanceof の関係は「曖昧」になります。 new および instanceof 演算子の主な目的は、「オブジェクト指向プログラミング」に近づくことです。 したがって、new を理解した以上、instanceof

を理解しない理由はありません。 MDN の

instanceof の説明を引用します: 「instanceof 演算子は、コンストラクターの prototype 属性がインスタンス オブジェクトのプロトタイプ チェーンに現れるかどうかを検出するために使用されます。 。」 これを見た後、instanceof の実装では、プロトタイプ チェーンと prototype

の理解をテストする必要があることが基本的にわかりました。 JavaScript のプロトタイプとプロトタイプ チェーンに関する内容は、わかりやすく説明するには大量の内容が必要ですが、インターネット上には優れたまとめブログ記事もいくつかあります。その 1 つは、JS のプロトタイプ、__proto__、およびコンストラクター (図) を徹底的に理解するのに役立ちます。 . それらの関係性や繋がりを明確に梳いてまとめた稀有な秀逸な論文。

「あなたが知らない JavaScript」の第 2 部 - 第 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)
    }
}复制代码
ログイン後にコピー
3. Object.create()

Object.create()

メソッドは新しいオブジェクトを作成し、既存のオブジェクトを使用して新しく作成されたオブジェクトを提供します

__プロト__

在《你不知道的JavaScript》中,多次用到了Object.create()这个方法去模仿传统面向对象编程中的“继承”,其中也包括上面讲到了new操作符的实现过程。在MDN中对它的介绍也很简短,主要内容大都在描述可选参数propertiesObject的用法。

简单起见,为了和newinstanceof的知识串联起来,我们只着重关注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

作为最经典的手写“劳模”们,callapplybind已经被手写了无数遍。也许本文中手写的版本是无数个前辈们写过的某个版本,但是有一点不同的是,本文会告诉你为什么要这样写,让你搞懂了再写。

在《你不知道的JavaScript上卷》第二部分的第1章和第2章,用了2章斤30页的篇幅中详细地介绍了this的内容,已经充分说明了this的重要性和应用场景的复杂性。

而我们要实现的callapplybind最为人所知的功能就是使用指定的thisArg去调用函数,使得函数可以使用我们指定的thisArg作为它运行时的上下文。

《你不知道的JavaScript》总结了四条规则来判断一个运行中函数的this到底是绑定到哪里:

  1. new 调用?绑定到新创建的对象。
  2. call 或者 apply (或者 bind )调用?绑定到指定的对象。
  3. 由上下文对象调用?绑定到那个上下文对象。
  4. 默认:在严格模式下绑定到 undefined ,否则绑定到全局对象。

更具体一点,可以描述为:

  1. 函数是否在 new 中调用( new 绑定)?如果是的话 this 绑定的是新创建的对象:
var bar = new foo()复制代码
ログイン後にコピー
  1. 函数是否通过 callapply (显式绑定)或者硬绑定(bind)调用?如果是的话, this 绑定的是指定的对象:
var bar = foo.call(obj2)复制代码
ログイン後にコピー
  1. 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话, this 绑定的是那个上下文对象:
var bar = obj1.foo()复制代码
ログイン後にコピー
  1. 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到 undefined ,否则绑定到全局对象:
var bar = foo()复制代码
ログイン後にコピー

就是这样。对于正常的函数调用来说,理解了这些知识你就可以明白 this 的绑定原理了。

至此,你已经搞明白了this的全部绑定规则,而我们要去手写实现的callapplybind只是其中的一条规则(第2条),因此,我们可以在另外3条规则的基础上很容易地组织代码实现。

4.1 call和apply

实现callapply的通常做法是使用“隐式绑定”的规则,只需要绑定thisArg对象到指定的对象就好了,即:使得函数可以在指定的上下文对象中调用:

const context = {
    name: 'ZhangSan'
}
function sayName () {
    console.log(this.name)
}
context.sayName = sayName
context.sayName() // ZhangSan复制代码
ログイン後にコピー

这样,我们就完成了“隐式绑定”。落实到具体的代码实现上:

/**
* @param {context} Object 
* @param {arg1, arg2, ...} 指定的参数列表
*/
Function.prototype.call = function (context, ...args) {
    // 指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装
    if (context === null || context === undefined) {
        context = window
    } else if (typeof context !== 'object') {
        context = new context.constructor(context)
    } else {
        context = context
    }
    const func = this
    const fn = Symbol('fn')
    context[fn] = func
    const result = context[fn](...args)
    delete context[fn]
    return result
}

/**
* @param {context}
* @param {args} Array 参数数组
*/
Function.prototype.apply = function (context, args) {
    // 和call一样的原理
    if (context === null || context === undefined) {
        context = window
    } else if (typeof context !== 'object') {
        context = new context.constructor(context)
    } else {
        context = context
    }
    const fn = Symbol('fn')
    const func = this
    context[fn] = func
    const result = context[fn](...args)
    delete context[fn]
    return result
}复制代码
ログイン後にコピー

细看下来,大家都那么聪明,肯定一眼就看到了它们的精髓所在:

const fn = Symbol('fn')
const func = this
context[fn] = func复制代码
ログイン後にコピー

在这里,我们使用Symbol('fn')作为上下文对象的键,对应的值指向我们想要绑定上下文的函数this(因为我们的方法是声明在Function.prototype上的),而使用Symbol(fn)作为键名是为了避免和上下文对象的其他键名冲突,从而导致覆盖了原有的属性键值对。

4.2 bind

在《你不知道的JavaScript》中,手动实现了一个简单版本的bind函数,它称之为“硬绑定”:

function bind(fn, obj) {
    return function() {
        return fn.apply( obj, arguments );
    };
}复制代码
ログイン後にコピー

硬绑定的典型应用场景就是创建一个包裹函数,传入所有的参数并返回接收到的所有值。

由于硬绑定是一种非常常用的模式,所以在 ES5 中提供了内置的方法 Function.prototype.bind ,它的用法如下:

function foo(something) {
    console.log( this.a, something )
    return this.a + something;
}

var obj = {
    a:2
}

var bar = foo.bind( obj )

var b = bar( 3 ); // 2 3

console.log( b ); // 5复制代码
ログイン後にコピー

bind(..) 会返回一个硬编码的新函数,它会把参数设置为 this 的上下文并调用原始函数。

MDN是这样描述bind方法的:bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

因此,我们可以在此基础上实现我们的bind方法:

/**
* @param {context} Object 指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装
*
* @param {arg1, arg2, ...} 指定的参数列表
*
* 如果 bind 函数的参数列表为空,或者thisArg是null或undefined,执行作用域的 this 将被视为新函数的 thisArg
*/
Function.prototype.bind = function (context, ...args) {
    if (typeof this !== 'function') {
        throw new TypeError('必须使用函数调用此方法');
    }
    const _self = this

    // fNOP存在的意义:
    //  1. 判断返回的fBound是否被new调用了,如果是被new调用了,那么fNOP.prototype自然是fBound()中this的原型
    //  2. 使用包装函数(_self)的原型对象覆盖自身的原型对象,然后使用new操作符构造出一个实例对象作为fBound的原型对象,从而实现继承包装函数的原型对象
    const fNOP = function () {}

    const fBound = function (...args2) {

        // fNOP.prototype.isPrototypeOf(this) 为true说明当前结果是被使用new操作符调用的,则忽略context
        return _self.apply(fNOP.prototype.isPrototypeOf(this) && context ? this : context, [...args, ...args2])
    }

    // 绑定原型对象
    fNOP.prototype = this.prototype
    fBound.prototype = new fNOP()
    return fBound
}复制代码
ログイン後にコピー

具体的实现细节都标注了对应的注释,涉及到的原理都有在上面的内容中讲到,也算是一个总结和回顾吧。

五、函数柯里化

维基百科:柯里化,英语:Currying,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术 。

看这个解释有一点抽象,我们就拿被做了无数次示例的add函数,来做一个简单的实现:

// 普通的add函数
function add(x, y) {
    return x + y
}

// Currying后
function curryingAdd(x) {
    return function (y) {
        return x + y
    }
}

add(1, 2)           // 3
curryingAdd(1)(2)   // 3复制代码
ログイン後にコピー

实际上就是把add函数的xy两个参数变成了先用一个函数接收x然后返回一个函数去处理y参数。现在思路应该就比较清晰了,就是只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。

函数柯里化在一定场景下,有很多好处,如:参数复用、提前确认和延迟运行等,具体内容可以拜读下这篇文章,个人觉得受益匪浅。

最简单的实现函数柯里化的方式就是使用Function.prototype.bind,即:

function curry(fn, ...args) {
    return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);
}复制代码
ログイン後にコピー

如果用ES5代码实现的话,会比较麻烦些,但是核心思想是不变的,就是在传递的参数满足调用函数之前始终返回一个需要传参剩余参数的函数:

// 函数柯里化指的是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。
function curry(fn, args) {
    args = args || []

    // 获取函数需要的参数长度
    let length = fn.length
    return function() {
        let subArgs = args.slice(0)

        // 拼接得到现有的所有参数
        for (let i = 0; i < arguments.length; i++) {
        subArgs.push(arguments[i])
        }

        // 判断参数的长度是否已经满足函数所需参数的长度
        if (subArgs.length >= length) {
            // 如果满足,执行函数
            return fn.apply(this, subArgs)
        } else {
            // 如果不满足,递归返回科里化的函数,等待参数的传入
            return curry.call(this, fn, subArgs)
        }
    };
}复制代码
ログイン後にコピー

六、文末总结

在本文中,我们熟悉了new的底层执行原理、instanceof和原型链直接的密切关系、Object.create()是如何实现的原型链指定以及JavaScript中最复杂最难搞定的this的绑定问题。

当然,在《你不知道的JavaScript》中还有很多精妙的见解和知识内容,如"JavaScript是需要先编译再执行的"、"面相对象编程只是JavaScript中的一种设计模式"等等独到的见解和观点。墙裂推荐大家多读几遍,每一遍都会有不同的收获。

相关免费学习推荐:JavaScript(视频)

以上があなたの知らないJavaScriptの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

WebSocket と JavaScript を使用してオンライン音声認識システムを実装する方法 WebSocket と JavaScript を使用してオンライン音声認識システムを実装する方法 Dec 17, 2023 pm 02:54 PM

WebSocket と JavaScript を使用してオンライン音声認識システムを実装する方法 はじめに: 技術の継続的な発展により、音声認識技術は人工知能の分野の重要な部分になりました。 WebSocket と JavaScript をベースとしたオンライン音声認識システムは、低遅延、リアルタイム、クロスプラットフォームという特徴があり、広く使用されるソリューションとなっています。この記事では、WebSocket と JavaScript を使用してオンライン音声認識システムを実装する方法を紹介します。

WebSocket と JavaScript: リアルタイム監視システムを実装するための主要テクノロジー WebSocket と JavaScript: リアルタイム監視システムを実装するための主要テクノロジー Dec 17, 2023 pm 05:30 PM

WebSocketとJavaScript:リアルタイム監視システムを実現するためのキーテクノロジー はじめに: インターネット技術の急速な発展に伴い、リアルタイム監視システムは様々な分野で広く利用されています。リアルタイム監視を実現するための重要なテクノロジーの 1 つは、WebSocket と JavaScript の組み合わせです。この記事では、リアルタイム監視システムにおける WebSocket と JavaScript のアプリケーションを紹介し、コード例を示し、その実装原理を詳しく説明します。 1.WebSocketテクノロジー

JavaScript と WebSocket を使用してリアルタイムのオンライン注文システムを実装する方法 JavaScript と WebSocket を使用してリアルタイムのオンライン注文システムを実装する方法 Dec 17, 2023 pm 12:09 PM

JavaScript と WebSocket を使用してリアルタイム オンライン注文システムを実装する方法の紹介: インターネットの普及とテクノロジーの進歩に伴い、ますます多くのレストランがオンライン注文サービスを提供し始めています。リアルタイムのオンライン注文システムを実装するには、JavaScript と WebSocket テクノロジを使用できます。 WebSocket は、TCP プロトコルをベースとした全二重通信プロトコルで、クライアントとサーバー間のリアルタイム双方向通信を実現します。リアルタイムオンラインオーダーシステムにおいて、ユーザーが料理を選択して注文するとき

WebSocketとJavaScriptを使ったオンライン予約システムの実装方法 WebSocketとJavaScriptを使ったオンライン予約システムの実装方法 Dec 17, 2023 am 09:39 AM

WebSocket と JavaScript を使用してオンライン予約システムを実装する方法 今日のデジタル時代では、ますます多くの企業やサービスがオンライン予約機能を提供する必要があります。効率的かつリアルタイムのオンライン予約システムを実装することが重要です。この記事では、WebSocket と JavaScript を使用してオンライン予約システムを実装する方法と、具体的なコード例を紹介します。 1. WebSocket とは何ですか? WebSocket は、単一の TCP 接続における全二重方式です。

JavaScript と WebSocket: 効率的なリアルタイム天気予報システムの構築 JavaScript と WebSocket: 効率的なリアルタイム天気予報システムの構築 Dec 17, 2023 pm 05:13 PM

JavaScript と WebSocket: 効率的なリアルタイム天気予報システムの構築 はじめに: 今日、天気予報の精度は日常生活と意思決定にとって非常に重要です。テクノロジーの発展に伴い、リアルタイムで気象データを取得することで、より正確で信頼性の高い天気予報を提供できるようになりました。この記事では、JavaScript と WebSocket テクノロジを使用して効率的なリアルタイム天気予報システムを構築する方法を学びます。この記事では、具体的なコード例を通じて実装プロセスを説明します。私たちは

簡単な JavaScript チュートリアル: HTTP ステータス コードを取得する方法 簡単な JavaScript チュートリアル: HTTP ステータス コードを取得する方法 Jan 05, 2024 pm 06:08 PM

JavaScript チュートリアル: HTTP ステータス コードを取得する方法、特定のコード例が必要です 序文: Web 開発では、サーバーとのデータ対話が頻繁に発生します。サーバーと通信するとき、多くの場合、返された HTTP ステータス コードを取得して操作が成功したかどうかを判断し、さまざまなステータス コードに基づいて対応する処理を実行する必要があります。この記事では、JavaScript を使用して HTTP ステータス コードを取得する方法を説明し、いくつかの実用的なコード例を示します。 XMLHttpRequestの使用

JavaScriptでinsertBeforeを使用する方法 JavaScriptでinsertBeforeを使用する方法 Nov 24, 2023 am 11:56 AM

使用法: JavaScript では、insertBefore() メソッドを使用して、DOM ツリーに新しいノードを挿入します。このメソッドには、挿入される新しいノードと参照ノード (つまり、新しいノードが挿入されるノード) の 2 つのパラメータが必要です。

JavaScript と WebSocket: 効率的なリアルタイム画像処理システムの構築 JavaScript と WebSocket: 効率的なリアルタイム画像処理システムの構築 Dec 17, 2023 am 08:41 AM

JavaScript は Web 開発で広く使用されているプログラミング言語であり、WebSocket はリアルタイム通信に使用されるネットワーク プロトコルです。 2 つの強力な機能を組み合わせることで、効率的なリアルタイム画像処理システムを構築できます。この記事では、JavaScript と WebSocket を使用してこのシステムを実装する方法と、具体的なコード例を紹介します。まず、リアルタイム画像処理システムの要件と目標を明確にする必要があります。リアルタイムの画像データを収集できるカメラ デバイスがあるとします。

See all articles