Repository: puppeteer
Issue: page.exposeFunction requires exposed function to stay on `window` object
Labels: feature,confirmed


### Steps to reproduce

**Tell us about your environment:**

* Puppeteer version: 5.5.0
* Platform / OS version: Windows 10.0.18363
* URLs (if applicable):
* Node.js version: 14.15.3

**What steps will reproduce the problem?**

_Please include code that reproduces the issue._

```js
const puppeteer=require("puppeteer");

(async ()=>{
    const browser = await puppeteer.launch();
    
    const page = await browser.newPage();
    await page.exposeFunction('testing',b=>{
        return b+1
    })
    console.log(await page.evaluate(async ()=>{
        const e=window.testing.bind(window)
        delete window.testing;
        return await e(4)
    }))
    await browser.close();
})();
```

**What is the expected result?**
This logs `5` to the console.

**What happens instead?**
```
(node:20164) UnhandledPromiseRejectionWarning: Error: Evaluation failed: TypeError: Cannot read property 'callbacks' of undefined
    at win.<computed> (__puppeteer_evaluation_script__:9:32)
    at __puppeteer_evaluation_script__:4:16
    at ExecutionContext._evaluateInternal (G:\Documents\testing-puppeteer\node_modules\puppeteer\lib\cjs\puppeteer\common\ExecutionContext.js:217:19)
    at processTicksAndRejections (internal/process/task_queues.js:93:5)
    at async ExecutionContext.evaluate (G:\Documents\testing-puppeteer\node_modules\puppeteer\lib\cjs\puppeteer\common\ExecutionContext.js:106:16)
    at async G:\Documents\testing-puppeteer\test.js:10:14
(Use `node --trace-warnings ...` to show where the warning was created)
(node:20164) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
```

I found the source of the problem in [`helper.ts`](https://github.com/puppeteer/puppeteer/blob/b57f3fcd5393c68f51d82e670b004f5b116dcbc3/src/common/helper.ts#L188-L190), where the `pageBindingInitString` function causes the bound function to directly reference `window[bindingName]`.
```ts
const win = window as any;
const binding = win[bindingName];

win[bindingName] = (...args: unknown[]): Promise<unknown> => {
  const me = window[bindingName];
  let callbacks = me.callbacks;
```

Since `window[bindingName]` is `undefined`, trying to access `me.callbacks` causes an error.

My question is why this implementation is the way it is. The responsible lines of code could easily be replaced with the following:
```ts
const win = window as any;
const binding = win[bindingName];

const me = (...args: unknown[]): Promise<unknown> => {
  let callbacks = me.callbacks;
  // rest of function
}
win[bindingName] = me;
```
This code would solve the problem and allow people to reassign exposed functions.

I'm asking now instead of opening a PR because I want to be sure that this behavior isn't intended.