JavaScript的几个概念简单理解(深入解释见You Don't know JavaScript这本书)

时间:2023-03-10 05:13:21
JavaScript的几个概念简单理解(深入解释见You Don't know JavaScript这本书)

ES201X是JavaScript的一个版本。

ES2015新的feature

  • let, const
  • Scope, 块作用域
  • Hoisting
  • Closures
  • DataStructures: Objects and Arrays
  • this

let, const, Block Scope

新的声明类型let, const,配合Block Scope。(if, forEach,)

之前:

var,  Global scope和function scope。

之后:

let, const , 这2个类型声明用在Block Scope内。

声明一次还是多次?

let, const只能声明一次。但var声明可以反复声明:

const num = ;
const num = ; // SyntaxError: identifier 'num' has already been declared var x = ; // x = 2
var x = ; // x = 4

⚠️const 用于不会改变的值。

⚠️const x = {} , 可以改变其内的属性值,或增加新的key/value对儿。

这是因为,const variables当处理arrays 或objects时会改变,

技术上讲,不是re-assign分配一个新的值给它,而是仅仅改变内部的元素。

const farm = [];
farm = ['rice', 'beans', 'maize'] // TypeError: Assignment to constant variable //但可以
farm.push('rice')

Hoisting

Declarations will be brought to the top of the execution of the scope!

⚠️ var x = "hello" 这种声明加分配assign的写法无法Hoisting!!

⚠️ 函数声明也可以Hoisting.


Closures

lexical or function closures.

用于把函数和周围的state(var, const, let声明的变量,函数。)绑定一起使用。

换句话说,closure给你入口进入到函数作用域外面的区域,并使用这个区域的变量,函数。

function jump() {
var height = 10; function scream() {
console.log(height);
} return scream;
} var newJump = jump() //function runs but doesnot log anything newJump(); //logs 10

在函数jump中声明了height, 它只存在于函数作用域内。

通过Closure, 函数scream得到了height。

执行函数jump, 并把函数scream存入一个新的变量newJump, 这个newJump就是一个函数

执行newJump(), 就是执行scream。 而scream可以引用jump内的height。

JavaScript所做的是保持一个对原始作用域的引用,我们可以使用它和height变量。

这个引用就叫做closure。

另一个例子:

function add (a) {
return function (b) {
return a + b
};
} // use
var addUp7 = add(7)
var addUp14 = add(14) console.log (addUp7(8)); //
console.log (addUp14(12)); //

addUp7和addUp14都是closures。

例如:

addup7创造了一个函数和一个变量a。a的值是7。

执行addUp7(8), 会返回a + 8的结果15。

为什么要使用闭包closures?

开发者总是寻找更简洁和高效的编码方式,来简化日常工作和让自己更多产。

理解和使用closures就是其中的一个技巧。使用closures有以下好处:

1. Data Encapsulation

closures可以把数据储存在独立的scope内。

例子:

在一个函数内定义一个class

//使用(function(){}())在函数声明后马上执行,需要用()括起来。
(function () {
var foo = 0
function MyClass() {
foo += 1;
};
MyClass.prototype = {
howMany: function() {
return foo;
}
};
window.MyClass = MyClass;
}());

foo可以通过MyClass constructor存入和howMany方法取出。

即使IIFE方法被执行后退出, 变量foo仍然存在。MyClass通过closure功能存取它的值。

2.Higher Order Functions

简化多层嵌套的函数的代码。

//x从它被定义的位置被一步步传递到它被使用的位置。
function bestSellingAlbum(x) {
return albumList.filter(
function (album) { return album.sales >= x; }
);
}

filter自动获取了x,这就是闭包的作用。

如果没有closures, 需要写代码把x的值传递给filte方法。

现在使用ES6, 代码就更简单了。

const bestSellingAlbums = (x) => albumList.filter(album => album.sales >= x);

Closures的实践

1.和对象化编程一起使用

//声明一个函数对象
function count() {
var x = 0;
return {
increment: function() { ++x; },
decrement: function() { --x; },
get: function() { return x; },
reset: function() { x = 0; }
}
}

执行这个函数

var x = count();
x.increment() //返回1
x.increment() //返回2
x.get() //返回2
x.reset() //重置内部变量x为0

2.传递值和参数进入一个算法。

function proximity_sort(arr, midpoint) {
return arr.sort(function(x, y) { x -= midpoint; y -= midpoint; return x*x - y*y; });
}

3. 类似namspace的作用。

 var houseRent = (function() {
var rent = 100000;
function changeBy(amount) {
rent += amount;
}
return {
raise: function() {
changeBy(10000);
},
lower: function() {
changeBy(-10000);
},
currentAmount: function() {
return rent;
}
};
})();

houseRent可以执行raise, lower, currentAmount函数。但不能读取rent,changeBy函数。

Closures在函数编程中的2个重要concepts(不理解?)

  • partial application 局部应用
  • currying 梳理(?)

Currying是一种简单的创建函数的方法,这种方法会考虑一个函数的参数的局部使用。

简单来说,一个函数接受另一个函数和多个参数,并返回一个函数及较少的参数。

Partial application搞定了在返回函数中的一个或多个参数。

返回的函数接受了剩下的parameters作为arguments来完成这个函数应用。

    partialApplication(targetFunction: Function, ...fixedArgs: Any[]) =>
functionWithFewerParams(...remainingArgs: Any[])

使用array的普通方法的内部结构来理解这2个概念:

Array.prototype.map()方法

map用来创建一个新的array。 调用一个函数作为参数,这个函数应用到每个array元素上,并返回结果。

var new_array = arr.map(function callback(currentValue[, index[, array]]) {
// Return element for new_array
}[, thisArg])

参数Parameters:

  • callback:函数,用于创建新array的元素,它接受3个参数,第一是必须的:
    • currentValue: 在array中,正在被执行的元素。
    • index:  current element在array 中的索引。
    • array: 调用map方法的数组本身。
  • thisArg: 当执行callback时,作为this本身。

Return value:

一个新的array,每个元素是被callback函数处理过的。

内部结构:

 Array.prototype.map = function(callback) {
arr = [];
for (var i = 0; i < this.length; i++)
arr.push(callback(this[i],i,this));
return arr;
};

Array.prototype.filter()方法

和map结构类似,但是,callback函数用来测试每个数组中的元素。如果返回ture则保留这个元素。


Data Structures: Objects and Arrays

相比String, Number, Boolean, Null, Undefined更复杂的数据结构。可以看作是其他数据结构的容器。

一般来说,几乎everything in JS is an Object. 因此, JS可以看成是对象化编程语言。

在浏览器中,一个window加载后,Document对象的一个实例就被创建了。

在这个页面上的所有things都是作为document object的child。

Document methods是用来对父对象进行相关的操作。

const myObject = {
myKey1: 'ObjectValue1',
myKey2: 'ObjectValue2',
mykeyN: 'ObjectValueN',
objectMethod: function() {
// Do something on this object
}
};

为什么说everything is an Object?

const myMessage = "look at me!"这个数据类型是字符串。

但其实它是由String对象生成的实例instance,通过_proto_属性,可以调用String中的methods.

String.prototype.toUpperCase.

myMessage.toUpperCase()

⚠️:

myMessage是String的一个实例,myMessage的_proto_属性,就是String.prototype。

实例myMessage通过_proto_属性,调用String中储存在prototype上的方法。

// simple way to create a string
const myMessage = 'look at me go!'; // what javascript sees
const myOtherMessage = String('look at me go!'); myMessage == myOtherMessage; // true

扩展知识点:见博客:原型对象和prototype(https://www.cnblogs.com/chentianwei/p/9675630.html)

constructor属性

还是说上面的例子,String实例myMessage的数据类型是什么:

typeof myOtherMessage
//返回 "string"

这是如何构建的?使用实例的constructor属性:

myOtherMessage.constructor
//得到,ƒ String() { [native code] }

由此可知myOtherMessage变量用于存储一个字符串。它本身是由String构造的实例对象。

everything is object,理解了把!


"this" Keyword

详细看(博客)(或者YDJS对应的章节)
4条rule:

  1. default binding
  2. implicit binding
  3. explicit binding:  使用call(), apply(),bind()方法明确指定call-site
  4. new binding

使用方法:

先找到call-site,然后检查符合哪条rule。

call-site,即一个函数被调用的地点,也可以说一个函数所处的execution context。

优先级:

new > explicit > implicit > default

Rule 1: Default (Global) Binding

  1. 默认使用全局作用域。
  2. 如果在函数中使用'use strict'模式,this的作用域是这个函数的作用域。

Rule 2: Implicit Binding

根据call-site。根据对象。

例子:

var company = {
name: 'Baidu',
printName() {
console.log(this.name)
}
} var name = 'google' company.printName() // Baidu, 因为printName()函数被company对象调用。 var printNameAgain = company.printName printNameAgain(); // google, 因为这个函数被全局对象调用。

Rule 3: Explicit Binding

可以明确指定调用点。

使用call, apply ,bind方法。传递对象作为参数。第一个参数是一个对象,它被传递给this关键字。

因此this指向就明确了。

//根据上例子

printNameAgain.call(company)   //  Baidu.

call(), 可以传递string, 数字等作为其他参数。

apply(), 只能接受一个数组作为第2个参数.

bind()最为特殊,它会绑定一个context。然后返回这个函数和调整后的context。

var printFunc = printNameAgain.bind(company)
//printFunc是printNameAgain函数,并绑定company对象, this永远指向company。
printFunc() // Baidu

Rule 4: Constructor Calls with new

使用函数构造调用。如new Number()

注意:

constructor就是一个函数,和new操作符一起在被调用时运行。和类无关也不实例化类!就是一个标准的函数。

当一个函数前面有一个new, 会自动做以下事情:

  1. a brand new object is created
  2. the newly constructed object is [[Prototype]]-linked,
  3. the newly constructed object is set as the this binding for that function call
  4. unless the function returns its own alternate object, the new-invoked function call will automatically return the newly constructed object

第3句:新构建的对象被设置为this, 这个对象和new-invoked函数调用绑定在一起。

function Company() {
this.name = 'baidu'
} var b = new Company() //当执行代码时,会发生:
function Company() {
// var this = {};
this.name = 'Scotch'
// return this; 给变量b。
}

由此可知:

第4句:默认情况下,使用new关键字的函数,会自动地返回新构建的对象。除非明确指定返回对象。

Async Handling

在异步逻辑时,函数回调可能存在this binding的陷阱。

因为这些handers往往绑定一个不同的context,让this的行为产生非期待的结果。

Event handing就是一个例子。事件处理器是回调函数。在运行时,当事件被激活,回调函数被执行。

浏览器需要提供和这个event相关的contextual information。它会绑定到回调函数中的this关键字。

以下代码,在执行后可以看到this的绑定对象:

button.addEventListener('click', function() {
console.log(this)
});

假如你希望产生某个行为:

var company = {
name: 'Scotch',
getName: function() {
console.log(this.name)
}
} // This event's handler will throw an error
button.addEventListener('click', company.getName)

因为浏览器提供的contextual info内,不一定有name这个属性。所以可能会报告❌。

告诉你name property is not existed。

或者有这个name属性,但返回的值肯定不是你想要的结果。

所以需要使用bind()方法,明确指定this的绑定:

button.addEventListener('click', company.getName.bind(company))


Basic Deisgn Patterns

软件工程设计,存在大量不同的设计模式。

JS是一种非传统的面向对象的编程语言。

设计模式(全免介绍不同的JS设计模式)

(https://www.dofactory.com/javascript/design-patterns)

3种设计模式分类:

Creational patterns:

这种模式集中在创建对象。当在一个大型程序种创建对象时,有让对象变复杂的趋向。通过控制对象的创建,Creational design patterns创造式设计模式可以解决这个问题。

Structural patterns:

这种模式提供了方法,用于管理对象和对象的关系,以及创建class structure。

其中一种方法是通过使用inheritance继承, 和composition,从多个小的对象,到创建一个大的对象。

Behavioral patterns

这种模式集中在对象之间的交互上。

区别:

  • Creational patterns描述了一段时间。
  • Structural patterns描述了一个或多或少的static structure。
  • 而Behavioral patterns描述了一个process, a flow。

Creational Patterns

Module模块

这种模式常用于软件开发,这个module pattern可以被看作 Immediately-Invoked-Function-Expression (IIFE).

(function() {

  // code goes here!

})();

所有module代码在一个closure内存在。

Variables通过执行函数传入value来进口imported, 并返回一个对象来出口exported。

这种Modules比单独的函数使用的方法有优势:

它能够让你的global namespace 更干净,更可读性,也让你的函数可以importable and exportable.

一个例子:

const options = {
username: 'abcd',
server: '127.0.0.1'
}; const ConfigObject = (function(params) { // return the publicly available things
// able to use login function at the top of this module since it is hoisted
return {
login: login
}; const username = params.username || '',
server = params.server || '',
password = params.password || ''; function checkPassword() {
if (this.password === '') {
console.log('no password!');
return false;
} return true;
} function checkUsername() {
if (this.username === '') {
console.log('no username!');
return false;
} return true;
} function login() {
if (checkPassword() && checkUsername()) {
// perform login
}
} })(options);

Builder

这种模式的典型就是jQuery,虽然现在不再使用它了。

const myDiv = $('<div id="myDiv">This is a div.</div>');
// myDiv now represents a jQuery object referencing a DOM node.
const myText = $('<p/>');
// myText now represents a jQuery object referencing an HTMLParagraphElement.
const myInput = $('<input />');
// myInput now represents a jQuery object referencing a HTMLInputElement

这种模式,让我们构建对象而无需创建这个对象,我们需要做的是指定数据type和对象的content。

这种模式的关键:

It aims at separating an object’s construction from its representation

我们无需再设计construction了。只提供内容和数据类型即可。

$ variable adopts the Builder Pattern in jQuery.

因此,无需再使用如document.createElement('div')等传统的创建node的method。

除了Module和Builder,Creational Patterns还有Factory Method, Prototype, Singleton

Abstract Factory Creates an instance of several families of classes Builder Separates object construction from its representation Factory Method Creates an instance of several derived classes Prototype      A fully initialized instance to be copied or cloned Singleton      A class of which only a single instance can exist    


Structural Patterns

Facade

A single class that represents an entire subsystem

这种模式:简单地把一大片逻辑隐藏在一个简单的函数调用中function call。

内部的子程序和layers也被隐藏,并通过a facade来使用。

这种模式让开发者看不到它的内部结构。开发者也无需关心它。

例子:

$(document).ready(function() {

  // all your code goes here...

});

Composites

Composites are objects composed of multiple parts that create a single entity.

A tree structure of simple and composite objects

例子:

$('.myList').addClass('selected');
$('#myItem').addClass('selected'); // dont do this on large tables, it's just an example.
$('#dataTable tbody tr').on('click', function(event) {
alert($(this).text());
});

其他模式:

  Adapter    Match interfaces of different classes
Bridge Separates an object’s interface from its implementation
Composite A tree structure of simple and composite objects
Decorator Add responsibilities to objects dynamically
Facade A single class that represents an entire subsystem
Flyweight A fine-grained instance used for efficient sharing
Proxy An object representing another object

Behavioral patterns

Observer

The observer design pattern implements a single object which maintains a reference to a collection of objects and broadcasts notifications when a change of state occurs. When we don’t want to observe an object, we remove it from the collection of objects being observed.

Vue.js对Vue实例中 data属性的追踪,应该是一个Observer pattern。

结论:

没有完美的设计模式。各种模式都是为了更好的写web app。

相关书籍:

"Learning JavaScript Design Patterns" by Addy Osmani


Callbacks, Promises, and Async

函数是First-Class Objects:

1.函数可以被当作value,分配给变量

2.可以嵌套函数

3.可以返回其他函数。

Callback Functions

当一个函数简单地接受另一个函数作为参数argument, 这个被当作参数的函数就叫做回调函数。

使用回调函数是函数编程的核心概念。

例子:

setInterval(),每间隔一段time, 调用一次回调函数。

setInterval(function() {
console.log('hello!');
}, 1000);
var intervalID = scope.setInterval(func, delay[, param1, param2, ...]);

例子:

Array.map(), 把数组中的每个元素,当作回调函数的参数,有多少元素,就执行多少次回调。最后返回一个新的array.

Naming Callback functions

回调函数可以被命名,回调函数可以是异步的。setInterval()中的函数就是异步函数。

function greeting(name) {
console.log(`Hello ${name}, welcome to here!`)
} function introduction(firstName, lastName, callback) {
const fullName = `${firstName} ${lastName}`
callback(fullName)
}
introduction('chen', 'ming', greeting)
// Hello chen ming, welcome to here!

当执行introduction()时,greeting函数被当作参数传入,并在introduction内部执行。

回调地狱Callback hell

function setInfo(name) {
address(myAddress) {
officeAddress(myOfficeAddress) {
telephoneNumber(myTelephoneNumber) {
nextOfKin(myNextOfKin) {
console.log('done'); //let's begin to close each function!
};
};
};
};
}

除了不好看外,当变复杂后,传统的回调函数调用还会出现回调函数调用过早,或者过晚,等等问题

具体看这篇博客https://www.cnblogs.com/chentianwei/p/9774098.html

Promises

因为Promises封装了时间依赖状态the time-dependent state ---等待满足或拒绝这个潜在的value--从外部,因此Promises可以被composed(combined, 比如x和y做加法运算)

一旦一个Promise被解决,它就是一个不变的值,如果需要可以多次被observed。

Promises是一个方便的可重复的机制用于封装和组织furture value。

Promise有3个状态:

  • Pending: 初始状态,在一个操作开始前的状态.
  • Fulfilled: 当指定的操作被完成后的状态。
  • Rejected: 操作未完成,或者抛出一个❌值
const promise = new Promise(function(resolve, reject) {
//相关代码
//resole, reject是2个Promise的内置函数。处理不同的结果。
})
var weather = true
const date = new Promise(function(resolve, reject) {
if (weather) {
const dateDetails = {
name: 'Cubana Restaurant',
location: '55th Street',
table: 5
}; resolve(dateDetails)
} else {
reject(new Error('Bad weather, so no Date'))
}
});

date的值是一个Promise对象,其中:

属性[[PromiseStatus]]的值是"resolved",

(如果weather = false, 最后date中的属性[[PromiseStatus]]的值是"rejected")

属性[[PromiseValue]]的值是一个对象.

JavaScript的几个概念简单理解(深入解释见You Don't know JavaScript这本书)

使用Promise对象

当得到promise对象后,可以使用then(), catch(),调用回调函数,

对promise对象进行进一步操作,并返回一个新的promise对象。

当Promise对象中的属性[[PromiseStatus]]的值:

  • resolve,会调用then()
  • rejecte,   会调用catch().

例子:

const weather = false
const date = new Promise(function(resolve, reject) {
if (weather) {
const dateDetails = {
name: 'Cubana Restaurant',
location: '55th Street',
table: 5
}; resolve(dateDetails)
} else {
reject('Bad weather, so no Date')
}
}); date
.then(function(done) {
//
})
.catch(function(err){console.log(err)})
//最后输出'Bad weather, so no Date'

我们使用