还是接着我们的上文,接下来谈谈js对于新手入门需要掌握哪些东西;
- 分清楚ECMAScript标准和W3C标准
所谓的ECMA,我们看百度百科的定义:
ECMAScript是一种由Ecma国际(前身为欧洲计算机制造商协会,European Computer Manufacturers Association)通过ECMA-262标准化的脚本程序设计语言。这种语言在万维网上应用广泛,它往往被称为JavaScript或JScript,所以它可以理解为是JavaScript的一个标准,但实际上后两者是ECMA-262标准的实现和扩展。
也就是说,ECMA标准主要是为了规范js语言,定义js语言是一种怎样的表现的一个标准;
再来看看W3C标准:
一、文档对象模型(外语缩写:DOM)根据W3C DOM规范,DOM是一种与浏览器,平台,语言的接口,使得你可以访问页面其他的标准组件。简单理解,DOM解决了Netscaped的Javascript和Microsoft的Jscript之间的冲突,给予web设计师和开发者一个标准的方法,让他们来访问他们站点中的数据、脚本和表现层对象。
这是W3C标准里面的行为标准,这是根据DOM规范,制定了一套关于web设计师如何与浏览器打交道的标准;
简单点来说,也就是ECMA标准规定了js这门语言本身应该具有的形态,例如原型、闭包、作用域等特性,我们常说的es6特性就是ECMAScript特性;
而W3C标准则是规定了js可以如何去跟DOM节点打交道,例如我们常用的window、document等js页面常见的全局对象就是标准里面定义的。
- 了解js基本的语法;
首先js语法拥有很多的特性,例如原型链、作用域链、闭包和事件循环,还有不断加入的es6、es7新特性;
事件循环机制我之前有专门写过一篇文章来讲解是怎么一回事,有兴趣的同学可以去看看,这里附上原文链接:
简单而面试中又常见的知识点:JS执行机制
这里给小伙伴们详细讲解一下js的原型是怎么一回事,我会尽量用简短的篇幅去描述;
- javascript原型
首先请大家先想一下什么是原型?什么是js的原型?
js中关于原型的定义其实很明确:
js的所有函数,都有一个prototype属性,这个属性本身是一个对象,对象里面又有一个constructor属性指向函数本身;
来看一下图示:
上图很显然,Person是一个函数,函数里面有一个prototype属性指向了Person Prototype,这就是函数Person的原型;原型里面总会有一个constructor属性指向Person本身;
我们接着看更进一步的图:
注意Student不是一个函数,而是一个实例对象,是通过
var Student = new Person()
实例化出来的;
我们可以看到上图的Student有一个proto属性,这就是我要讲的第二点:
每一个对象都会有一个叫做proto的属性,称为隐式原型;
它指向的是函数的prototype对象,也就是函数的原型;
那么这个属性有什么作用呢,或者说js的原型有什么作用呢?
只要记住,当一个对象的某一个属性或者方法找不到的时候,它会沿着自身的原型往上寻找,直到找到该属性或者方法为止,当然,如果找到尽头还找不到的话,会返回xx is undefined;
什么时候才是它的尽头呢?null,null是一切的终点,所以说js万物皆对象,万物皆空;
我们接着来更深入一下原型,首先我们直到一个事实:函数是对象;
我们可以通过这种方式创建函数:
var Person = new Function('console.log(1)')
Person() // 1
上面代码中可以看出,函数也是可以通过new语法来创建的,new的是什么,new的是Function这个函数;
也就是说,所有的函数,都是由Function函数实例化而来的;
那么回到我们上面所讲的,既然函数是对象,那么对象就都有proto属性指向其原型,那么Person同样也应该有proto属性,这个属性指向的是Function.prototype,所以所有函数实例的原型都是Function.prototype;
那么问题来了,我前面说过万物皆对象,那么Function.prototype自然也是一个对象,所有对象都有protp属性,所以Function.prototype是不是也有这个属性呢?
答案是肯定的,Function.prototype里面有proto属性,并且这个属性指向的是Object.prototype;
如果你听到这里觉得已经蒙了,那么恭喜你,这是很正常的现象;
为了方便小伙伴理解我刚才讲的话,特意画了一个草图把刚才的逻辑理了出来,看下方的图:
这幅图是经典原型图的简化版,可以很好的帮助我们理解js的原型关系;
从左到右分成三块:Person函数、Function函数与Object函数、Function.prototype和Object.prototype;
我们一块块来看;
- 第一块
首先是Person函数,函数都有prototype属性,指向其原型,所以指向的是Person.prototype;
同时函数也是对象,对象都有proto属性,因为Person原型是函数,所以其隐式原型指向的是Function.prototype
- 第二块
然后是中间的Function和Object,可以看到两个都是函数,所以都有其自身的prototype指向 自身的原型对象;
这里提一下,Function的proto属性,指向的也是Function.prototype,和prototype属性一样;
- 第三块
然后第三块,可以看到所有的prototype对象都指向同一个地方:Object.prototype,这表示所有的原型都是对象;
然后Object.prototype总不能指向自己吧,所以js设计者将其指向了null,所以js最后是万物皆空;
最后总结一下的话,只要记住,凡是函数都有prototype属性指向函数.prototype,这就叫做该函数的原型;
原型是一个对象,所以所有原型最终指向的是Object.prototype,同时因为Object.prototype不可能再指向自身,所以它指向的是Null;
然后所有的对象都有proto属性,指向的是生成该对象的函数的prototype属性;
关于函数的原型,暂时就只讲到这里了,希望你看了之后,能对js的原型有更深的理解~
- js的作用域与闭包
这块我会讲得更加通俗点;
js的作用域分为两个:全局作用域和函数作用域(暂时不包括es6之后的块级作用域);
作用域有内层外层的概念,也就是全局作用域是外层,定义在全局的函数是内层,定义在函数内部的函数是更内层;
- 内层作用域可以访问到外层或者同层级的变量;
- 但是反过来外层作用域不能访问到内层作用域的变量;
看下代码:
// 全局作用域
var a = '这里是最外层'
function foo() {
var f = '这里是同层级'
console.log(a)
console.log(f)
}
foo()
// 这里是最外层
// 这里是同层级
定义在全局的foo函数内部可以访问到外部变量a和同层级的变量f;
// 全局作用域
var a = '这里是最外层'
function foo() {
var f = '这里是同层级'
console.log(a)
}
console.log(f)
// f is not defined
而全局作用域没办法访问到函数foo内部的变量f;
其实这也是符合我们的认知习惯的,js中还有个作用域链的概念,也就是一旦在函数内部作用域没有找到的变量,将会从内向外一层层寻找,直到找到为止;
说完了作用域,我们再来讲讲闭包;
为什么要有闭包?
我们知道外部没办法访问函数内部的变量,当我确实想要访问的时候怎么办呢?闭包就是用来干这个的,在函数内部返回另一个函数,使得外部可以访问到函数内部的变量;
来看代码:
function foo() {
var a = 2
return function(b) {
return a + b
}
}
var closureFn = foo()
console.log(closureFn(1)) // 3
函数foo返回了一个function,function里面引用了foo函数的a变量,最后成功计算得出了3;
这就是闭包最基本的使用了,至于它的原理,这里我不做太细的展开。
只提一点就是:由于返回的函数内部引用了变量a,所以foo会一直留在内存中,a变量也会一直存在内存中,直到closureFn这个引用不再使用;
闭包还有什么其他的应用场景吗?
我们经典的一个for循环大坑,代码如下:
var arr = []
for(var i = 0;i < 5;i++) {
setTimeout(function() {
arr.push(i)
}, 0)
}
console.log(arr)
// [ 5, 5, 5, 5, 5]
这里输出的都是5,因为setTimeout是在js事件循环最后才执行的(有关于js事件循环机制的请参考文章前面提到的往期文章),所以最后i已经变成了5,arr.push(i)实际上等同于arr.push(5);
那么如何利用闭包解决这个事情呢?我们直接看解决代码:
var arr = []
for(var i = 0;i < 5;i++) {
(function(i){
setTimeout(function() {
arr.push(i)
}, 0)
}(i))
}
console.log(arr) // 0, 1, 2, 3, 4
我们看到上面最大的不同,就仅仅是给setTimeout函数外层包了一层自执行函数(也就是该函数会立刻执行),并且将i作为变量传入了该函数;
为什么这样的输出反而就变正常了呢?
我们改下代码相信你就一目了然了:
var arr = []
for(var i = 0;i < 5;i++) {
(function(any){
setTimeout(function() {
arr.push(any)
}, 0)
}(i))
}
console.log(arr) // 0, 1, 2, 3, 4
明白了吗?我们将i当前的值传给了函数,函数拿到的是值本身,并不是i这个索引;
所以即使最后i变成了5,对传给函数的参数(any)并不会有任何的影响,因为参数的值从for循环的时候就已经决定了,而不是for循环结束后再去拿当前的i的值;
以上就是作用域跟闭包的一些知识点了;
- 了解一些常用的浏览器操作DOM节点的API
虽然有了vue、react等框架,现在使用原生js或者jq直接操作dom节点的情况已经很少了,但是我们还是要了解一些基本的api的使用;
常见的有如下api:
- document.getElementById('id') // 根据元素id获取节点
- document.getElementsByClassName('class') // 根据元素类名获取节点
- document.getElementsByName('name') // 根据元素name属性获取节点
- document.getElementsByTagName('div') // 根据元素标签名称属性获取节点
- document.createElement('div') // 创建新的DOM节点
- parentNode.insertBefore(新节点,目标节点) // 在父节点中,将新节点插入到目标节点之前
- parentNode.appendChild(新节点) // 在父节点的最后插入一个新节点
- parentNode.removeChild(子节点) // 删除父节点中指定的子节点
- parentNode.replaceChild(新节点,老节点) // 在父节点中,用新节点替换老节点
- ...
还有很多很多基础的api,这里不再一一列举;
那么关于javascript的必须掌握点,我们先总结到这里;
最后再提一下前端工程师必须掌握两点知识:Ajax和http协议;
- Ajax
我们知道,ajax的出现改变了前后端的开发方式,后端能从渲染页面数据的工作中解脱出来,前端能做到更多的事情;
基本的ajax分为get请求和post请求, 我们来看下原生js实现一个ajax get请求:
var xmlHttpRequest;
function createXmlHttpRequest() {
if(window.XMLHttpRequest) {
// 非IE
xmlHttpRequest = new XMLHttpRequest();
} else if(window.ActiveObject) {
//IE6+
xmlHttpRequest = new ActiveObject("Msxml2.XMLHTTP");
} else {
// IE6-
xmlHttpRequest = new ActiveObject("Microsoft.XMLHTTP");
}
open('GET', url);//分别为提交的方法(GET或者POST)和提交的url
send(content);
onreadystatechange(){
if(xmlHttpRequest.readyState == 4){
if(xmlHttpRequest.state == 200){
//请求成功
}
} else{
//请求失败
}
}
不过很多时候你都不需要自己封装ajax的方法,也不需要兼容ie6+的浏览器(不排除有一些传统行业还需要)
- Http协议
最后简单讲一下http协议;
所谓http协议,指的是我们平时上网的时候,客户端用来和服务端通信最常使用的协议之一;
http协议的步骤如下:
- 客户端连接到Web服务器
- 发送HTTP请求
- 服务器接受请求并返回HTTP响应
- 释放连接TCP连接
- 客户端浏览器解析HTML内容
Http协议是存在于应用层,和用户打交道最多的协议;
这里面知识点超级多,每一项都可以作为一篇单独的文章来发布了,因此后续可能会专门做这一块的专栏;这里的话只是简单提醒一下,小白可以根据自己的情况去搜集更多的资料来进行学习;
最后,再次感谢大家的阅读,希望这篇教程能给你一些帮助;