[Direct collection] 100 front-end JavaScript interview questions (Part 2)

61. What are the common HTTP requests? What are their differences?

There are five common types, namely GET, HEAD, POST, PUT, DELETE

  • GET: It is the most common method, used to obtain resources, and is often used to query the server for certain information. Opening a web page generally uses the GET method, because it is necessary to obtain information from the web server.

  • HEAD: Similar to a GET request, except that there is no specific content in the returned response, which is used to obtain the header.

  • POST: Submit data to a specified resource for processing (such as submitting a form or uploading a file), and the data is included in the request body. POST requests may result in the creation of new resources and/or the modification of existing resources.

  • PUT: The data transmitted from the client to the server replaces the content of the specified document.

  • DELETE: Request the server to delete the specified page.

The most common HTTP request methods are GET and POST. GET is generally used to obtain/query resource information, and POST is generally used to update resource information. The difference between GET and POST:

  • The data submitted by GET will be placed after the ?, and the URL and the transmission data will be separated by a question mark (?), and the parameters will be connected by &

  • The size of the data submitted by GET is limited (because the browser has a limit on the length of the URL), while the size of the data submitted by the POST method is not limited.

  • Submitting data through GET will bring security problems. For example, when a login page submits data through GET, the user name and password will appear on the URL. If the page can be cached or other people can access the machine, it can be obtained from the history. The user's account and password.

62. What are the data types of JS? How to judge the data types? What are their advantages and disadvantages?

  • typeof operator used to detect data types

    Whether it is an array or a regular expression, it returns "object", so typeof cannot judge whether a value is an array

  • instanceof/constructor. Detect whether an instance belongs to a certain class Use instanceof/constructor to detect arrays and regular

    When using instanceof to detect, as long as the current class is on the prototype chain of the instance (it can be found through the prototype chain __proto__), the detection results are all true.

    The value of the basic data type cannot be detected by instanceof

    In the prototype inheritance of the class, the result detected by instanceof is actually inaccurate

  • Object.prototype.toString.call(value) -> Find the toString method on the Object prototype, let the method execute, and let this in the method change to value (value-> is the value we want to detect the data type). There are many types of detection, and they are more accurate.

63. How do you understand symbol?

Symbol is  ES6 a new primitive type that represents unique values

It can choose to accept a string as a parameter or not, but two Symbolvalues ​​​​of the same parameter are not equal

//不传参数
   const s1 = Symbol();
   const s2 = Symbol();
   console.log(s1 === s2); // false

   // 传入参数
   const s3 = Symbol('debug');
   const s4 = Symbol('debug');
   console.log(s3 === s4); // false

It can be typeofjudged whether it is Symbola type

console.log(typeof s1); // symbol

Symbol.for(): Used to point variables with the same description Symbolto the same Symbolvalue

let a1 = Symbol.for('a');
let a2 = Symbol.for('a');
a1 === a2  // true
typeof a1  // "symbol"
typeof a2  // "symbol"

let a3= Symbol("a");
a1 === a3      // false

Symbol.keyFor(): Used to detect whether the value of the string parameter as the name  Symbolhas been registered, and return a registered  Symbol type valuekey

let a1 = Symbol.for("a");
Symbol.keyFor(a1);    // "a"

let a2 = Symbol("a");
Symbol.keyFor(a2);    // undefined

description: SymbolThe description used to return the data:

// Symbol()定义的数据
let a = Symbol("acc");
a.description  // "acc"
Symbol.keyFor(a);  // undefined

// Symbol.for()定义的数据
let a1 = Symbol.for("acc");
a1.description  // "acc"
Symbol.keyFor(a1);  // "acc"

// 未指定描述的数据
let a2 = Symbol();
a2.description  // undefined
  • Use scenario 1: add attributes to objects

let n = Symbol('N');
let obj = {
    name: "hello world",
    age: 11,
    [n]: 100
};
  • Use Scenario 2: Adding Private Properties to Objects

const speak = Symbol();
class Person {
    [speak]() {
        console.log(123)
    }
}
let person = new Person()
console.log(person[speak]())

64. What are the commonly used methods of arrays?

Common methods of arrays. Such interview questions are very basic interview questions. The purpose of the interviewer is not just to let you recite all the methods of arrays.

The key point here is that these two words are often used. The purpose of the interviewer is to use this question to see your usual application and understanding of array functions in projects, and then judge your usual application of arrays in projects, and then infer your real technology. level

The suggested answer here is to talk about the application in actual projects through an in-depth analysis of the array function method that I use the most.

For example, when it comes to deleting an array by an array unit, in addition to talking about the usage of the function, splice() also talks about the impact of the collapse of the array after deleting the array unit in a specific project and how to deal with it

concat() concatenates two or more arrays and returns the result.

join() puts all the elements of the array into a string. Elements are separated by the specified delimiter.

pop() removes and returns the last element of the array. 

shift() removes and returns the first element of the array

push() adds one or more elements to the end of the array and returns the new length.

unshift() Adds one or more elements to the beginning of the array and returns the new length.

reverse() reverses the order of the elements in the array.

slice() returns selected elements from an existing array

sort() sorts the elements of an array

splice() removes elements and adds new elements to the array.

toSource() returns the source code for this object.

toString() converts an array to a string and returns the result.

toLocaleString() Converts an array to a local array and returns the result.

valueOf() returns the original value of the array object

65. How JavaScript stores cookies

The basic syntax is document.cookie = 'key name=key value; expires=time object; path=path';

If the aging is not set, the default is seeion. If the aging path is not set, the default is the folder where the current file is located.

To set the time limit, you need to set a time object. The time stamp of the time object is the time limit. Pay attention to calculating the time difference between the current time zone and the world standard time.

The path is generally set to the root directory, which is '/'

66. Ke physicochemical function

The so-called curried function refers to transforming a function that accepts multiple parameters into a function that accepts a single parameter and returns a new function that accepts the remaining parameters and returns the result

// 普通的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

advantage:

1. Parameter reuse.

For example, a function has two parameters, but the first parameter will be used repeatedly. Each time you need to input a repeated parameter.
After using the curried function, you only need to input one parameter.

2. Confirm in advance and

define in advance A good parameter also determines the execution direction of the entire function program to avoid performing judgments and comparisons every time.

shortcoming:

Only one parameter can be defined in advance. If you want to define multiple parameters in advance, this syntax is not supported.

Performance issues with curried functions:

Accessing the arguments object is usually a bit slower than accessing named parameters.
Some older browsers have a considerably slower implementation of arguments.length.
Using function.apply() and function.call() is usually faster than calling fn() directly. Slightly slower
Creating lots of nested scopes and closures has a cost, both in memory and speed

67. Object traversal method

Object traversal methods in JavaScript

The basic syntax of for...in

is for(variable in object) {loop body program}

Here, it should be noted that
1, the key name stored in the variable is obtained from the key value stored in the object through the key name,
  because the value of the variable point syntax is not Support parsing variables To use the object [key name] to get the key value

2, the execution effect of the loop variable definition let and var definition is different

Object.keys(object)

returns an array of all keys of the current object,
and then loops through the array before performing operations

Object.value (object)

returns an array that is composed of all key values ​​of the current object,
and then loops through the array before performing operations

68. Array flattening

Array flattening

The so-called flattening of arrays is to convert multidimensional arrays into one-dimensional arrays. Generally, arrays are flattened. The multidimensional data stored in the array is an array and will not be an object or a function.

The most commonly used method is array.toString() to convert the array into a string. The result is to get the data of each unit in the array to form a string. Use commas to separate and then convert the string to an array with commas as intervals.

function fun1( arr ){
   let str = arr.toString();
   return str.split(',');
}

You can also use the array.some() method to determine whether there is still an array in the array and use the spread operator to assign

function fun1(arr) {
    while (arr.some(item => Array.isArray(item))) {
        arr = [].concat(...arr);
    }
    return arr;
}

In addition, the new flat function in ES6 syntax can also realize the flattening of arrays. The parameters are fixed.

const arr = 原始数组.flat( Infinity );

69. The principle of typeof

Use typeof to judge the data type based on the result of the return value

The specific return values ​​are number, string, object, boolean, function, undefined

Among them, the return value of the array null object is object, so the specific data type cannot be distinguished very clearly, and it cannot be accurately distinguished in the actual project.

If you want to specifically distinguish the data type, you need to use the Object.prototype.toString.call() method. The return value is

object String string
object Number numeric type
object Boolean boolean type
object Undefined undefined type
object Null null type
object Function function type
object Array array type

70. Introducing Type Conversion

Because JavaScript is a weakly typed computer language, when storing data, there is no setting for the data type stored in variables, so any type of data can be stored in a variable. During the execution of the program, data type conversion will be encountered.

Automatically converted
Automatically converted to a string
The data is directly converted to the corresponding string

        Automatically convert to numeric type Convert to corresponding numeric value 1 true 0 false null "" ''    

Strings that conform to the numeric specification
are converted to NaN
        Strings that do not conform to the numeric specification
          undefined

are automatically converted to numeric types
          false:
              0 0.0000 '' NaN null undefined
          true:
              all other cases are converted to true

forced conversion

Mandatory conversion to Boolean type

Boolean (variable/expression)
              conversion principle is exactly the same as automatic conversion principle
              false : 0 0.000 '' null NaN undefined
              true : All other cases are converted to true

  Mandatory conversion to string type
      String (variable/expression) ;
          The principle of conversion is exactly the same as the principle of           automatic conversion     .           It will not change the original data stored in
          the           variable. Conversion base           Variable storage null or undefined does not support   coercive conversion to numeric type       Number()           conversion principle is exactly the same as automatic conversion,           it will not change the original content stored in the variable       parseInt()           obtains the content part that conforms to the integer syntax specification from the left           If the first character from the left does not conform to the integer syntax specification,           the execution result is NaN

















      parseFloat()
          gets the part of the content that conforms to the grammar specification of floating-point numbers from the left
          If the first character from the left does not conform to the grammar specification of floating-point numbers,
          the execution result is NaN    

71. Execution context

Execution context: Refers to variables, function declarations, parameters (arguments), scope chain, this and other information in the current execution environment. Divided into global execution context and function execution context, the difference is that there is only one global execution context, and a function execution context will create a new function execution context every time a function is called.

The variable object is the data scope associated with the execution context, storing the variable and function declarations defined in the context.

A variable object is an abstract concept that represents different objects in different contexts:

The variable object of the global execution context In the global execution context, the variable object is the global object. In the top-level js code, this points to the global object, and global variables will be queried as attributes of the object. In the browser, window is the global object. The variable object of the function execution context In the function context, the variable object VO is the active object AO. When initialized, with the arguments attribute. When the function code is divided into two phases and enters the execution context, the variable object includes the formal parameter function declaration, which will replace the existing variable object variable declaration, and will not replace the formal parameter and function function execution

The function of the execution context stack is to track the code. Since JS is single-threaded, only one thing can be done at a time, and other things will be queued in the specified context stack for execution.

When the JS interpreter initializes the code, it will first create a new global execution context to the top of the execution context stack, and then create a new execution context and put it on the top of the stack with each function call. After the execution is completed, it is popped from the top of the execution context stack until it returns to the global execution context.

First, the global execution context is created, and the current global execution context is active. There are two functions getName and getYear in the global code, and then call the getName function, the JS engine stops executing the global execution context, creates a new function execution context, and puts the function context on the top of the execution context stack. The getYear function is called in the getName function. At this time, the execution context of getName is suspended, a new execution context of the getYear function is created, and the execution context of this function is placed on the top of the execution context stack. After the getYear function is executed, its execution context is popped from the top of the stack, and returns to the getName execution context to continue execution. After getName is executed, its execution context is popped from the top of the stack and returned to the global execution context.

72. Closure problems and optimization

Closure: A function that has access to variables in the scope of another function. A common way to create closures is to create a function inside another function.

effect:

1. You can read the variables inside the function. 2. It is equivalent to drawing a private scope to avoid data pollution. 3. Keep variables in the memory all the time.

Closures have three properties:

1. Function nested function

2. External parameters and variables can be referenced inside the function

3. Parameters and variables will not be recycled by the garbage collection mechanism

The problem with closures

Closures will generate a context that will not be destroyed, which will cause excessive stack/heap memory consumption, and sometimes cause memory leaks, etc., which will affect the running performance of the page. Therefore, in real projects, closures must be applied reasonably!

Closure optimization

  original code

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
  this.getName = function() {
    return this.name;
  };

  this.getMessage = function() {
    return this.message;
  };
}

optimize code

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype.getName = function() {
  return this.name;
};
MyObject.prototype.getMessage = function() {
  return this.message;
};

73. The difference between browser and Node event loop

1. The direction of this in the global environment

In node, this points to global and in browser, this points to window, which is why a root is defined in underscore;

and many APIs are encapsulated under window in browser, such as alert, document, location, history Wait and many more. We can't use xxx(); or window.xxx(); in the node environment. Because these APIs are browser-level packages, they are not stored in javascript. Of course, node also provides many node-specific APIs.

Two, js engine

In the browser, different browser vendors provide different browser kernels, and browsers rely on these kernels to interpret the js we write. But considering the small amount of differences between different cores, we need corresponding compatibility. Fortunately, there are some excellent libraries to help us deal with this problem, such as jquery, underscore and so on.

  nodejs is based on Chromes JavaScript runtime, that is to say, it actually encapsulates the Google V8 engine (applied to Google Chrome browser). The V8 engine executes Javascript very fast and has very good performance.

NodeJS does not provide simple encapsulation, and then provides API calls, if this is the case, then it will not be as popular as it is now. Node optimizes some special use cases and provides alternative APIs to make V8 run better in non-browser environments. For example, in a server environment, processing binary data is usually essential, but Javascript lacks support for this. Therefore, V8.Node adds the Buffer class to process binary data conveniently and efficiently. Therefore, Node not only simply uses V8, but also optimizes it to make it more powerful in various environments.

Three, DOM operation

In most cases, js in the browser operates the DOM directly or indirectly (some virtual DOM libraries and frameworks). Because the code in the browser mainly works on the presentation layer. But node is a server-side technology. There is no front page, so we will not manipulate the DOM in node.

4. I/O read and write

Unlike browsers, we need to read and write files like other server technologies, nodejs provides more convenient components. And the browser (to ensure compatibility) is a lot of trouble if it wants to open a local picture directly on the page (don’t tell me it’s not easy, relative paths... you’ll know if you try it or find a library It’s either a binary stream, or it’s uploaded and the network address is displayed. Otherwise, why would someone build a js library), and all of this is done with one component.

5. Module loading

 JavaScript has a characteristic, that is, the native API that does not provide package references executes all the things to be loaded at once. Here it depends on the skill of everyone's closure. All the things used are together, there is no divide and conquer, and it is particularly illogical and reusable. If the page is simple or the website, of course we can use some js libraries of AMD and CMD (such as requireJS and seaJS) to get it done. In fact, many large websites do this.

  The module loading API of CMD is provided in nodeJS. If you have used seaJS, you should get started quickly.
Node also provides a package management tool like npm, which can manage the libraries we drink more effectively and conveniently

74. Mobile click delay

reason :

In order to determine whether the user is going to click, double-click or do other operations, when you click on the mobile terminal, there will be a 300 millisecond delay in order to wait to determine what the user's next operation is

Solution 1

禁用缩放

<meta name="viewport" content="user-scalable=no">
<meta name="viewport" content="initial-scale=1,maximum-scale=1">


当HTML文档头部包含以上meta标签时 表明这个页面是不可缩放的,那双击缩放的功能就没有意义了,此时浏览器可以禁用默认的双击缩放行为并且去掉300ms的点击延迟。
这个方案有一个缺点,就是必须通过完全禁用缩放来达到去掉点击延迟的目的,然而完全禁用缩放并不是我们的初衷,我们只是想禁掉默认的双击缩放行为,这样就不用等待300ms来判断当前操作是否是双击。但是通常情况下,我们还是希望页面能通过双指缩放来进行缩放操作,比如放大一张图片,放大一段很小的文字。

Solution 2 Change the default viewport width

<meta name="viewport" content="width=device-width">

一开始,为了让桌面站点能在移动端浏览器正常显示,移动端浏览器默认的视口宽度
并不等于设备浏览器视窗宽度,而是要比设备浏览器视窗宽度大,通常是980px。
我们可以通过以下标签来设置视口宽度为设备宽度。因为双击缩放主要是用来改善
桌面站点在移动端浏览体验的,而随着响应式设计的普及,很多站点都已经对移动
端坐过适配和优化了,这个时候就不需要双击缩放了,如果能够识别出一个网站是
响应式的网站,那么移动端浏览器就可以自动禁掉默认的双击缩放行为并且去掉
300ms的点击延迟。如果设置了上述meta标签,那浏览器就可以认为该网站已经
对移动端做过了适配和优化,就无需双击缩放操作了。
这个方案相比方案一的好处在于,它没有完全禁用缩放,而只是禁用了浏览器默认
的双击缩放行为,但用户仍然可以通过双指缩放操作来缩放页面。

Solution 3 CSS touch-action

跟300ms点击延迟相关的,是touch-action这个CSS属性。这个属性指定了相应元素上能够触发的用户代理(也就是浏览器)的默认行为。如果将该属性值设置为touch-action: none,那么表示在该元素上的操作不会触发用户代理的任何默认行为,就无需进行300ms的延迟判断。


最后的最后 我们还可以使用一些 插件来解决这个问题 例如 FastClick 是 FT Labs 专门为解决移动端浏览器 300 毫秒点击延迟问题所开发的一个轻量级的库。FastClick的实现原理是在检测到touchend事件的时候,会通过DOM自定义事件立即出发模拟一个click事件,并把浏览器在300ms之后的click事件阻止掉。

安装    npm install fastclick -S

使用    如何你是vue项目可以在main.js里面直接引入,当然这样是全局的,如果你需要某个页面用到,那就单个页面引入。

//引入
import fastClick from 'fastclick'
//初始化FastClick实例。在页面的DOM文档加载完成后
fastClick.attach(document.body)

75. cookie attribute

Common properties of cookies

Key Name The key name of the cookie key-value pair
Key value The key value of the cookie key-value pair
expirescookie aging is divided into session session aging time aging time aging is the server time, which is the universal standard time, and the
path path matches the file path to access the cookie.
httponly is set to After true, it can prevent js program access, prevent xss attacks, and increase the security of cookies. Secure is
set to true, and cookies can only be sent through https protocol. HTTP protocol cannot be sent. This is also to increase the security of cookies.

76. Anti-currying

The function of anti-curry is that when we call a method, we don’t need to consider whether the object has this method when it is designed. As long as the method is applicable to it, we can use it on the object.

For example

Function.prototype.uncurring = function() {
  var self = this;
  return function() {
    var obj = Array.prototype.shift.call(arguments);
    return self.apply(obj, arguments);
  };
};

Let's take a look at what the above code does.

We want to convert the Array.prototype.push method into a general push function, just do this:

var push = Array.prototype.push.uncurring();

//测试一下
(function() {
  push(arguments, 4);
  console.log(arguments); //[1, 2, 3, 4]
})(1, 2, 3)

There is no push method for arguments. Usually, we need to use Array.prototype.push.call to implement the push method, but now, calling the push function directly is concise and clear.

We don't need to consider whether the object has this method, as long as it applies to this method, then this method can be used (similar to duck typing).

Let's analyze what happens when the code Array.prototype.push.uncurring() is called:

Function.prototype.uncurring = function() {
  var self = this;  //self此时是Array.prototype.push

  return function() {
    var obj = Array.prototype.shift.call(arguments);
    //obj 是{
    //  "length": 1,
    //  "0": 1
    //}
    //arguments的第一个对象被截去(也就是调用push方法的对象),剩下[2]

    return self.apply(obj, arguments);
    //相当于Array.prototype.push.apply(obj, 2);

  };
};

//have a test

var push = Array.prototype.push.uncurring();
var obj = {
  "length": 1,
  "0" : 1
};

push(obj, 2);
console.log( obj ); //{0: 1,1: 2, length: 2 }

Seeing this, you should have a preliminary understanding of currying and anti-currying, but to use them proficiently in development, we need a deeper understanding of their inner meanings.

77, thousandth

The requirement here is essentially to convert a number into a string with a thousandths. There are many methods

Method 1 regular expression

console.info( str.replace(/\d{1,3}(?=(\d{3})+$)/g,function(s){
  return s+','
}) )

Method 2 string replacement

console.info( str.replace(/(\d{1,3})(?=(\d{3})+$)/g,function($1){
  return $1=$1+','
}) )

Method 3 Convert the number to an array and add it after inversion, and then reverse it back and splicing it into a string

console.info( str.split("").reverse().join("").replace(/(\d{3})+?/g,function(s){
  return s+",";
}).replace(/,$/,"").split("").reverse().join("") )

Method 4 Use the while loop to splice strings and add a separator every 3 numbers, without adding the beginning and the end

var result="",
  index = 0,
  len = str.length-1;
while(len>=0) {
  index%3===0&&index!==0 ? result+=","+str[len] : result+=str[len];
  len--;
  index++;
};
result=result.split("").reverse().join("");
console.info(result);

Method 5 Use the while loop to push the separator in the array without adding the beginning and the end

// 利用while循环在数组里push分隔符
var result="",
  index = 0,
  len = str.length,
  i = len-1,
  arr = str.split("");
while(len-index>0){
  len>=index&&len-index!==len && arr.splice(len-index,0,",");
  index+=3;
  i-=4;
};
console.log(arr.join(""));

78. The difference between load and ready

document.ready:

is ready, indicating that the document structure has been loaded and does not include non-text media files such as pictures, as long as the html tag structure is loaded;

document.load:

is onload, indicating that all elements of the page including pictures and other files are loaded Finish.

1. Concept

2. Function

document.ready:

After the DOM is loaded, the DOM can be manipulated.

In general, the sequence of loading a page response is, domain name resolution-loading html-loading js and css-loading pictures and other information.
Then Dom Ready should be between "loading js and css" and "loading pictures and other information", and you can operate Dom.

document.load:

After the document document is loaded, the DOM can be operated. The document document includes other information such as loading pictures.

Then Dom Load is to operate Dom after "loading pictures and other information" in the order of page response loading.

3. Loading order

document.ready:

The order of document loading: domain name resolution --> load HTML --> load JavaScript and CSS --> load non-text media files such as pictures.

As long as the <img> tag is loaded, you can set the attributes or styles of the image without waiting for the image to load.

There is no direct method of Dom ready in native JavaScript.

document.load:

The order of document loading: domain name resolution --> load HTML --> load JavaScript and CSS --> load pictures and other non-text media files.

After DOM load loads non-text media files such as images, it means that the DOM can only be operated after the document file is loaded. The document file includes non-text media files such as loaded images.

For example, you need to wait for the picture to be loaded before you can set the properties or styles of the picture.

Use the onload event in native JavaScript.

79. Custom events

Custom event is to define the event type and event processing function by yourself.

When we usually operate dom, we often use event types of browser-specific behaviors such as onclick and onmousemove.

The basic idea of ​​encapsulating is custom events:

var eventTarget = {
  addEvent: function(){
    //添加事件
  },
  fireEvent: function(){
    //触发事件
  },
  removeEvent: function(){
    //移除事件
  }
};

In the js default event, the event type and the corresponding execution function are in one-to-one correspondence, but for custom events, a mapping table is needed to establish the connection between the two.

Such as: so that each type can handle multiple event functions

handlers = {
      "type1":[
            "fun1",
            "fun2",
            // "..."
         ],
       "type2":[
            "fun1",
            "fun2"
             // "..."
         ]
         //"..."
}

Code:

function EventTarget(){
    //事件处理程序数组集合
    this.handlers={};
}

//自定义事件的原型对象
EventTarget.prototype={
    //设置原型构造函数链
    constructor:EventTarget,
    //注册给定类型的事件处理程序
    //type->自定义事件类型,如click,handler->自定义事件回调函数
    addEvent:function(type,handler){
        //判断事件处理函数中是否有该类型事件
        if(this.handlers[type]==undefined){
            this.handlers[type]=[];
        }
        this.handlers[type].push(handler);
    },

    //触发事件
    //event为一个js对象,属性中至少包含type属性。
    fireEvent:function(event){
        //模拟真实事件的event
        if(!event.target){
            event.target=this;
        }
        //判断是否存在该事件类型
        if(this.handlers[event.type] instanceof Array){
            var items=this.handlers[event.type];
            //在同一事件类型下可能存在多个事件处理函数,依次触发
            //执行触发
            items.forEach(function(item){
                item(event);
            })
        }
    },

    //删除事件
    removeEvent:function(type,handler){
        //判断是否存在该事件类型
        if(this.handlers[type] instanceof Array){
            var items=this.handlers[type];
            //在同一事件类型下可能存在多个处理事件
            for(var i=0;i<items.length;i++){
                if(items[i]==handler){
                    //从该类型的事件数组中删除该事件
                    items.splice(i,1);
                    break;
                }
            }
        }
    }

}

//调用方法
function fun(){
    console.log('执行该方法');
}
function fun1(obj){
    console.log('run '+obj.min+'s');
}
var target=new EventTarget();
target.addEvent("run",fun);//添加事件
target.addEvent("run",fun1);//添加事件

target.fireEvent({type:"run",min:"30"});//执行该方法   123

target.removeEvent("run",fun);//移除事件

target.fireEvent({type:"run",min:"20"});//123

Why add methods to object prototypes?

Add properties to the constructor and methods to the prototype.

It is no problem to write properties and methods in the constructor, but every time the instantiation process is performed, it is necessary to repeatedly create methods with unchanged functions.

Since the method is essentially a function, in fact, a new object space is created in the heap memory to store the storage function, resulting in unnecessary waste of resources.

Adding it in itself will cause the code to be copied every time the object is instantiated, and it is necessary to apply for a piece of memory to store the method.

Write an EventEmitter class, including on(), off(), once(), emit() method once(): register a single listener for a specified event, a single listener is only triggered once at most, and the listener is released immediately after triggering device.

class EventEmitter{
            constructor(){
                this.handlers={};
            }
            on(type,fn){
                if(!this.handlers[type]){
                    this.handlers[type]=[];
                }
                this.handlers[type].push(fn);
                return this;
            }
            off(type,fn){
                let fns=this.handlers[type];
                for(let i=0;i<fns.length;i++){
                    if(fns[i]==fn){
                        fns.splice(i,1);
                        break;
                    }
                }
                return this;
            }
            emit(...args){
                let type=args[0];
                let params=[].slice.call(args,1);
                let fn=this.handlers[type];
                fn.forEach((item)=>{
                    item.apply(this,params);//执行函数
                })
                return this;
            }
            once(type,fn){
                let wrap=(...args)=>{
                    fn.apply(this,args);//执行事件后删除
                    this.off(type,wrap);
                }
                this.on(type,wrap);//再添加上去
                return this;
            }
        }
         let emitter=new EventEmitter();
    function fun1(){
        console.log('fun1');
    }
    function fun2(){
        console.log('fun2');
    }
    function fun3(){
        console.log('fun3');
    }
    emitter.on('TEST1',fun1).on('TEST2',fun2).emit('TEST1').once('TEST2',fun3);
    emitter.emit("TEST2");

80. setTimeout implements setInterval

setTimeout() : Calls a function or computed expression after the specified number of milliseconds, only once.
setInterval() : Calls a function or evaluates an expression at a specified interval (in milliseconds). method will keep calling functions until clearInterval() is called or the window is closed.

The idea is to use a recursive function to continuously execute setTimeout to achieve the effect of setInterval, see the code

function mySetInterval(fn, millisec){
  function interval(){
    setTimeout(interval, millisec);
    fn();
  }
  setTimeout(interval, millisec)
}

The mySetInterval function has an inner function called interval which is automatically called via setTimeout, and inside interval there is a closure which calls the callback function and calls interval again via setTimeout.

For a better implementation we add an additional parameter to indicate the number of code executions

function mySetInterval(fn, millisec,count){
  function interval(){
    if(typeof count===‘undefined’||count-->0){
      setTimeout(interval, millisec);
      try{
        fn()
      }catch(e){
        count = 0;
        throw e.toString();
      }
    }
  }
  setTimeout(interval, millisec)
}

81. Avoid Callback Hell

Using async await with promise is the ultimate solution to callback hell

async/await features

1, async/await更加语义化,async 是“异步”的简写,async function 用于申明一个 function 是异步的;await,可以认为是async wait的简写, 用于等待一个异步方法执行完成;

2, async/await是一个用同步思维解决异步问题的方案(等结果出来之后,代码才会继续往下执行)

3, 可以通过多层 async function 的同步写法代替传统的callback嵌套

async function syntax

1, 自动将常规函数转换成Promise,返回值也是一个Promise对象

2, 只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数

3, 异步函数内部可以使用await

await syntax

1, await 放置在Promise调用之前,await 强制后面点代码等待,直到Promise对象resolve,得到resolve的值作为await表达式的运算结果

2. await只能在async函数内部使用,用在普通函数里就会报错

functional form

function timeout(ms) {

  return new Promise((resolve, reject) => {

    setTimeout(() => {reject('error')}, ms);  //reject模拟出错,返回error

  });

}

async function asyncPrint(ms) {

  try {

     console.log('start');

     await timeout(ms);  //这里返回了错误

     console.log('end');  //所以这句代码不会被执行了

  } catch(err) {

     console.log(err); //这里捕捉到错误error

  }

}

82. The role of callee and caller

caller returns a reference to a function that calls the current function; callee puts back a reference to the executing function itself, which is an attribute of arguments

callercaller returns a reference to the function that called the current function. Pay attention to using this property: 1 This property is only useful when the function is executing 2 If in the javascript program, the function is called by the top level, return null

functionName.caller: functionName是当前正在执行的函数。
var a = function() {
    alert(a.caller);
}
var b = function() {
    a();
}
b();

In the above code, b calls a, then a.caller returns a reference to b, and the result is as follows:

var b = function() {
    a();
}

If a is called directly (that is, a is called in any function, that is, the top-level call), null is returned:

var a = function() {
    alert(a.caller);
}
var b = function() {
    a();
}
//b();
a();
输出结果:
null

silence

callee puts back a reference to the executing function itself, which is a property of arguments

Pay attention when using callee: callee callee puts back the reference of the function itself that is being executed, which is an attribute of arguments. Pay attention when using callee:

1 这个属性只有在函数执行时才有效

2 它有一个length属性,可以用来获得形参的个数,因此可以用来比较形参和实参个数是否一致,即比较arguments.length是否等于arguments.callee.length

3 它可以用来递归匿名函数。

var a = function() {
    alert(arguments.callee);
}
var b = function() {
    a();
}
b();


a在b中被调用,但是它返回了a本身的引用,结果如下:

var a = function() {
    alert(arguments.callee);
}

 

83. Count the number of letters in a string or count the most letters

Count the number of occurrences of letters

function count( str ){
    var obj={};
    for(var i=0;i<str.length; i++){
        if(obj[ str[i] ]==undefined){
            //对象初始化;如果key在对象中找不到,那么会返回undefined,反向思维
            obj[ str[i] ]= 1;
        } else{
            obj[ str[i] ]++;
        }
    }

    //取出各个字母和它的个数,作为一个新对象保存在obj对象中
    return obj;

}

cosnle.log( count( "shhkfahkahsadhadskhdskdha" ) );

The letter with the most occurrences of the statistical character

function allProMax(obj){
    var mm="";
    for(var m in obj){
        if(mm==""){
            mm=new Object();
            mm[m]=obj[m];
        }else{
            for(var j in mm){
               if(mm[j]<obj[m]){
                   //清空原来的内容
                   mm=new Object();
                   //放入新的内容
                   mm[m]=obj[m];
               }
            }
        }
    }
    return mm ;
}

console.log( allProMax(count()) )

84. The difference between object-oriented and process-oriented

一、面向对象与面向过程的区别
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了;面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。

可以拿生活中的实例来理解面向过程与面向对象,例如五子棋,面向过程的设计思路就是首先分析问题的步骤:1、开始游戏,2、黑子先走,3、绘制画面,4、判断输赢,5、轮到白子,6、绘制画面,7、判断输赢,8、返回步骤2,9、输出最后结果。把上面每个步骤用不同的方法来实现。

如果是面向对象的设计思想来解决问题。面向对象的设计则是从另外的思路来解决问题。整个五子棋可以分为1、黑白双方,这两方的行为是一模一样的,2、棋盘系统,负责绘制画面,3、规则系统,负责判定诸如犯规、输赢等。第一类对象(玩家对象)负责接受用户输入,并告知第二类对象(棋盘对象)棋子布局的变化,棋盘对象接收到了棋子的变化就要负责在屏幕上面显示出这种变化,同时利用第三类对象(规则系统)来对棋局进行判定。

可以明显地看出,面向对象是以功能来划分问题,而不是步骤。同样是绘制棋局,这样的行为在面向过程的设计中分散在了多个步骤中,很可能出现不同的绘制版本,因为通常设计人员会考虑到实际情况进行各种各样的简化。而面向对象的设计中,绘图只可能在棋盘对象中出现,从而保证了绘图的统一。

上述的内容是从网上查到的,觉得这个例子非常的生动形象,我就写了下来,现在就应该理解了他俩的区别了吧,其实就是两句话,面向对象就是高度实物抽象化、面向过程就是自顶向下的编程!

二、面向对象的特点
在了解其特点之前,咱们先谈谈对象,对象就是现实世界存在的任何事务都可以称之为对象,有着自己独特的个性

1, 概念 对 具有相同特性的一类事物的抽象描述

2, 组成 属性 和 方法

3, 模板 构造函数

4, 特点 封装 继承 多态

属性用来描述具体某个对象的特征。比如小志身高180M,体重70KG,这里身高、体重都是属性。
面向对象的思想就是把一切都看成对象,而对象一般都由属性+方法组成!

属性属于对象静态的一面,用来形容对象的一些特性,方法属于对象动态的一面,咱们举一个例子,小明会跑,会说话,跑、说话这些行为就是对象的方法!所以为动态的一面, 我们把属性和方法称为这个对象的成员!

类:具有同种属性的对象称为类,是个抽象的概念。比如“人”就是一类,期中有一些人名,比如小明、小红、小玲等等这些都是对象,类就相当于一个模具,他定义了它所包含的全体对象的公共特征和功能,对象就是类的一个实例化,小明就是人的一个实例化!我们在做程序的时候,经常要将一个变量实例化,就是这个原理!我们一般在做程序的时候一般都不用类名的,比如我们在叫小明的时候,不会喊“人,你干嘛呢!”而是说的是“小明,你在干嘛呢!”

面向对象有三大特性,分别是封装性、继承性和多态性,这里小编不给予太多的解释,因为在后边的博客会专门总结的!

三、面向过程与面向对象的优缺点
很多资料上全都是一群很难理解的理论知识,整的小编头都大了,后来发现了一个比较好的文章,写的真是太棒了,通俗易懂,想要不明白都难!

用面向过程的方法写出来的程序是一份蛋炒饭,而用面向对象写出来的程序是一份盖浇饭。所谓盖浇饭,北京叫盖饭,东北叫烩饭,广东叫碟头饭,就是在一碗白米饭上面浇上一份盖菜,你喜欢什么菜,你就浇上什么菜。我觉得这个比喻还是比较贴切的。

蛋炒饭制作的细节,我不太清楚,因为我没当过厨师,也不会做饭,但最后的一道工序肯定是把米饭和鸡蛋混在一起炒匀。盖浇饭呢,则是把米饭和盖菜分别做好,你如果要一份红烧肉盖饭呢,就给你浇一份红烧肉;如果要一份青椒土豆盖浇饭,就给浇一份青椒土豆丝。

蛋炒饭的好处就是入味均匀,吃起来香。如果恰巧你不爱吃鸡蛋,只爱吃青菜的话,那么唯一的办法就是全部倒掉,重新做一份青菜炒饭了。盖浇饭就没这么多麻烦,你只需要把上面的盖菜拨掉,更换一份盖菜就可以了。盖浇饭的缺点是入味不均,可能没有蛋炒饭那么香。

到底是蛋炒饭好还是盖浇饭好呢?其实这类问题都很难回答,非要比个上下高低的话,就必须设定一个场景,否则只能说是各有所长。如果大家都不是美食家,没那么多讲究,那么从饭馆角度来讲的话,做盖浇饭显然比蛋炒饭更有优势,他可以组合出来任意多的组合,而且不会浪费。

盖浇饭的好处就是"菜"“饭"分离,从而提高了制作盖浇饭的灵活性。饭不满意就换饭,菜不满意换菜。用软件工程的专业术语就是"可维护性"比较好,“饭” 和"菜"的耦合度比较低。蛋炒饭将"蛋”“饭"搅和在一起,想换"蛋”"饭"中任何一种都很困难,耦合度很高,以至于"可维护性"比较差。软件工程追求的目标之一就是可维护性,可维护性主要表现在3个方面:可理解性、可测试性和可修改性。面向对象的好处之一就是显著的改善了软件系统的可维护性。

我们最后简单总结一下

面向过程

优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、 Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
缺点:没有面向对象易维护、易复用、易扩展


面向对象

优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护
缺点:性能比面向过程低

85、eval

eval()是全局对象的一个函数属性。

eval()的参数是一个字符串。如果字符串表示的是表达式,eval()会对表达式进行求值。如果参数表示一个或多个JavaScript语句, 那么eval()就会执行这些语句。注意不要用eval()来执行一个四则运算表达式;因为 JavaScript 会自动为四则运算求值并不需要用eval来包裹。

这里的四则运算是指数学上的运算,如:3 + 4 * 4 / 6。注意这里面并没有变量,只是单纯的数学运算,这样的运算式并不需要调用eval来计算,直接在代码中计算就可以。其实即便带有变量,JavaScript也是可以直接计算的,但是如果你现在只想声明一个带有变量的表达式,但是想稍后进行运算(你有可能在声明这个带有变量的运算式之后还有可能对里面的变量进行修改),就可以使用eval。
如果要将算数表达式构造成为一个字符串,你可以用eval()在随后对其求值。比如,假如你有一个变量 x ,你可以通过一个字符串表达式来对涉及x的表达式延迟求值,将 “3 * x + 2”,存储为变量,然后在你的脚本后面的一个地方调用eval()。

如果eval()的参数不是字符串,eval()将会将参数原封不动的返回。在下面的例子中,字符串构造器被指定,eval()返回了字符串对象而不是对字符串求值。

// 返回了包含"2 + 2"的字符串对象
eval(new String("2 + 2"));

// returns 4
eval("2 + 2");


eval() 是一个危险的函数, 他执行的代码拥有着执行者的权利。如果你用eval()运行的字符串代码被恶意方(不怀好意的人)操控修改,您可能会利用最终在用户机器上运行恶意方部署的恶意代码,并导致您失去您的网页或者扩展程序的权限。更重要的是,第三方代码可以看到某一个eval()被调用时的作用域,这也有可能导致一些不同方式的攻击。相似的Function就是不容易被攻击的。

eval()的运行效率也普遍的比其他的替代方案慢,因为他会调用js解析器,即便现代的JS引擎中已经对此做了优化。

在常见的案例中我们都会找更安全或者更快的方案去替换他

86、proxy

One, proxy

The proxy builds a layer of interception on the outer layer of the target object, and certain operations on the target object must pass through this layer of interception

var proxy = new Proxy(target, handler);
new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为

var target = {
  name: 'poetries'
};
var logHandler = {
  get: function(target, key) {
    console.log(`${key} 被读取`);
    return target[key];
  },
  set: function(target, key, value) {
    console.log(`${key} 被设置为 ${value}`);
    target[key] = value;
  }
}
var targetWithLog = new Proxy(target, logHandler);

targetWithLog.name; // 控制台输出:name 被读取
targetWithLog.name = 'others'; // 控制台输出:name 被设置为 others

console.log(target.name); // 控制台输出: others
targetWithLog 读取属性的值时,实际上执行的是 logHandler.get :在控制台输出信息,并且读取被代理对象 target 的属性。
在 targetWithLog 设置属性值时,实际上执行的是 logHandler.set :在控制台输出信息,并且设置被代理对象 target 的属性的值
// 由于拦截函数总是返回35,所以访问任何属性都得到35
var proxy = new Proxy({}, {
get: function(target, property) {
  return 35;
}
});

proxy.time // 35
proxy.name // 35
proxy.title // 35
Proxy 实例也可以作为其他对象的原型对象

var proxy = new Proxy({}, {
get: function(target, property) {
  return 35;
}
});

let obj = Object.create(proxy);
obj.time // 35

The proxy object is the prototype of the obj object, and the obj object itself does not have a time attribute, so according to the prototype chain, the attribute will be read on the proxy object, causing it to be intercepted

The role of proxy

The role of proxy mode Proxy is mainly reflected in three aspects

Intercept and monitor external access to objects Reduce the complexity of functions or classes Verify operations before complex operations or manage required resources

2. The scope that Proxy can act as agent --handler

实际上 handler 本身就是ES6所新设计的一个对象.它的作用就是用来 自定义代理对象的各种可代理操作 。它本身一共有13中方法,每种方法都可以代理一种操作.其13种方法如下

// 在读取代理对象的原型时触发该操作,比如在执行 Object.getPrototypeOf(proxy) 时。
handler.getPrototypeOf()

// 在设置代理对象的原型时触发该操作,比如在执行 Object.setPrototypeOf(proxy, null) 时。
handler.setPrototypeOf()


// 在判断一个代理对象是否是可扩展时触发该操作,比如在执行 Object.isExtensible(proxy) 时。
handler.isExtensible()


// 在让一个代理对象不可扩展时触发该操作,比如在执行 Object.preventExtensions(proxy) 时。
handler.preventExtensions()

// 在获取代理对象某个属性的属性描述时触发该操作,比如在执行 Object.getOwnPropertyDescriptor(proxy, "foo") 时。
handler.getOwnPropertyDescriptor()


// 在定义代理对象某个属性时的属性描述时触发该操作,比如在执行 Object.defineProperty(proxy, "foo", {}) 时。
andler.defineProperty()


// 在判断代理对象是否拥有某个属性时触发该操作,比如在执行 "foo" in proxy 时。
handler.has()

// 在读取代理对象的某个属性时触发该操作,比如在执行 proxy.foo 时。
handler.get()


// 在给代理对象的某个属性赋值时触发该操作,比如在执行 proxy.foo = 1 时。
handler.set()

// 在删除代理对象的某个属性时触发该操作,比如在执行 delete proxy.foo 时。
handler.deleteProperty()

// 在获取代理对象的所有属性键时触发该操作,比如在执行 Object.getOwnPropertyNames(proxy) 时。
handler.ownKeys()

// 在调用一个目标对象为函数的代理对象时触发该操作,比如在执行 proxy() 时。
handler.apply()


// 在给一个目标对象为构造函数的代理对象构造实例时触发该操作,比如在执行new proxy() 时。
handler.construct()

3. Proxy scene

3.1 Implement private variables

var target = {
   name: 'poetries',
   _age: 22
}

var logHandler = {
  get: function(target,key){
    if(key.startsWith('_')){
      console.log('私有变量age不能被访问')
      return false
    }
    return target[key];
  },
  set: function(target, key, value) {
     if(key.startsWith('_')){
      console.log('私有变量age不能被修改')
      return false
    }
     target[key] = value;
   }
}
var targetWithLog = new Proxy(target, logHandler);

// 私有变量age不能被访问
targetWithLog.name;

// 私有变量age不能被修改
targetWithLog.name = 'others';
在下面的代码中,我们声明了一个私有的 apiKey,便于 api 这个对象内部的方法调用,但不希望从外部也能够访问 api._apiKey

var api = {
    _apiKey: '123abc456def',
    /* mock methods that use this._apiKey */
    getUsers: function(){},
    getUser: function(userId){},
    setUser: function(userId, config){}
};

// logs '123abc456def';
console.log("An apiKey we want to keep private", api._apiKey);

// get and mutate _apiKeys as desired
var apiKey = api._apiKey;
api._apiKey = '987654321';
很显然,约定俗成是没有束缚力的。使用 ES6 Proxy 我们就可以实现真实的私有变量了,下面针对不同的读取方式演示两个不同的私有化方法。第一种方法是使用 set / get 拦截读写请求并返回 undefined:

let api = {
    _apiKey: '123abc456def',
    getUsers: function(){ },
    getUser: function(userId){ },
    setUser: function(userId, config){ }
};

const RESTRICTED = ['_apiKey'];
api = new Proxy(api, {
    get(target, key, proxy) {
        if(RESTRICTED.indexOf(key) > -1) {
            throw Error(`${key} is restricted. Please see api documentation for further info.`);
        }
        return Reflect.get(target, key, proxy);
    },
    set(target, key, value, proxy) {
        if(RESTRICTED.indexOf(key) > -1) {
            throw Error(`${key} is restricted. Please see api documentation for further info.`);
        }
        return Reflect.get(target, key, value, proxy);
    }
});

// 以下操作都会抛出错误
console.log(api._apiKey);
api._apiKey = '987654321';
第二种方法是使用 has 拦截 in 操作

var api = {
    _apiKey: '123abc456def',
    getUsers: function(){ },
    getUser: function(userId){ },
    setUser: function(userId, config){ }
};

const RESTRICTED = ['_apiKey'];
api = new Proxy(api, {
    has(target, key) {
        return (RESTRICTED.indexOf(key) > -1) ?
            false :
            Reflect.has(target, key);
    }
});

// these log false, and `for in` iterators will ignore _apiKey
console.log("_apiKey" in api);

for (var key in api) {
    if (api.hasOwnProperty(key) && key === "_apiKey") {
        console.log("This will never be logged because the proxy obscures _apiKey...")
    }
}

3.2 Extraction verification module

让我们从一个简单的类型校验开始做起,这个示例演示了如何使用 Proxy 保障数据类型的准确性

let numericDataStore = {
    count: 0,
    amount: 1234,
    total: 14
};

numericDataStore = new Proxy(numericDataStore, {
    set(target, key, value, proxy) {
        if (typeof value !== 'number') {
            throw Error("Properties in numericDataStore can only be numbers");
        }
        return Reflect.set(target, key, value, proxy);
    }
});

// 抛出错误,因为 "foo" 不是数值
numericDataStore.count = "foo";

// 赋值成功
numericDataStore.count = 333;
如果要直接为对象的所有属性开发一个校验器可能很快就会让代码结构变得臃肿,使用 Proxy 则可以将校验器从核心逻辑分离出来自成一体

function createValidator(target, validator) {
    return new Proxy(target, {
        _validator: validator,
        set(target, key, value, proxy) {
            if (target.hasOwnProperty(key)) {
                let validator = this._validator[key];
                if (!!validator(value)) {
                    return Reflect.set(target, key, value, proxy);
                } else {
                    throw Error(`Cannot set ${key} to ${value}. Invalid.`);
                }
            } else {
                throw Error(`${key} is not a valid property`)
            }
        }
    });
}

const personValidators = {
    name(val) {
        return typeof val === 'string';
    },
    age(val) {
        return typeof age === 'number' && val > 18;
    }
}
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
        return createValidator(this, personValidators);
    }
}

const bill = new Person('Bill', 25);

// 以下操作都会报错
bill.name = 0;
bill.age = 'Bill';
bill.age = 15;
通过校验器和主逻辑的分离,你可以无限扩展 personValidators 校验器的内容,而不会对相关的类或函数造成直接破坏。更复杂一点,我们还可以使用 Proxy 模拟类型检查,检查函数是否接收了类型和数量都正确的参数

let obj = {
    pickyMethodOne: function(obj, str, num) { /* ... */ },
    pickyMethodTwo: function(num, obj) { /*... */ }
};

const argTypes = {
    pickyMethodOne: ["object", "string", "number"],
    pickyMethodTwo: ["number", "object"]
};

obj = new Proxy(obj, {
    get: function(target, key, proxy) {
        var value = target[key];
        return function(...args) {
            var checkArgs = argChecker(key, args, argTypes[key]);
            return Reflect.apply(value, target, args);
        };
    }
});

function argChecker(name, args, checkers) {
    for (var idx = 0; idx < args.length; idx++) {
        var arg = args[idx];
        var type = checkers[idx];
        if (!arg || typeof arg !== type) {
            console.warn(`You are incorrectly implementing the signature of ${name}. Check param ${idx + 1}`);
        }
    }
}

obj.pickyMethodOne();
// > You are incorrectly implementing the signature of pickyMethodOne. Check param 1
// > You are incorrectly implementing the signature of pickyMethodOne. Check param 2
// > You are incorrectly implementing the signature of pickyMethodOne. Check param 3

obj.pickyMethodTwo("wopdopadoo", {});
// > You are incorrectly implementing the signature of pickyMethodTwo. Check param 1

// No warnings logged
obj.pickyMethodOne({}, "a little string", 123);
obj.pickyMethodOne(123, {});

3.3 Access log

对于那些调用频繁、运行缓慢或占用执行环境资源较多的属性或接口,开发者会希望记录它们的使用情况或性能表现,这个时候就可以使用 Proxy 充当中间件的角色,轻而易举实现日志功能

let api = {
    _apiKey: '123abc456def',
    getUsers: function() { /* ... */ },
    getUser: function(userId) { /* ... */ },
    setUser: function(userId, config) { /* ... */ }
};

function logMethodAsync(timestamp, method) {
    setTimeout(function() {
        console.log(`${timestamp} - Logging ${method} request asynchronously.`);
    }, 0)
}

api = new Proxy(api, {
    get: function(target, key, proxy) {
        var value = target[key];
        return function(...arguments) {
            logMethodAsync(new Date(), key);
            return Reflect.apply(value, target, arguments);
        };
    }
});

api.getUsers();

3.4 Early warning and interception

假设你不想让其他开发者删除 noDelete 属性,还想让调用 oldMethod 的开发者了解到这个方法已经被废弃了,或者告诉开发者不要修改 doNotChange 属性,那么就可以使用 Proxy 来实现

let dataStore = {
    noDelete: 1235,
    oldMethod: function() {/*...*/ },
    doNotChange: "tried and true"
};

const NODELETE = ['noDelete'];
const NOCHANGE = ['doNotChange'];
const DEPRECATED = ['oldMethod'];

dataStore = new Proxy(dataStore, {
    set(target, key, value, proxy) {
        if (NOCHANGE.includes(key)) {
            throw Error(`Error! ${key} is immutable.`);
        }
        return Reflect.set(target, key, value, proxy);
    },
    deleteProperty(target, key) {
        if (NODELETE.includes(key)) {
            throw Error(`Error! ${key} cannot be deleted.`);
        }
        return Reflect.deleteProperty(target, key);

    },
    get(target, key, proxy) {
        if (DEPRECATED.includes(key)) {
            console.warn(`Warning! ${key} is deprecated.`);
        }
        var val = target[key];

        return typeof val === 'function' ?
            function(...args) {
                Reflect.apply(target[key], target, args);
            } :
            val;
    }

});

// these will throw errors or log warnings, respectively
dataStore.doNotChange = "foo";
delete dataStore.noDelete;
dataStore.oldMethod();

3.5 Filter operation

某些操作会非常占用资源,比如传输大文件,这个时候如果文件已经在分块发送了,就不需要在对新的请求作出相应(非绝对),这个时候就可以使用 Proxy 对当请求进行特征检测,并根据特征过滤出哪些是不需要响应的,哪些是需要响应的。下面的代码简单演示了过滤特征的方式,并不是完整代码,相信大家会理解其中的妙处

let obj = {
    getGiantFile: function(fileId) {/*...*/ }
};

obj = new Proxy(obj, {
    get(target, key, proxy) {
        return function(...args) {
            const id = args[0];
            let isEnroute = checkEnroute(id);
            let isDownloading = checkStatus(id);
            let cached = getCached(id);

            if (isEnroute || isDownloading) {
                return false;
            }
            if (cached) {
                return cached;
            }
            return Reflect.apply(target[key], target, args);
        }
    }

});

おすすめ

転載: blog.csdn.net/sdasadasds/article/details/132041731