>웹 프론트엔드 >JS 튜토리얼 >JavaScript_javascript 스킬에 대한 자세한 설명

JavaScript_javascript 스킬에 대한 자세한 설명

WBOY
WBOY원래의
2016-05-16 15:41:131308검색

자바스크립트는 매우 유연한 언어라고 하지만 사실은 혼란스러운 언어라고도 할 수 있습니다. 함수형 프로그래밍과 객체지향 프로그래밍을 결합하고 동적 언어 기능을 결합하여 매우 강력합니다(사실 C와 비교할 수 없습니다. ^_^).

여기서 주제는 이것입니다. 너무 멀리 가지 마세요. 이 자체는 원래 매우 간단하며 항상 클래스의 현재 인스턴스를 가리키므로 값을 할당할 수 없습니다. 이 전제는 클래스/객체와 분리될 수 없다는 것입니다. 즉, 객체지향 언어에서 공통적인 키워드입니다. 극단적으로 말하면, 객체 지향 스타일 대신 기능적 스타일을 사용하여 JS를 작성하면 모든 코드에서 이 스타일이 훨씬 적거나 아예 없을 수도 있습니다. 이 점을 명심하십시오. 이것을 사용할 때는 객체/클래스 접근 방식을 사용하여 개발해야 합니다. 그렇지 않으면 이는 함수 호출의 부작용일 뿐입니다.

JS에서는

내부 함수 생성
호출 시 함수에 바인딩된 개체를 가리킵니다(한 입)
배정은 불가능하며, 전화/신청을 통해 변경 가능합니다

이전에는 이 내용이 누구를 가리키는지 모르실까봐 걱정이 되기도 했는데요. 사용되는 모든 장소의 목록은 다음과 같습니다

이것과 생성자
이것과 물건
이것과 기능
지구환경의 이
이것과 DOM/이벤트
전화/신청으로 변경 가능합니다
ES5의 새로운 바인드와 this
ES6 화살표 기능과 이

1. this와 생성자

이것 자체가 클래스를 정의할 때 생성자에서 사용해야 하는 것이므로 생성자와 함께 있는 것이 당연합니다.

/**
 * 页签
 *
 * @class Tab
 * @param nav {string} 页签标题的class
 * @param content {string} 页面内容的class
 *
 */
function Tab(nav, content) {
  this.nav = nav
  this.content = content
}
 
Tab.prototype.getNav = function() {
  return this.nav;
};
Tab.prototype.setNav = function(nav) {
  this.nav = nav;
};
Tab.prototype.add = function() {
};

자바스크립트 규칙에 따라 이는 속성/필드에 첨부되어야 하며 메소드는 프로토타입에 배치되어야 합니다.

2. 이것과 사물

JS의 객체는 클래스 없이 생성될 수 있습니다. 어떤 사람들은 클래스가 객체를 위한 템플릿이고 객체가 템플릿에서 복사된다는 사실에 놀랄 수도 있습니다. JS는 그렇습니다. 단일 클래스를 작성하지 않고도 수만 줄의 기능 코드를 작성할 수 있습니다. 그런데 OOP는 클래스 지향 프로그래밍이 아닌 객체 지향 프로그래밍에 대해 이야기하고 있습니다. 그렇죠 ^_^.

var tab = {
  nav: '',
  content: '',
  getNav: function() {
    return this.nav;
  },
  setNav: function(n) {
    this.nav = n;
  }
}

3. 이것과 기능

우선 이것과 독립된 기능을 합치는 것은 의미가 없습니다. 앞서 언급했듯이 이것은 객체지향과 관련이 있어야 합니다. 순수 함수는 단지 낮은 수준의 추상화, 캡슐화 및 재사용일 뿐입니다. 다음과 같이

function showMsg() {
  alert(this.message)
}
showMsg() // undefined

showMsg를 정의한 다음 함수로 호출하세요. this.message는 정의되지 않습니다. 따라서 순수함수에서는 이것을 사용하는 것을 엄격히 금지하고 있으나 가끔 이렇게 작성하기도 하고, 호출방식은 call/apply

function showMsg() {
  alert(this.message)
}
 
var m1 = {
  message: '输入的电话号码不正确'
}
var m2 = {
  message: '输入的身份证号不正确'
}
 
showMsg.call(m1) // '输入的电话号码不正确'
showMsg.call(m2) // '输入的身份证号不正确'

이 방법으로 일부 코드를 저장할 수 있습니다. 예를 들어 두 클래스/객체에 유사한 메서드가 있는 경우 두 개의 복사본을 작성할 필요가 없으며 하나만 정의한 다음 해당 프로토타입과 객체에 바인딩하면 됩니다. . 현재 실제로는 간접적으로 객체나 클래스(메서드 1/2)를 계속 사용하고 있습니다.

4. 지구환경의 이

앞서 언급했듯이 "함수 호출시 함수에 바인딩된 개체를 가리키는 것"입니다. 이 문장은 어색하지만 절대 맞는 단어가 없습니다. 글로벌 환경에는 서로 다른 호스트 개체가 있습니다. 브라우저 환경에서는 창이고 노드 환경에서는 전역입니다. 여기서는 브라우저 환경에서 이에 중점을 둡니다.

브라우저 환경에서는 함수가 아닌 부분이 윈도우를 가리킵니다

alert(window=== this) // true
따라서 이렇게 작성된 오픈소스 JS 라이브러리를 많이 볼 수 있습니다

(함수() {
// ...
     
})(이것);
아니면 이렇게 쓰세요

(함수() {
// ...

}).call(this);
예를 들어 underscore 및 requirejs의 경우 일반적인 아이디어는 전역 변수 창을 익명 함수에 전달하고 이를 캐시하여 직접 액세스를 방지하는 것입니다. 캐싱이 필요한 이유는 JS 범위 체인과 관련이 있습니다. 외부 식별자를 읽는 성능이 저하됩니다. 관련 지식을 직접 확인하십시오. 그렇지 않으면 너무 무리한 정보가 될 것입니다.

브라우저에서는 함수가 아닌 함수 내에서 var를 사용하여 직접 선언된 변수가 전역 변수로 기본 설정되며 기본적으로 창에 속성으로 표시됩니다.

var andy = '刘德华'
alert(andy === window.andy) // true
alert(andy === this.andy) // true
alert(window.andy === this.andy) // true

이 기능으로 인해

과 같은 일부 필기 시험 문제가
var x = 10;
function func() {
  alert(this.x)
}
var obj = {
  x: 20,
  fn: function() {
    alert(this.x)
  }
}
var fn = obj.fn
func() // 10
fn() // 10

没错,最终输出的都是全局的 10。永远记住这一点:判断 this 指向谁,看执行时而非定义时,只要函数(function)没有绑定在对象上调用,它的 this 就是 window。

5. this 和 DOM/事件

W3C 把 DOM 实现成了各种节点,节点嵌套一起形成 DOM tree。节点有不同类型,如文本节点,元素节点等10多种。元素节点又分成了很多,对写HTML的人来说便是很熟悉的标签(Tag),如 div,ul,label 等。 看 W3C 的 API 文档,会发现它完全是按照面向对象方式实现的各种 API,有 interface,extends 等。如

看到了吧,这是用 Java 写的,既然是用面向对象方式实现的API,一定有类/对象(废话^_^),有 类/对象,则一定有 this (别忘了这篇文章的中心主题)。所有的 HTML tag 类命名如 HTMLXXXElement,如

HTMLDivElement
HTMLLabelElement
HTMLInputElement
...
前面说过 this 是指向当前类的实例对象,对于这些 tag 类来说,不看其源码也知它们的很多方法内部用到的 this 是指向自己的。 有了这个结论,写HTML和JS时, this 就清晰了很多。

示例A

<!-- this 指向 div -->
<div onclick="alert(this)"></div>

示例B

<div id="nav"></div>
<script>
  nav.onclick = function() {
    alert(this) // 指向div#nav
  }
</script>

示例C

$('#nav').on('click', function() {
  alert(this) // 指向 nav
})

以上三个示例可以看到,在给元素节点添加事件的时候,其响应函数(handler)执行时的 this 都指向 Element 节点自身。jQuery 也保持了和标准一致,但却让人迷惑,按 “this 指向调用时所在函数所绑定的对象” 这个定义,jQuery 事件 handler 里的 this,应该指向 jQuery 对象,而非 DOM 节点。因此你会发现在用 jQuery 时,经常需要把事件 handler 里的 element 在用 $ 包裹下变成 jQuery 对象后再去操作。比如

$('#nav').on('click', function() {
  var $el = $(this) // 再次转为 jQuery 对象,如果 this 直接为 jQuery 对象更好
  $el.attr('data-x', x)
  $el.attr('data-x', x)
})

有人可能有如下的疑问

<div id="nav" onclick="getId()">ddd</div>
<script>
  function getId() {
    alert(this.id)
  }
</script>

点击 div 后,为什么 id 是 undefined,不说是指向的 当前元素 div 吗? 如果记住了前面提到的一句话,就很清楚为啥是 undefined,把这句话再贴出来。

判断 this 指向谁,看执行时而非定义时,只要函数(function)没有绑定在对象上调用,它的 this 就是 window

这里函数 getId 调用时没有绑定在任何对象上,可以理解成这种结构

div.onclick = function() {
  getId()
}

getId 所处匿名函数里的 this 是 div,但 getId 自身内的 this 则不是了。 当然 ES5 严格模式下还是有个坑。

6. this 可以被 call/apply 改变

call/apply 是函数调用的另外两种方式,两者的第一个参数都可以改变函数的上下文 this。call/apply 是 JS 里动态语言特性的表征。动态语言通俗的定义

程序在运行时可以改变其结构,新的函数可以被引进,已有的函数可以被删除,即程序在运行时可以发生结构上的变化

通常有以下几点特征表示它为动态语言

动态的数据类型
动态的函数执行
动态的方法重写
动态语言多从世界第二门语言 LISP 发展而来,如死去的 SmallTalk/VB,目前还活着的 Perl/Python, 以及还流行的 Ruby/JavaScript。JS 里动态数据类型的体现便是弱类型,执行的时候才去分析标识符的类型。函数动态执行体现为 eval,call/aply。方法重写则体现在原型重写。不扯远,这里重点说下 call/apply 对 this 的影响。

var m1 = {
  message: 'This is A'
} 
var m2 = {
  message: 'This is B'
} 
 
function showMsg() {
  alert(this.message)
}
 
showMsg() // undefined
showMsg.call(m1) // 'This is A'
showMsg.call(m2) // 'This is B'

可以看到单独调用 showMsg 返回的是 undefined,只有将它绑定到具有 message 属性的对象上执行时才有意义。发挥想象力延伸下,如果把一些通用函数写好,可以任意绑定在多个类的原型上,这样动态的给类添加了一些方法,还节省了代码。这是一种强大的功能,也是动态语言的强表现力的体现。

经常会听到转向 Ruby 或 Python 的人提到“编程的乐趣”,这种乐趣是源自动态语言更接近人的思维(而不是机器思维),更符合业务流程而不是项目实现流程。同样一个功能,动态语言可以用更小的代码量来实现。动态语言对程序员生产力的提高,是其大行其道的主要原因。

性能方面,动态语言没有太大的优势,但动态语言的理念是:优化人的时间而不是机器的时间。提高开发者的生产力,宁肯牺牲部分的程序性能或者购买更高配置的硬件。随着IT业的不断发展和摩尔定律的作用,硬件相对于人件一直在贬值,这个理念便有了合理的现实基础。

JS 里的 call/apply 在任何一个流行的 lib 里都会用到,但几乎就是两个作用

配合写类工具实现OOP,如 mootools, ClassJS, class.js,
修复DOM事件里的 this,如 jQuery, events.js

关于 call 和 apply 复用:利用apply和arguments复用方法

关于 call 和 apply 的性能问题参考: 冗余换性能-从Backbone的triggerEvents说开了去

7. ES5 中新增的 bind 和 this

上面 6 里提到 call/apply 在 JS 里体现动态语言特性及动态语言的流行原因,其在 JS 用途如此广泛。ES5发布时将其采纳,提了一个更高级的方法 bind。

var modal = {
  message: 'This is A'
}
 
function showMsg() {
  alert(this.message)
}
 
var otherShowMsg = showMsg.bind(modal)
otherShowMsg() // 'This is A'

因为是ES5才加的,低版本的IE不支持,可以修复下Function.prototype。bind 只是 call/apply 的高级版,其它没什么特殊的。

8. ES6 箭头函数(arrow function) 和 this

ES6 在今年的 6月18日 正式发布(恰京东店庆日同一天,^_^),它带来的另一种类型的函数 - 箭头函数。箭头函数的一个重要特征就是颠覆了上面的一句话,再贴一次

判断 this 指向谁,看执行时而非定义时,只要函数(function)没有绑定在对象上调用,它的 this 就是 window

是的,前面一直用这句话来判断 this 的指向,在箭头函数里前面半句就失效了。箭头函数的特征就是,定义在哪,this 就指向那。即箭头函数定义在一个对象里,那箭头函数里的 this 就指向该对象。如下

var book = {
  author: 'John Resig',
  init: function() {
    document.onclick = ev => {
      alert(this.author) ; // 这里的 this 不是 document 了
    }
  }
};
book.init()

对象 book 里有一个属性 author, 有一个 init 方法, 给 document 添加了一个点击事件,如果是传统的函数,我们知道 this 指向应该是 document,但箭头函数会指向当前对象 book。

箭头函数让 JS 回归自然和简单,函数定义在哪它 this 就指向哪,定义在对象里它指向该对象,定义在类的原型上,指向该类的实例,这样更容易理解。

总结:

函数的上下文 this 是 JS 里不太好理解的,在于 JS 函数自身有多种用途。目的是实现各种语言范型(面向对象,函数式,动态)。this 本质是和面向对象联系的,和写类,对象关联一起的, 和“函数式”没有关系的。如果你采用过程式函数式开发,完全不会用到一个 this。 但在浏览器端开发时却无可避免的会用到 this,这是因为浏览器对象模型(DOM)本身采用面向对象方式开发,Tag 实现为一个个的类,类的方法自然会引用类的其它方法,引用方式必然是用 this。当你给DOM对象添加事件时,回调函数里引用该对象就只能用 this 了。

明白了么?

相信看完全文以后,this不再是坑~

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.