Cet article organise principalement quelques mots-clés, méthodes et concepts similaires dans javascript
et les partage avec tout le monde, dans l'espoir d'aider tout le monde.
Une variable déclarée à l'aide de var a sa portée dans la fonction où se trouve l'instruction, et la variable existe Phénomène de levage
Les variables déclarées en utilisant let ont leur portée dans le bloc de code où se trouve l'instruction Il n'y a pas de levage de variable
//a. 变量提升 console.log(a) // => undefined var a = 123 //b. 作用域 function f() { var a = 123 console.log(a) // => 123 } console.log(a) // => a is not defined for (var i = 0; i < 10; i ++) {} console.log(i) // => 10
//a. 变量不提升 console.log(a) // => a is not defined let a = 123 //b. 作用域为所在代码块内 for (let i = 0; i < 10; i ++) {} console.log(i) // => i is not defined
//a. 不能修改的是栈内存在的值和地址 const a = 10 a = 20 // => Assignment to constant variable // 但是以下的赋值确是合法的 const a = { b: 20 } a.b = 30 console.log(a.b) // => 30
//a. 函数提升 fn() // => 123 function fn() { return 123 } //b. 作用域 function fn() { function fn1 () { return 123456 } fn1() // => 123456 } fn1() // => fn1 is not defined
var a = 1 function fn() { if (!a) { var a = 123 } console.log(a) } fn() ?
for (var i = 0; i < 10; i++) { setTimeout(function(){ console.log(i) }) }
function Foo() { getName = function(){ console.log("1"); }; return this; } Foo.getName = function() { console.log("2"); }; Foo.prototype.getName = function(){ console.log("3"); }; var getName = function() { console.log("4"); } function getName(){ console.log("5"); } Foo.getName(); ? getName(); ? Foo().getName(); ? getName(); ? new Foo.getName(); ? new Foo().getName(); ?
Question 1
//我们把它执行顺序整理下 var a = 1 function fn() { var a = nudefined if (!a) { var a = 123 } console.log(a) } //所以 答案很明显 就是 123
Question 2
for (var i = 0; i < 10; i++) { print(i) } function print(i) { // 把每个变量i值传进来,变成只可当前作用域访问的局部变量 setTimeout(function(){ console.log(i) }) } // 或者自执行函数简写 for (var i = 0; i < 10; i++) { (function(i){ setTimeout(function(){ console.log(i) }) })(i) }
Question 3
// 我们整理下它的执行顺序 var getName = undefined function Foo() { getName = function(){ console.log("1"); }; return this; } function getName(){ console.log("5"); } Foo.getName = function() { console.log("2"); }; Foo.prototype.getName = function(){ console.log("3"); }; getName = function() { console.log("4"); } Foo.getName(); // 2 /* 函数也是对象, Foo.getName 相当于给 Foo这个对象添加了一个静态方法 getName,我们调用的其实是这个静态方法,并不是调用的我们实例化的 getName */ getName(); // 4 /* 按照上面的执行顺序,其实这个就很好理解了,因为 `getName = function() { console.log("4"); }` 是最后一个赋值, 执行的应该是这个函数 */ Foo().getName(); // 1 /* 这里为什么是 1 而不是我们想象的 3 呢? 问题就是出在 调用的是 Foo(); 并没有使用 new 这个关键字,所以那时候返回的 this 指向的并不是 Foo, 而是 window; 至于为什么不用 new 返回的 this 不指向 Foo, 这个随便去哪查一下就好, 就不在这介绍了 */ getName(); // 1 /* 这里为什么也是1 呢? 其实原因就是 上面我们调用了 `Foo().getName();` 这个方法引起的, 因为我们执行了 Foo 函数, 触发了 getName = function(){ console.log("1"); } 这段代码, 而且并没有在Foo里面声明 getName 变量, 于是就一直往上查找, 找到外部的 getName 变量 并赋值给它. 所以这里调用 getName() 方法时, 它的值已经变成 getName = function(){ console.log("1"); } 了 */ new Foo.getName(); // 2 /*这个时候还是没有实例化, 调用的还是它的静态方法*/ new Foo().getName(); // 3 /*因为实例化了,所以调的是原型上的方法*/
Je me souviens avoir vu quelques exemples classiques, mais je ne les ai pas trouvés après une longue recherche, alors c'est tout pour l'instant.
2. La différence entre == et ===Mêmes points : Les deux opérateurs sont autorisés Opérandes de n'importe quel type, si les opérandes sont égaux, renvoie vrai, sinon renvoie faux
Différence :
== : L'opérateur est appelé égalité et est utilisé pour vérifier si deux opérandes sont égaux. La définition de l'égalité ici est très vague et permet la conversion de type
=== : est utilisé pour vérifier si deux opérandes sont égaux. sont strictement égaux, aucune conversion de type ne sera effectuée
== Règles de conversion
a. un objet, et l'objet utilise toString() Ou valueOf() pour la conversion ;
b. Si l'autre partie est un nombre, convertissez la chaîne en un nombre
c. directement ;
d. Sinon, retournez false
// 不同类型,相同值 var a = 1 var b = '1' console.log(a == b) // => true console.log(a === b) // => false // 对象和字符串 console.log([1,2,3] == '1,2,3') // => true 因为 [1,2,3]调用了 toString()方法进行转换 // 对象和布尔 console.log([] == true) // => false []转换为字符串'',然后转换为数字0, true 转换成1 // 对象和数字 console.log(['1'] == 1) // => true []转换为字符串'1' console.log(2 == {valueOf: function(){return 2}}) // => true 调用了 valueOf()方法进行转换 // null, undefined 不会进行类型转换, 但它们俩相等 console.log(null == 1) // => false console.log(null == 0) // => false console.log(undefined == 1) // => false console.log(undefined == 0) // => false console.log(null == false) // => false console.log(undefined == false) // => false console.log(null == undefined) // => true console.log(null === undefined) // => false // NaN 跟任何东西都不相等(包括自己) console.log(NaN == NaN) // => false console.log(NaN === NaN) // => false
==
===
Tous les objets héritent de ces deux méthodes de conversion
: Renvoie une chaîne reflétant cet objettoString
: renvoie sa valeur d'origine correspondantevalueOf
var arr = [1,2,3] var obj = { a: 1, b: 2 } console.log(arr.toString()) // => 1,2,3 console.log(obj.toString()) // => [object Object] // 那我们修改一下它原型上的 toString 方法呢 Array.prototype.toString = function(){ return 123 } Object.prototype.toString = function(){ return 456 } console.log(arr.toString()) // => 123 console.log(obj.toString()) // => 456 // 我们看下其余类型转换出来的结果, 基本都是转换成了字符串 console.log((new Date).toString()) // => Mon Feb 05 2018 17:45:47 GMT+0800 (中国标准时间) console.log(/\d+/g.toString()) // => "/\d+/g" console.log((new RegExp('asdad', 'ig')).toString()) // => "/asdad/gi" console.log(true.toString()) // => "true" console.log(false.toString()) // => "false" console.log(function(){console.log(1)}.toString()) // => "function (){console.log(1)}" console.log(Math.random().toString()) // => "0.2609205380591437"
var arr = [1,2,3] var obj = { a: 1, b: 2 } console.log(arr.valueOf()) // => [1, 2, 3] console.log(obj.valueOf()) // => {a: 1, b: 2} // 证明valueOf返回的是自身的原始值 // 同样我们修改下 valueOf 方法 Array.prototype.valueOf = function(){ return 123 } Object.prototype.valueOf = function(){ return 456 } console.log(arr.valueOf()) // => 123 console.log(obj.valueOf()) // => 456 // valueOf转化出来的基本都是原始值,复杂数据类型Object返回都是本身,除了Date 返回的是时间戳 console.log((new Date).valueOf()) // => 1517824550394 //返回的并不是字符串的世界时间了,而是时间戳 console.log(/\d+/g.valueOf()) // => 456 当我们不设置时valueOf时,正常返回的正则表式本身:/\d+/g,只是我们设置了 Object.prototype.valueOf 所以返回的时:456 console.log(Math.valueOf()) // => 456 同上 console.log(function(){console.log(1)}.valueOf()) // => 456 同上
var a = { toString: function() { console.log('你调用了a的toString函数') return 8 } } console.log( ++a) // 你调用了a的toString函数 // 9 // 当你设置了 toString 方法, 没有设置 valueOf 方法时,会调用toString方法,无视valueOf方法
var a = { num: 10, toString: function() { console.log('你调用了a的toString函数') return 8 }, valueOf: function() { console.log('你调用了a的valueOf函数') return this.num } } console.log( ++a) // 你调用了a的valueOf函数 // 11 // 而当你两者都设置了的时候,会优先取valueOf方法, 不会执行toString方法
Si "||" et "&&" sont utilisés pour le jugement conditionnel
var a = true,b = false, c = true, d = false var str = 'none' if (b || d || a) { str = '现在是 ||' } console.log(str) // => '现在是 ||' ,因为其中a为true所有满足条件 var str = 'none' if (b || d ) { str = '现在是 ||' } console.log(str) // => 'none' ,因为b,d都是false, 不满足条件 var str = 'none' if (a && c && d) { str = '现在是 &&' } console.log(str) // => 'none' ,因为d是false, 其中有一个false就不满足条件 var str = 'none' if (a && c) { str = '现在是 &&' } console.log(str) // => '现在是 &&' ,因为b,d都是true, 满足条件
Principe du court-circuit :
|| : 1. Tant que "||" est vrai devant, le résultat renverra "||" La valeur précédente
2. Si "||" est faux, le résultat sera être "||" et la valeur suivante sera renvoyée
var a = true,b = false, c = true, d = false var str = 'none' if (b || d || a) { str = '现在是 ||' } console.log(str) // => '现在是 ||' ,因为其中a为true所有满足条件 var str = 'none' if (b || d ) { str = '现在是 ||' } console.log(str) // => 'none' ,因为b,d都是false, 不满足条件 var str = 'none' if (a && c && d) { str = '现在是 &&' } console.log(str) // => 'none' ,因为d是false, 其中有一个false就不满足条件 var str = 'none' if (a && c) { str = '现在是 &&' } console.log(str) // => '现在是 &&' ,因为b,d都是true, 满足条件
&& (et) : 1. Tant que "&&" est précédé de false, peu importe qu'il soit vrai ou faux après "&&", le résultat renverra la valeur avant "&&"
2. Tant que "&&" est précédé de vrai, peu importe si "&&" est suivi de vrai ou faux, le résultat renverra la valeur après "&&"
var a = false, b = true console.log(a && b) // => false 只要“&&”前面是false,无论“&&”后面是true还是false,结果都将返“&&”前面的值 console.log(b && a) // => false 只要“&&”前面是true,无论“&&”后面是true还是false,结果都将返“&&”后面的值
var name = '小刚' var person = { name: '小明', fn: function() { console.log(this.name + '撸代码') } } person.fn() // => 小明撸代码 // 如何把它变成 “小刚撸代码” 呢? // 我们可以用 call/bind/apply 分别来实现 person.fn.call(window) // => 小刚撸代码 person.fn.apply(window) // => 小刚撸代码 person.fn.bind(window)() // => 小刚撸代码
Alors, quelle est la différence entre appeler et postuler ?
obj.call(thisObj, arg1, arg2, ...) obj.apply(thisObj, [arg1, arg2, ...]) // 通过上面的参数我们可以看出, 它们之间的区别是apply接受的是数组参数,call接受的是连续参数。 // 于是我们修改上面的函数来验证它们的区别 var person = { name: '小明', fn: function(a,b) { if ({}.toString.call(a).slice(8, -1) === 'Array') { console.log(this.name+','+a.toString()+'撸代码') }else{ console.log(this.name+','+a+','+b+'撸代码') } } } person.fn.call(this, '小红', '小黑' ) // => 小刚,小红,小黑撸代码 person.fn.apply(this, ['小李', '小谢']) // => 小刚,小李,小谢撸代码
Contrairement à call et apply, bind ne sera pas exécuté immédiatement après la liaison. Il déterminera uniquement le point this de la fonction puis renverra la fonction
var name = "小红" var obj = { name: '小明', fn: function(){ console.log('我是'+this.name) } } setTimeout(obj.fn, 1000) // => 我是小红 // 我们可以用bind方法打印出 "我是小明" setTimeout(obj.fn.bind(obj), 1000) // => 我是小明 // 这个地方就不能用 call 或 apply 了, 不然我们把函数刚一方去就执行了 // 注意: bind()函数是在 ECMA-262 第五版才被加入 // 所以 你想兼容低版本的话 ,得需要自己实现 bind 函数 Function.prototype.bind = function (oThis) { if (typeof this !== "function") { throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); } var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, fNOP = function () {}, fBound = function () { return fToBind.apply( this instanceof fNOP && oThis ? this : oThis || window, aArgs.concat(Array.prototype.slice.call(arguments)) ); }; fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound; };
这三个东西牵涉到的可能就是我们最常见到的 “同步”、“异步”、“任务队列”、“事件循环” 这几个概念了
例:
var data; $.ajax({ ... success: function(data) { data = data } }) console.log(data)
当我们从服务器获取到数据的时候,为什么打印出来的是undefined ?
解决这个问题之前我们先来了解javascript的运行环境
JavaScript是单线程语言,JS中所有的任务可以分为两种:同步任务和异步任务。
同步任务:
意思是我必须做完第一件事,才能做第二件事,按照顺序一件一件往下执行(在主线程上)
异步任务:
假如我第一件事需要花费 10s, 但是我第二件事急着要做, 于是我们就把第一件事告诉主线程,然后主线程暂停先放到某个地方, 等把第二件事完成之后,再去那个地方执行第一件事,第一件事也就可以理解为异步任务
任务队列(task queue):
任务队列是干嘛的呢; 上面我们说了异步任务的情况, 我们把第一件放到某个地方, 那某个地方是什么地方呢,就是 “任务队列” 这个东西。里面乘放的是所有异步任务。
Event Loop(事件循环)
当主线程上面所有同步任务执行完之后,主线程就会向任务队列中读取异步任务(队列方法:先进先出)
而且是一直重复向任务队列中,即使没有任务。它也会一直去轮询。
只不过在任务列表里面没有任务的时候, 主线程只需要稍微过一遍就行, 一旦遇到任务队列里面有任务的时候,就会去执行它
也就是说在我们打开网页的时候,JS引擎会一直执行事件循环,直到网页关闭
如图所示
由此,上面为什么会产生 undefined的原因了, 因为ajax 是异步任务,而我们console.log(data)是同步任务,所以先执行的同步任务,才会去执行 ajax
说了这么多,我们来看下 为什么我们很需要 从 callback
=> promise
=> async/await
因为很多时候我们需要把一个异步任务的返回值,传递给下一个函数,而且有时候是连续的n个
callback
// 只有一个callback的时候 function fn(callback) { setTimeout(function(){ callback && callback() }, 1000) } fn(function(){ console.log(1) }) // 一旦我们多几个呢? function fn(a){ // 传入a 返回a1 function fn1(a1){ function fn2(a2){ function fn3(a3){ console.log(a3) .... } } } } // 当项目一复杂,这滋味。。。
Promise
什么是promise?
Promise是异步编程的一种解决方案,同时也是ES6的内置对象,它有三种状态:
Promise方法
基本用法
let promise = new Promise( (resolve, reject) => { setTimeout(function(){ resolve(1) }, 1000) }) promise.then( res => { console.log(res)// 一秒之后打印1 })
我们把上面的回调地狱转换下
const fn = a => { return Promise.resolve(a) } const fn1 = a => { return Promise.resolve(a) } const fn2 = a => { // return Promise.resolve(a) return new Promise( (resolve, reject) => { setTimeout(function(){ resolve(a) },1000) }) } const fn3 = a => { // return Promise.resolve(a) return new Promise( (resolve, reject) => { setTimeout(function(){ resolve(a) },1000) }) } fn(123) .then(fn1) .then(fn2) .then(fn3) .then( res => { console.log(res) // => 123 })
这样就简单明了多了, 我们就不需要一层一层嵌套callback了,可以通过链式调用来解决callback的问题
然而,仅仅这样还是觉得不够好
因为这种面条式调用还是让人很不爽,而且 then 方法里面虽然是按先后顺序来的,但是其本身还是异步的
看下面这段代码
const promise = new Promise( (resolve, reject) => { setTimeout(function(){ resolve(222) }, 1000) }) console.log(111) promise.then( res => { console.log(res) }) console.log(333)
打印结果依然还是 111 => 333 => 222, 并不是我们想象的 111 => 222 => 333
依然不适合单线程的思维模式。所以下一个解决方案 又出现了
Promise.prototype.then() 接收两个函数,一个是处理成功后的函数,一个是处理错误结果的函数。可以进行链式调用
Promise.prototype.catch() 捕获异步操作时出现的异常, 一般我们用来代替.then方法的第二个参数
Promise.resolve() 接受一个参数值,可以是普通的值, 会返回到对应的Promise的then方法上
Promise.reject() 接受一个参数值,可以是普通的值, 会返回到对应的Promise的catch方法上或着then方法的第二个参数上
Promise.all() 接收一个参数,它必须是可以迭代的,比如数组。通常用来处理一些并发的异步操作。成功调用后返回一个数组,数组的值是有序的,即按照传入参数的数组的值操作后返回的结果
Promise.race() 接收一个可以迭代的参数,比如数组。但是只要其中有一个执行了,就算执行完了,不管是成功还是失败。
pending: 进行中
resolved: 已完成
rejected:已失败
async/await
这是ES7的语法,当然,在现在这种工程化的时代,基本babel编译之后也都是能在项目中引用的
基本用法跟规则
async 表示这是一个async函数,
await只能用在这个函数里面。后面应该跟着是 Promise 对象, 不跟的话也没关系, 但是await就不会在这里等待了
await 表示在这里等待promise返回结果
例:
const fn = () => { return new Promise( (resolve, reject) => { setTimeout(function(){ resolve(222) }, 1000) }) } (async function(){ console.log(111) let data = await fn() console.log(data) console.log(333) })() // 是不是返回 111 => 222 => 333 了呢 // 我们来试下返回别的东西, 不返回 promise const fn = () => { return new Promise( (resolve, reject) => { setTimeout(function(){ resolve(222) }, 1000) }) } (async function(){ console.log(111) let data = await fn() console.log(data) console.log(333) })() // 打印结果: 111 => null => 333 => 222 // 当我们不是在await 关键字后面返回的不是 promise 对象时, 它就不会在原地等待 promise执行完再执行, 而是向正常的JS一样执行,把异步任务跳过去
await
关键字必须包裹在 async
函数里面,而且async
函数必须是它的父函数
const fn = () => { let promise = new Promise( (resolve, reject) => { setTimeout(function(){ resolve(222) }, 1000) }) } // 这样是不行的,会报错,因为的await关键字的父函数不是 async 函数 const grand = async () => { return function parent() { let data = await fn() } } // 这样才行,因为await 的父函数 是一个 async 函数 const grand = () => { return async function parent() { let data = await fn() } }
柯里化
函数柯里化就是对高阶函数的降阶处理。
柯里化简单的说,就是把 n 个参数的函数,变成只接受一个参数的 n 个函数function(arg1,arg2)
变成function(arg1)(arg2)
function(arg1,arg2,arg3)
变成function(arg1)(arg2)(arg3)
function(arg1,arg2,arg3,arg4)
变成function(arg1)(arg2)(arg3)(arg4)
柯里化有什么作用
例:
//求和 function add (a, b, c) { return a + b + c } add(1,2,3)
如果我只改变 c 的值,在求和add(1,2,4)
是不是得多出重新计算 a + b 的部分
我们是不是可以提前返回a+b的值, 然后只传入 c 的值进行计算就行了
修改一下方法
function add (a, b) { return function (c) { return a + b + c } } var sum = add(1, 2) sum(3) sum(4)
在此基础上我们在做下修改
function add (a) { return function (b) { return function (c) { return a + b + c } } }
这样我们是不是可以随时复用某个参数,并且控制在某个阶段提前返回
还有一个经典的例子
var addEvent = function(el, type, fn, capture) { if (window.addEventListener) { el.addEventListener(type, function(e) { fn.call(el, e); }, capture); } else if (window.attachEvent) { el.attachEvent("on" + type, function(e) { fn.call(el, e); }); } };
我们每次调用事件时,都需要判断兼容问题, 但我们运用柯里化的方式就只要判断一次就行了
var addEvent = (function(){ if (window.addEventListener) { return function(el, sType, fn, capture) { el.addEventListener(sType, function(e) { fn.call(el, e); }, (capture)); }; } else if (window.attachEvent) { return function(el, sType, fn, capture) { el.attachEvent("on" + sType, function(e) { fn.call(el, e); }); }; } })();
还有一个作用就是延迟计算
小明每天都会花一部分钱吃饭
小明想知道它5天之后总共会花费多少钱
var total = 0 var fn = function(num) { total += num } fn(50) fn(70) fn(60) fn(100) fn(80)
这样我们便能算出它总共花了都少钱
但是小明又突然想知道 如果他每天花费的的钱翻一倍 会产生多少钱
于是我们是不是得改下 上面的 函数
var fn = function(num) { total += num*2 } fn(50) fn(70) fn(60) fn(100) fn(80)
那我们是不是有什么办法,先把这些数 存起来,到最后在进行计算
我们接着来封装
var curry = function(fn) { var args = [] return function() { if (arguments.length === 0) { return fn.apply(null, args) }else{ args = args.concat([].slice.call(arguments)) return curry.call(null, fn, args) } } } var curryFn = function() { var args = [].slice.call(arguments), total = 0 for (var i = 0; i < args.length; i++) { total += args[i] } return total } var fn = curry(curryFn) fn(50) fn(70) fn(60) fn(100) fn(80) fn() //不传参数的时候进行计算
这样我们只有最后的时候才进行计算。
而且只需要修改 curryFn 里面的计算方法就行
我们整理下上面的方法封装完整的柯里化函数
var curry = function (fn, length) { length = length || fn.length; var sub_curry = function (f) { var args = [].slice.call(arguments, 1); return function () { return f.apply(null, args.concat([].slice.call(arguments))) } } return function () { var args = [].slice.call(arguments); if (length > args.length) { var newArgs = [fn].concat(args); return curry(sub_curry.apply(null,newArgs), length - args.length) }else{ fn.apply(null,arguments) } } }
// 1. var fn = curry( function(a,b,c){ console.log(a, b, c) }) fn('a')('b')('c') // 2. fn1 = curry(function(){ console.log(arguments) }, 3) fn1('a')('b')('c')
参数复用;
提前返回;
延迟计算/运行
反柯里化
反柯里化的作用在与扩大函数的适用性,使本来作为特定对象所拥有的功能的函数可以被任意对象所用.
被任意对象使用? 是不是想到了用call, apply 设置this指向
通过 call/apply 被任意对象所用
var obj = { a: 1, fn: function (b) { return this.a + b } } obj.fn(2) // 3 var obj1 = {a:4} obj.fn.call(obj1, 2) // 6
反柯里化版本
var uncurrying= function (fn) { return function () { var context=[].shift.call(arguments); return fn.apply(context,arguments); } } // const uncurrying = fn => (...args) => Function.prototype.call.apply(fn,args) // 简洁版 var f = function (b) { return this.a + b } var uncurry = uncurrying(f) var obj = {a:1}, obj1 = {a:4} uncurry(obj, 2) // 3 uncurry(obj1, 2) // 3
相信大家已经看出区别了,这丫的就相当于一个外部的call方法
上面很多只是自己的部分理解,不一定准确。如果有不同理解,谢谢指出。
相关推荐:
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!