博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
深入认知 JavaScript
阅读量:6543 次
发布时间:2019-06-24

本文共 9220 字,大约阅读时间需要 30 分钟。

本文当时写在本地,发现换电脑很不是方便,在这里记录下。

深入认知 Javascript

:zero: 前言

关于 Javascript,平时我们仅仅做到了使用,但是真的理解为什么这么使用吗?

这里详细介绍一些我们常用的 Javascript 语法。

:one: 关键字

what: 在Javascript 关键字是有很多的,而普通的关键字基本没有太多的难度,例如var,eval,void,break...,这里仅仅挑选两个 this 和 new 也是最让人疑惑的关键字。

1.1 this

在 Javascript6.0 以下,Javascript是没有块级作用域的,只有函数作用域。而如果在作用域中嵌套作用域,那么就会有作用域链。

foo = "window";function first(){    var foo = "first";    function second(){       var foo = "second";       console.log(foo);    }    function third(){       console.log(foo);    }    second(); //second    third();  //first}first();复制代码

:exclamation: 理解:当执行second时,JS引擎会将second的作用域放置链表的头部,其次是first的作用域,最后是window对象,于是会形成如下作用域链:second->first->window, 此时,JS引擎沿着该作用域链查找变量foo, 查到的是 second。当执行third时,third形成的作用域链:third->first->window, 因此查到的是:frist。

弄清楚作用域,我们在来看 this 关键字,Javascript 中的 this 总是指向当前函数的所有者对象,this总是在运行时才能确定其具体的指向, 也才能知道它的调用对象

window.name = "window";function f(){    console.log(this.name);}f();//window var obj = {
name:'obj'};f.call(obj); //obj复制代码

:exclamation: 理解:在执行f()时,此时f()的调用者是window对象,因此输出 window ,f.call(obj) 是把f()放在obj对象上执行,相当于obj.f(),此时f 中的this就是obj,所以输出的是 obj

Demo 1

var foo = "window";var obj = {    foo : "obj",    getFoo : function() {        return function() {            return this.foo;        };    }};var f = obj.getFoo();f();复制代码

**Demo 2 **

var foo = "window";var obj = {    foo : "obj",    getFoo : function() {        var that = this;        return function(){            return that.foo;        };    }};var f = obj.getFoo();f();复制代码

❓ Demo1 和 Demo2 的返回值是多少

:exclamation: 代码解析:

// demo1://执行var  f = obj.getFoo()返回的是一个匿名函数,相当于:var f = function(){     return this.foo;}// f() 相当于window.f(), 因此f中的this指向的是window对象,this.foo相当于window.foo, 所以f()返回"window" // demo2:// 执行var f = obj.getFoo() 同样返回匿名函数,即:var f = function(){     return that.foo;}// 唯一不同的是f中的this变成了that, 要知道that是哪个对象之前,先确定f的作用域链:f->getFoo->window 并在该链条上查找that,// 此时可以发现that指代的是getFoo中的this, getFoo中的this指向其运行时的调用者,// 从var f = obj.getFoo() 可知此时this指向的是obj对象,因此that.foo 就相当于obj.foo,所以f()返回 "obj"复制代码

1.2 new

what: 和其他高级语言一样 Javascript 中也有 new 运算符,我们知道 new 运算符是用来实例化一个类,从而在内存中分配一个实例对象。 但在 Javascript 中,万物皆对象,为什么还要通过 new 来产生对象

1.2.1 认识

先看一个例子

01 function Animal(name){02     this.name = name;03 }04 Animal.color = "black";05 Animal.prototype.say = function(){06	   console.log("I'm " + this.name);07 };08 var cat = new Animal("cat");0910 console.log(11     cat.name,  //cat12	   cat.height //undefined13 );14 cat.say(); //I'm cat1516 console.log(17	   Animal.name, //Animal18	   Animal.color //back19 );20 Animal.say(); //Animal.say is not a function复制代码

:exclamation: 代码解析:

:small_blue_diamond: 1-3行创建了一个函数 Animal,并在其 this 上定义了属性 name,name的值是函数被执行时的形参。

:small_blue_diamond: 4行在 Animal 对象(Animal 本身是一个函数对象)上定义了一个静态属性 color,并赋值“black”

:small_blue_diamond: 5-7行在 Animal 函数的原型对象 prototype 上定义了一个 say 方法,say 方法输出了 this.name

:small_blue_diamond: 8行通过 new 关键字创建了一个新对象 cat

:small_blue_diamond: 10-14行 cat 对象尝试访问 namecolor 属性,并调用 say 方法。

:small_blue_diamond: 16-20行 Animal 对象尝试访问 namecolor 属性,并调用 say 方法。

:exclamation: 重点解析:

注意到第 8 行,

var cat = new Animal("cat");复制代码

JS引擎执行这句代码时,在内部做了很多工作,用伪代码模拟其工作流程如下:

new Animal("cat") = {    var obj = {};    obj.__proto__ = Animal.prototype;    var result = Animal.call(obj, "cat");    return typeof result === 'object'? result : obj;}复制代码

:exclamation: 代码解析:

:small_blue_diamond: 创建一个空对象obj;

:small_blue_diamond: 把obj__proto__ 指向 Animal 的原型对象 prototype,此时便建立了 obj 对象的原型链:

obj :arrow_right: Animal.prototype :arrow_right: Object.prototype :arrow_right: null

简单解释下原型对象和原型链:

  • 原型对象:指给后台函数继承的父对象
  • 原型链:链接成 java 的继承

? 在obj对象的执行环境调用Animal函数并传递参数cat。 相当于var result = obj.Animal("cat")。 当这句执行完之后,obj便产生了属性name并赋值为cat

简单解释下 call 和 apply:

相同:调用一个对象的一个方法,用另一个对象替换当前对象。

  • B.call(A, args1,args2); 即A对象调用B对象的方法。
  • B.apply(A, arguments); 即A对象应用B对象的方法

不同: 两者传入的列表形式不一样

  • call可以传入多个参数
  • apply只能传入两个参数,所以其第二个参数往往是作为数组形式传入

? 考察第3步返回的返回值,如果无返回值或者返回一个非对象值,则将obj返回作为新对象;否则会将返回值作为新对象返回。

❗️ 深入解析:

理解new的运行机制以后,我们知道cat其实就是过程(4)的返回值,因此我们对cat对象的认知就多了一些:

cat的原型链是: cat :arrow_right: Animal.prototype :arrow_right: Object.prototype :arrow_right: null

cat上新增了一个属性:name

分析完了cat的产生过程,我们再看看输出结果:

? cat.name -> 在过程(3)中,obj对象就产生了name属性。因此cat.name就是这里的obj.name

? cat.color -> cat会先查找自身的color,没有找到便会沿着原型链查找,在上述例子中,我们仅在Animal对象上定义了color,并没有在其原型链上定义,因此找不到。

? cat.say -> cat会先查找自身的say方法,没有找到便会沿着原型链查找,在上述例子中,我们在Animal的prototype上定义了say,因此在原型链上找到了say方法。

另外,在say方法中还访问this.name,这里的this指的是其调用者obj,因此输出的是obj.name的值。

对于Animal来说,它本身也是一个对象,因此,它在访问属性和方法时也遵守上述查找规则,所以:

? Animal.color -> "black"

? Animal.name -> "Animal" , Animal先查找自身的name, 找到了name,注意:但这个name不是我们定义的name,而是函数对象内置的属性。一般情况下,函数对象在产生时会内置name属性并将函数名作为赋值(仅函数对象)。

? Animal.say -> Animal在自身没有找到say方法,也会沿着其原型链查找,话说Animal的原型链是什么呢?

从测试结果看:Animal的原型链是这样的:

Animal :arrow_right: Function.prototype :arrow_right: Object.prototype :arrow_right: null

因此Animal的原型链上没有定义say方法!

1.2.2 存在的意义

之前提到js中,万物皆对象,为什么还要通过new来产生对象?要弄明白这个问题,我们首先要搞清楚cat和Animal的关系。

通过上面的分析,我们发现cat继承了Animal中的部分属性,因此我们可以简单的理解:Animal和cat是继承关系。

另一方面,cat是通过new产生的对象,那么cat到底是不是Animal的实例对象? 我们先来了解一下JS是如何来定义“实例对象”的?

A instanceof B复制代码

如果上述表达式为true,JS认为A是B的实例对象,我们用这个方法来判断一下cat和Animal

var isInstance = cat instanceof Animal; //true 复制代码

❗️ 代码解析:

cat 确实是 Animal 实例,要想证实这个结果,我们再来了解一下JS中 instanceof 的判断规则:

var L = A.proto; var R = B.prototype; console.log(L === R);复制代码

new的执行过程中,cat的__proto__指向了Animal的prototype,所以catAnimal符合instanceof的判断结果。因此,我们认为:catAnimal的实例对象。

1.2.3 总结

在javascript中, 通过new可以产生原对象的一个实例对象,而这个实例对象继承了原对象的属性和方法。因此,new存在的意义在于它实现了javascript中的继承,而不仅仅是实例化了一个对象!

:two: 高级函数

what: 在Javascript中,对象分普通对象和函数对象,普通的常见,这里不做说明。说起 Javascript的函数,十分强大。它们是第一类对象,也可以作为另一个对象的方法,还可以作为参数传入另一个函数,不仅如此,还能被一个函数返回!可以说,在JS中,函数无处不在,无所不能。

why: 除了函数相关的基础知识外,掌握一些高级函数并应用起来,不仅能让JS代码看起来更为精简,还可以提升性能。

2.1 安全构造函数

常规写法:

function Person(name,age){this.name = name;this.age = age;}var p1 = new Person("Claiyre",80);复制代码

但是,如果忘记加new了会发生什么?

var p3 = Person("Tom",30);console.log(p3); //undefinedconsole.log(window.name); //Tom复制代码

由于使用了不安全的构造函数,上面的代码意外的改变了windowname,因为this对象是在运行时绑定的,使用new调用构造函数时this是指向新创建的对象的,不使用new时,this是指向window的。 由于windowname属性是用来识别链接目标和frame的,所在这里对该属性的偶然覆盖可能导致其他错误。

作用域安全的构造函数会首先确认this对象是正确类型的实例,然后再进行更改,如下:

function Person(name,age){    if(this instanceof Person){        this.name = name;        this.age = age;    } else {    	return new Person(name,age);    }}复制代码

:pushpin: 作用:避免了在全局对象上意外更改或设置属性。 在多人协作的项目中,为了避免他们误改了全局对象,使用作用域安全的构造函数会更好。

2.2 惰性载入函数

由于浏览器间的行为差异,代码中可能会有许多检测浏览器行为的if语句。但用户的浏览器若支持某一特性,便会一直支持,所以这些if语句,只用被执行一次,即便只有一个if语句的代码,也比没有要快。 惰性载入表示函数执行的分支仅会执行一次,有两种实现惰性载入的方式

第一种就是在函数第一次被调用时再处理函数,用检测到的结果重写原函数。

function detection(){    if(//支持某特性){
detection = function(){ //直接用支持的特性 } } else if(//支持第二种特性){
detection = function(){ //用第二种特性 } } else { detection = function(){ //用其他解决方案 } }}复制代码

第二种实现惰性载入的方式是在声明函数时就指定适当的函数

var detection = (function(){    if(//支持某特性){
return function(){ //直接用支持的特性 } } else if(//支持第二种特性){
return function(){ //用第二种特性 } } else { return function(){ //用其他解决方案 } }})();复制代码

? 作用:惰性载入函数的有点是在只初次执行时牺牲一点性能,之后便不会再有多余的消耗性能 。

2.3 函数绑定作用域

在JS中,函数的作用域是在函数被调用时动态绑定的,也就是说函数的this对象的指向是不定的,但在一些情况下,我们需要让某一函数的执行作用域固定,总是指向某一对象。这时可以用函数绑定作用域函数。

function bind(fn, context){    return function(){    	return fn.apply(context, arguments);    }}复制代码
// 具体一点var person1 = {    name: "claiyre",    sayName: function(){    	alert(this.name);    }}var sayPerson1Name = bind(person1.sayName, person1);sayPerson1Name(); // claiyre复制代码

? 作用:函数的this对象固定,总是指向某一对象

2.4 函数柯里化

只传递部分参数来调用函数,然后让函数返回另一个函数去处理剩下的参数。可以理解为赋予了函数“加载”的能力。

// 较为简单的实现curry的方式function curry(fn){    var i = 0;    var outer = Array.prototype.slice.call(arguments,1);    var len = fn.length;    return function(){        var inner = outer.concat(Array.prototype.slice.call(arguments));        return inner.length === len?fn.apply(null,inner) : function (){            var finalArgs = inner.concat(Array.prototype.slice.call(arguments));            return fn.apply(null,finalArgs);        }    }}// 一旦函数经过柯里化,我们就可以先传递部分参数调用它,然后得到一个更具体的函数。var match = curry(function(what,str){	return str.match(what)}); var hasNumber = match(/[0-9]+/g);var hasSpace = match(/\s+/g) hasNumber("123asd"); // ['123']hasNumber("hello world!"); // null hasSpace("hello world!"); // [' '];hasSpace("hello"); // null console.log(match(/\s+/g,'i am Claiyre')); // 直接全部传参也可: [' ',' ']复制代码

? 作用:逐步的具体化函数,最后得到结果

2.5 debounce函数 (去抖函数)

当函数被调用时,不立即执行相应的语句,而是等待固定的时间w,若在w时间内,即等待还未结束时,函数又被调用了一次,则再等待w时间,重复上述过程,直到最后一次被调用后的w时间内该函数都没有被再调用,则执行相应的代码。

var myFunc = debounce(function(){// 繁重、耗性能的操作},250); // 函数节流window.addEventListener('resize',myFunc);复制代码

? 作用:防止某一函数被连续调用,从而导致浏览器卡死或崩溃

2.6 once函数

function once(fn){    var result;    return function(){        if(fn){        result = fn(arguments);        fn = null; // 在被执行过一次后,参数fn就被赋值null了,那么在接下来被调用时,便再也不会进入到if语句中了,也就是第一次被调用后,该函数永远不会被执行了。        }		return result;	}} var init = once(function(){	// 初始化操作})复制代码

? 作用:仅仅会被执行一次的函数 ,防止过多的污染

:clap: 结语

Javascript 太多内容了,基本讲一个点就能引申出很多点出来。

刚刚上述中关键字我们很常用,但是稍微不注意就可能会弄混淆;而在高级函数中,不难发现很多“高级函数”的实现其实并不复杂,数十行代码便可搞定,但重要的是能真正理解它们的原理,在实际中适时地应用,以此性能提升,让代码简洁,逻辑清晰。

:clipboard: 参考

转载于:https://juejin.im/post/5cfe2354e51d455071250aeb

你可能感兴趣的文章
Java 线程 — ThreadLocal
查看>>
安居客爬虫(selenium实现)
查看>>
-----二叉树的遍历-------
查看>>
ACM北大暑期课培训第一天
查看>>
F. Multicolored Markers(数学思维)
查看>>
Centos7安装搜狗输入法
查看>>
nodjs html 转 pdf
查看>>
Python字典
查看>>
ofstream 的中文目录问题
查看>>
Android存储方式之SQLite的使用
查看>>
洛谷P1287 盒子与球 数学
查看>>
Bootstrap vs Foundation如何选择靠谱前端框架
查看>>
与、或、异或、取反、左移和右移
查看>>
vue常用的指令
查看>>
matlab练习程序(随机游走图像)
查看>>
Linux命令行下运行java.class文件
查看>>
input文本框实现宽度自适应代码实例
查看>>
protocol buffers的编码原理
查看>>
行为型设计模式之命令模式(Command)
查看>>
减少死锁的几个常用方法
查看>>