>  기사  >  웹 프론트엔드  >  JS 기능을 위한 5가지 고급 기술 공유

JS 기능을 위한 5가지 고급 기술 공유

青灯夜游
青灯夜游앞으로
2020-10-26 17:57:241654검색

JS 기능을 위한 5가지 고급 기술 공유

함수는 이벤트 기반이거나 호출 시 실행되는 재사용 가능한 코드 블록입니다. 함수는 모든 언어, 특히 JavaScript의 핵심 개념입니다. 이 문서에서는 함수에 대한 5가지 고급 기술을 심층적으로 소개합니다.

범위 안전 생성자

생성자는 실제로 new 연산자를 사용하여 호출되는 함수입니다

function Person(name,age,job){
    this.name=name;    
    this.age=age;    
    this.job=job;
}
var person=new Person('match',28,'Software Engineer');
console.log(person.name);//match

new 연산자를 사용하지 않으면 원래 Person 개체를 대상으로 한 세 가지 속성이 창 개체에 추가됩니다

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
}
var person = Person('match', 28, 'Software Engineer');
console.log(person); //undefinedconsole.log(window.name);//match

창의 이름 속성은 링크 대상과 프레임을 식별하는 데 사용됩니다. 여기서 이 속성을 실수로 덮어쓰면 페이지에서 다른 오류가 발생할 수 있습니다. 이 문제에 대한 해결책은 범위 안전 생성자를 만드는 것입니다.

function Person(name, age, job) {
    if (this instanceof Person) {
        this.name = name;
        this.age = age;
        this.job = job;
    } else {
        return new Person(name, age, job);
    }
}
var person = Person('match', 28, 'Software Engineer');
console.log(window.name); // ""
console.log(person.name); //'match'
var person= new Person('match',28,'Software Engineer');
console.log(window.name); // ""
console.log(person.name); //'match'

그러나 생성자 훔치기 패턴을 상속하면 부작용이 발생합니다. 이는 다음 코드에서 this 개체가 Polygon 개체의 인스턴스가 아니므로 생성자 Polygon()이 새 인스턴스를 만들고 반환하기 때문입니다.

function Polygon(sides) {
    if (this instanceof Polygon) {
        this.sides = sides;
        this.getArea = function() {
            return 0;
        }
    } else {
        return new Polygon(sides);
    }
}
function Rectangle(wifth, height) {
    Polygon.call(this, 2);
    this.width = this.width;
    this.height = height;
    this.getArea = function() {
        return this.width * this.height;
    };
}
var rect = new Rectangle(5, 10);
console.log(rect.sides); //undefined

범위 안전 생성자 도용 패턴을 사용하려면 프로토타입 체인 상속을 결합하고 Rectangle의 프로토타입 속성을 다시 작성하여 인스턴스도 Polygon의 인스턴스가 되도록 해야 합니다.

function Polygon(sides) {
    if (this instanceof Polygon) {
        this.sides = sides;
        this.getArea = function() {
            return 0;
        }
    } else {
        return new Polygon(sides);
    }
}
function Rectangle(wifth, height) {
    Polygon.call(this, 2);
    this.width = this.width;
    this.height = height;
    this.getArea = function() {
        return this.width * this.height;
    };
}
Rectangle.prototype = new Polygo

지연 로딩 기능

브라우저 간의 동작 차이로 인해 브라우저 특성을 확인하고 다양한 브라우저의 호환성 문제를 해결하기 위해 함수에 많은 if 문을 포함하는 경우가 많습니다. 예를 들어 DOM 노드에 이벤트를 추가하는 가장 일반적인 기능은 addEvent 함수가 호출될 때마다 먼저 addEventListener 메소드가 지원되는지 확인해야 합니다. attachmentEvent 메소드가 지원되는지 여부 아직 지원되지 않는 경우 dom0 레벨 메소드를 사용하여 이벤트를 추가하십시오.

이 프로세스는 addEvent 함수가 호출될 때마다 수행되어야 합니다. 실제로 브라우저가 메서드 중 하나를 지원하면 항상 해당 메서드를 지원하므로 다른 분기를 감지할 필요가 없습니다. 즉, if 문을 매번 실행할 필요가 없고, 코드 실행 속도가 더 빨라진다.

해결책은 지연 로딩입니다. 소위 지연 로딩은 함수 실행 분기가 한 번만 발생한다는 것을 의미합니다. 지연 로딩을 구현하는 방법에는 두 가지가 있습니다. 첫 번째는 함수가 호출될 때 함수를 처리하는 것입니다. 함수가 처음 호출되면 해당 함수는 적절한 방식으로 실행되는 다른 함수로 덮어쓰므로 원래 함수에 대한 호출은 실행 분기를 거칠 필요가 없습니다. 다음과 같은 방법으로 addEvent()

function addEvent(type, element, fun) {
    if (element.addEventListener) {
        element.addEventListener(type, fun, false);
    } else if (element.attachEvent) {
        element.attachEvent('on' + type, fun);
    } else {
        element['on' + type] = fun;
    }
}

를 다시 작성하세요. 지연 로드된 addEvent()에서 if 문의 각 분기는 addEvent 변수에 값을 할당하여 원래 함수를 효과적으로 덮습니다. 마지막 단계는 새로운 할당 함수를 호출하는 것입니다. 다음에 addEvent()가 호출되면 새로 할당된 함수가 직접 호출되므로 if 문을 실행할 필요가 없습니다.

그러나 이 방법에는 함수 이름이 변경되면 수정하기가 더 번거롭다는 단점이 있습니다.

2. 두 번째는 함수를 선언할 때 적절한 함수를 지정하는 것입니다. 이런 방식으로 함수가 처음 호출될 때 성능 손실은 없지만 코드가 로드될 때 약간의 성능 손실만 발생합니다. 다음은 이 아이디어에 따라 다시 작성된 addEvent() 입니다. 다음 코드는 어떤 함수 구현을 사용해야 하는지 결정하기 위해 다양한 함수를 통해 분기하는 익명의 자체 실행 함수를 만듭니다.

function addEvent(type, element, fun) {
    if (element.addEventListener) {
        addEvent = function(type, element, fun) {
            element.addEventListener(type, fun, false);
        }
    } else if (element.attachEvent) {
        addEvent = function(type, element, fun) {
            element.attachEvent('on' + type, fun);
        }
    } else {
        addEvent = function(type, element, fun) {
            element['on' + type] = fun;
        }
    }
    return addEvent(type, element, fun);
}

함수 바인딩

javascript와 DOM 간의 상호 작용에서는 함수를 정의한 다음 이를 특정 DOM 요소나 컬렉션의 이벤트 트리거에 바인딩해야 하는 경우가 많습니다. 콜백 함수와 함께 사용되며 코드 실행 환경을 유지하면서 함수를 변수로 전달하기 위해 이벤트 핸들러와 함께 사용됩니다.

var addEvent = (function() {
    if (document.addEventListener) {
        return function(type, element, fun) {
            element.addEventListener(type, fun, false);
        }
    } else if (document.attachEvent) {
        return function(type, element, fun) {
            element.attachEvent('on' + type, fun);
        }
    } else {
        return function(type, element, fun) {
            element['on' + type] = fun;
        }
    }
})();

위 코드는 handler라는 객체를 생성합니다. handler.handlerFun() 메서드는 DOM 버튼에 대한 이벤트 핸들러로 할당됩니다. 버튼을 누르면 이 함수가 호출되고 경고 상자가 표시됩니다. 경고창에는 Event handler가 표시되어야 할 것 같지만 실제로는 undefiend라고 표시됩니다. 문제는 handler.handleClick() 환경이 저장되지 않아 이 객체가 결국 핸들러 대신 DOM 버튼을 가리키게 된다는 것입니다.

이 문제는 클로저를 사용하여 해결할 수 있습니다

<button id="btn">按钮</button>
<script>var handler = {
    message: "Event handled.",
    handlerFun: function() {
      alert(this.message);
    }
  };
  btn.onclick = handler.handlerFun;</script>

물론 이는 이 시나리오에 특정한 솔루션입니다. 클로저를 여러 개 만들면 코드를 이해하고 디버그하기가 어려워질 수 있습니다. 더 나은 접근 방식은 함수 바인딩을 사용하는 것입니다.

간단한 바인딩 함수인 바인드()는 함수와 환경을 받아들이고 주어진 환경에서 주어진 함수를 호출하는 함수를 반환하며 모든 매개변수를 변경하지 않고 전달합니다.

<button id="btn">按钮</button>
<script>var handler = {
    message: "Event handled.",
    handlerFun: function() {
      alert(this.message);
    }
  };
  btn.onclick = function() {
    handler.handlerFun();
  }</script>

이 기능은 간단해 보이지만 매우 강력합니다. 클로저는 바인드()에서 생성됩니다. 클로저는 apply()를 사용하여 들어오는 함수를 호출하고 컨텍스트 객체와 매개변수를 apply()에 전달합니다. 반환된 함수가 호출되면 지정된 환경에서 전달된 함수를 실행하고 모든 인수를 제공합니다.

function bind(fn, context) {
    return function() {
        return fn.apply(context, arguments);
    }
}

ECMAScript5는 모든 함수에 대한 기본 바인딩() 메서드를 정의하여 작업을 더욱 단순화합니다.

只要是将某个函数指针以值的形式进行传递,同时该函数必须在特定环境中执行,被绑定函数的效用就突显出来了。它们主要用于事件处理程序以及setTimeout()和setInterval()。

然而,被绑定函数与普通函数相比有更多的开销,它们需要更多内存,同时也因为多重函数调用稍微慢一点,所以最好只在必要时使用。

函数柯里化

与函数绑定紧密相关的主题是函数柯里化(function currying),它用于创建已经设置好了一个或多个参数的函数。函数柯里化的基本方法和函数绑定是一样的:使用一个闭包返回一个函数。两者的区别在于,当函数被调用时,返回的函数还需要设置一些传入的参数。

function add(num1, num2) {
    return num1 + num2;
}
function curriedAdd(num2) {
    return add(5, num2);
}
console.log(add(2, 3)); //5
console.log(curriedAdd(3));//8

这段代码定义了两个函数:add()和curriedAdd()。后者本质上是在任何情况下第一个参数为5的add()版本。尽管从技术来说curriedAdd()并非柯里化的函数,但它很好地展示了其概念。

柯里化函数通常由以下步骤动态创建:调用另一个函数并为它传入要柯里化的函数和必要参数。

下面是创建柯里化函数的通用方式:

function curry(fn) {
    var args = Array.prototype.slice.call(arguments, 1);
    return function() {
        var innerArgs = Array.prototype.slice.call(arguments),
        finalArgs = args.concat(innerArgs);
        return fn.apply(null, finalArgs);
    };
}

curry()函数的主要工作就是将被返回函数的参数进行排序。curry()的第一个参数是要进行柯里化的函数,其他参数是要传入的值。

为了获取第一个参数之后的所有参数,在arguments对象上调用了slice()方法,并传入参数1表示被返回的数组包含从第二个参数开始的所有参数。然后args数组包含了来自外部函数的参数。在内部函数中,创建了innerArgs数组用来存放所有传入的参数(又一次用到了slice())。

有了存放来自外部函数和内部函数的参数数组后,就可以使用concat()方法将它们组合为finalArgs,然后使用apply()将结果传递给函数。注意这个函数并没有考虑到执行环境,所以调用apply()时第一个参数是null。curry()函数可以按以下方式应用。

function add(num1, num2) {
    return num1 + num2;
}
var curriedAdd = curry(add, 5);
alert(curriedAdd(3)); //8

在这个例子中,创建了第一个参数绑定为5的add()的柯里化版本。当调用cuurriedAdd()并传入3时,3会成为add()的第二个参数,同时第一个参数依然是5,最后结果便是和8。也可以像下例这样给出所有的函数参数:

function add(num1, num2) {
    return num1 + num2;
}
var curriedAdd2 = curry(add, 5, 12);
alert(curriedAdd2()); //17

在这里,柯里化的add()函数两个参数都提供了,所以以后就无需再传递给它们了,函数柯里化还常常作为函数绑定的一部分包含在其中,构造出更为复杂的bind()函数。

function bind(fn, context) {
    var args = Array.prototype.slice.call(arguments, 2);
    return function() {
        var innerArgs = Array.prototype.slice.call(arguments),
        finalArgs = args.concat(innerArgs);
        return fn.apply(context, finalArgs);
    };
}

对curry()函数的主要更改在于传入的参数个数,以及它如何影响代码的结果。curry()仅仅接受一个要包裹的函数作为参数,而bind()同时接受函数和一个object对象。

这表示给被绑定的函数的参数是从第三个开始而不是第二个,这就要更改slice()的第一处调用。另一处更改是在倒数第3行将object对象传给apply()。当使用bind()时,它会返回绑定到给定环境的函数,并且可能它其中某些函数参数已经被设好。

要想除了event对象再额外给事件处理程序传递参数时,这非常有用。

var handler = {
    message: "Event handled",
    handleClick: function(name, event){
        alert(this.message + ":" + name + ":" + event.type);
    }
};var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler, "my-btn"));

handler.handleClick()方法接受了两个参数:要处理的元素的名字和event对象。作为第三个参数传递给bind()函数的名字,又被传递给了handler.handleClick(),而handler.handleClick()也会同时接收到event对象。

ECMAScript5的bind()方法也实现函数柯里化,只要在this的值之后再传入另一个参数即可。

var handler = {
    message: "Event handled",
    handleClick: function(name, event) {
        alert(this.message + ":" + name + ":" + event.type);
    }
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler, "my-btn"));

javaScript中的柯里化函数和绑定函数提供了强大的动态函数创建功能。使用bind()还是curry()要根据是否需要object对象响应来决定。它们都能用于创建复杂的算法和功能,当然两者都不应滥用,因为每个函数都会带来额外的开销。

函数重写

由于一个函数可以返回另一个函数,因此可以用新的函数来覆盖旧的函数。

function a(){
    console.log(&#39;a&#39;);
    a = function(){
        console.log(&#39;b&#39;);
    }
}

这样一来,当我们第一次调用该函数时会console.log('a')会被执行;全局变量a被重定义,并被赋予新的函数

当该函数再次被调用时, console.log('b')会被执行。

再复杂一点的情况如下所示:

var a = (function() {
    function someSetup() {
        var setup = &#39;done&#39;;
    }
    function actualWork() {
        console.log(&#39;work&#39;);
    }
    someSetup();
    return actualWork;
})()

我们使用了私有函数someSetup()和actualWork(),当函数a()第一次被调用时,它会调用someSetup(),并返回函数actualWork()的引用。

相关免费学习推荐:js视频教程

更多编程相关知识,请访问:编程入门!!

위 내용은 JS 기능을 위한 5가지 고급 기술 공유의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 51cto.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제