参考文档:
1,基本语法
1,数组
1,数组方法
es5
1,slice
arrayObject.slice(start,end);
start,必需。规定从何处开始选取。如果是负数,那么它规定从数组尾部开始算起的位置。也就是说,-1 指最后一个元素,-2 指倒数第二个元素,以此类推。
end,可选(不包含该元素)。规定从何处结束选取。该参数是数组片断结束处的数组下标。如果没有指定该参数,那么切分的数组包含从 start 到数组结束的所有元素。如果这个参数是负数,那么它规定的是从数组尾部开始算起的元素。
2,map
- 基本用法
array.map(function(currentValue, index, arr), thisIndex)
参数说明:
function(currentValue, index, arr):必须。为一个函数,数组中的每个元素都会执行这个函数。其中函数参数:
currentValue:必须。当前元素的的值。
index:可选。当前元素的索引。
arr:可选。当前元素属于的数组对象。
thisValue:可选。对象作为该执行回调时使用,传递给函数,用作"this"的值。
返回值是处理完的结果。
map()方法不会对空数组进行检测。
map()不会改变原始数组。
3,indexOf
ES5
//用于查找数组中是否存在某个值,没有则返回-1
let list = [1, 2, 3];
console.log(list.indexOf(2)) // 1
console.log(list.indexOf("蛙人")) // -1
//内部使用严格相等符进行判断,会导致对NaN的误判
[NaN].indexOf(NaN)// -1
4, forEach
用途:用于便利一个数组,接受三个参数:当前值,索引值,当前数组,返回值为undefined。
let list = [1, 2, 3];
const res = list.forEach((value, key, self) => {
console.log(value) // 1 2 3
console.log(key) // 0 1 2
console.log(self) // [1, 2, 3]
return 123
})
console.log(res) // undefined
forEach()方法也不会改变原数组。
5,splice
用途:用于数组删除或者替换内容,接受三个参数
- 删除或者添加的位置
- 要删除的数量
- 向数组中添加的内容
let list = [1, 2, 3];
list.splice(0, 1) // 把第0个位置,给删除一位
console.log(list) // [2, 3]
list.splice(0, 1, "蛙人") // 把第0个位置,给删除一位,添加上一个字符串
console.log(list) // ["蛙人", 2, 3]
list.splice(0, 2, "蛙人") // 把第0个位置,给删除2位,添加上一个字符串
console.log(list) // ["蛙人", 3]
6,filter
用途:用于过滤数组中符合条件的值,返回满足条件的数组对象
let list = [1, 2, 3];
let res = list.filter(item => item > 1);
console.log(res) // [2, 3]
7,every
用途:用于检测数组的所有元素是否满足条件,返回值为boolean,数组中的所有元素满足条件返回true,否则返回false。
let list = [1, 2, 3];
let res = list.every(item => item > 0)
console.log(res) // true
let res1 = list.every(item => item > 1)
console.log(res1) // false
8,some
用途:用于检测数组中是否有元素满足条件,返回值为boolean.
let list = [1, 2, 3];
let res = list.some(item => item > 0)
console.log(res) // true
9,reduce
用法:接收一个函数作为累加器,最终返回累加值。接收四个参数:
- 初始值,也即计算结束后的返回值。
- 当前元素
- 当前元素在数组中的索引。
- 当前元素所属的数组对象。
let list = [1, 2, 3];
let res = list.reduce(( prev, cur ) => prev += cur, 0)
console.log(res) // 6
1,计算数组中所有值的总和,或者最值。
// 处理总和的累计器
const delTotal = (acc, item) => {
return acc + item;
};
// 处理最最大值的累计器
const delMax = (acc, item) => {
return (acc > item) ? acc : item
};
const data = [1, 2, 3];
const total = data.reduce(delTotal);
const max = data.reduce(delMax);
console.log("The sum is: ", total); //The sum is: 6
console.log("The max is: ", max); //The max is: 3
console.log("data: ", data); //data: [1, 2, 3]
2,数组去重
const numberArray = [1,3,4,5,4,5,3,4]
const stringArray = ['a', 'b', 'a', 'b', 'c', 'e', 'e']
const delRepetition = (accumulator, currentValue)=> {
if (accumulator.indexOf(currentValue) === -1) {
accumulator.push(currentValue)
}
return accumulator
}
const repNumberArray = numberArray.reduce(delRepetition,[])
const repStringArray = stringArray.reduce(delRepetition,[])
console.log("The repNumberArray is: ", repNumberArray); //The repNumberArray is: [1, 3, 4, 5]
console.log("The repStringArray is: ", repStringArray); //The repStringArray is: ["a", "b", "c", "e"]
也可以使用:
let newArray = new Array(new Set(oldArray));
3, 将二维数组转换为一维
const flattened = [[0, 1], [2, 3], [4, 5]].reduce(
( acc, cur ) => acc.concat(cur),
[]
);
console.log("flattened: ", flattened); //flattened: [0, 1, 2, 3, 4, 5]
4,计算数组中每个元素出现的次数
const initialValue = {};
const reducer = (tally, vote) => {
if (!tally[vote]) {
tally[vote] = 1;
} else {
tally[vote] = tally[vote] + 1;
}
return tally;
};
const votes = [
"vue",
"react",
"angular",
"vue",
"react",
"angular",
"vue",
"react",
"vue"
];
const result = votes.reduce(reducer, initialValue);
console.log("Result: ", result) // Result: {vue: 4, react: 3, angular: 2}
console.log("Vue: ", result.vue) // Vue: 4
console.log("React: ", result.react) // React: 3
10,reverse
用途:用于数组反转
let list = [1, 2, 3];
let res = list.reverse();
console.log(res) // [3, 2, 1]
11,join
用法:数组元素拼接为字符串
let list = [1, 2, 3];
let res = list.join("-");
console.log(res) // 1-2-3
let sum = eval(list.join("+"))
console.log(sum) // 6
12,sort
用法:数组排序,排序规则看返回值
- 返回值为正数,后面的数在前面
- 返回值为负数,后面的数在后面
- 返回值为0,位置不变
let list = [1, 2, 3];
let sort = list.sort((a, b) => b - a)
console.log(sort) // [3, 2, 1]
13,concat
用法:合并数组
let list = [1, 2, 3];
let res = list.concat([1, 2, 3])
console.log(res) // [1, 2, 3, 1, 2, 3]
14,push
用法:向数组末尾添加元素,改变了原数组,返回值为数组的长度。
let list = [1, 2, 3];
let res = list.push(1)
console.log(res) // 4
15,pop
用法:用于删除数组尾部的元素,改变了原数组,返回值为删除的元素。
let list = [1, 2, 3];
let res = list.pop()
console.log(res) // 3
16,shift
用法:删除数组头部的元素,改变了原数组,返回值为删除的元素。
let list = [1, 2, 3];
let res = list.shift()
console.log(res) // 1
17,unshift
用法:在数组的头部添加元素,改变了原数组,返回值为数组的长度。
let list = [1, 2, 3];
let res = list.unshift(1)
console.log(res) // 4
18,toString
用法:将数组转换为字符串,不改变原数组,返回值为字符串。
let list = [1, 2, 3];
let res = list.toString()
console.log(res) // 1,2,3
19,Array.isArray
用法:检测对象是否是一个数组,返回值为boolean。
let list = [1, 2, 3];
let res = Array.isArray(list)
console.log(res) // true
es6+
20,includes
es7
用法:检测数组中是否存在某元素,返回值为boolean。
let list = [1, 2, 3];
let res = list.includes("蛙人")
let res1 = list.includes(1)
console.log(res, res1) // false true
list.includes(1, 2) // false 该方法的第二个参数表示搜索的起始位置,默认为 0
21,find
用法:查找数组的元素,返回值为符合条件的单个元素,按照就近原则返回。
可以查找NaN.
let list = [1, 2, 3];
let res = list.find((item) => item > 1)
console.log(res) // 2, 按照就近原则返回
22,findIndex
用法:查找数组的元素,返回值为符合条件的单个元素在数组中的索引,按照就近原则返回。
可以查找NaN.
let list = [1, 2, 3];
let res = list.findIndex((item) => item > 1)
console.log(res) // 1, 按照就近原则返回下标
//找不到就返回-1.
23,flat
用法:用于拉平嵌套数组对象,不改变原数组,返回值为拉平的数组对象。
let list = [1, 2, 3, [4, [5]]];
let res = list.flat(Infinity)
console.log(res) // [1, 2, 3, 4, 5]
24,fill
用法:用于填充数组对象,改变了原数组,返回值为填充后的数组。
let list = [1, 2, 3];
let res = list.fill(1)
console.log(res) // [1, 1, 1]
25,Array.of
用法:用于生成一个数组对象。
let res = Array.of(1, 2, 3)
console.log(res) // [1, 2, 3]
26,Array.from
用法:将伪数组转化为真数组。
let res = Array.from(document.getElementsByTagName("div"))
console.log(res) // 转换为真数组就可以调用数组原型的方法
27,include
ES7
判断数组中是否包含某个元素。
ES7之前的方法:
- indexOf() 返回在数组中找到的给定元素的第一个索引,不存在返回 -1.
if (arr.indexOf(el) !== -1) {
// ...
}
缺点:
- 不够语义化,表达不够直观。
- 内部使用严格相等符进行判断,导致对NaN的误判。
[NaN].indexOf(NaN)// -1
- find() 与 findIndex() 这两个方法可以发现Nan。
28,其他类型转换为数组
1,类数组对象转换为数组
var arr = Array.prototype.slice.call(arrLike);
var arr = [].slice.call(arrLike);
// ES2015
const arr = Array.from(arrLike);
const arr = [...arrLike];
2,数组属性
1,length
通常用于返回数组的长度,但是也是一个包含复杂行为的属性,并不是统计数组中元素的数量,而是代表数组中最高索引的值。
const arrs = [];
arrs[5] = 1;
console.log(arrs.length); // 6
使用delete删除数组的元素,不能改变length。
const arrs = [1, 2, 3];
delete arrs[2]; // 长度依然为3
可以对数组的length进行修改,如果修改值小于数组本身的最大索引值,会对数组进行部分截取。
const arrs = [1, 2, 3, 4];
arrs.length = 2; // arrs = [1, 2]
arrs.length = 0; // arrs = []
如果赋予的值大于最大索引值,会得到一个稀疏数组。
const arrs = [1, 2];
arrs.length = 5; // arrs = [1, 2,,,,]
3,数组其他问题
1,数组去重
Array.from(new Set([1, 1, 2, 2]))
2,生成[1,2,3,...,N]
采用new Array(10)生成的是一个拥有10个undefined元素的数组。
Object.getOwnPropertyNames([1, 2, 3]) // ["0", "1", "2", "length"]
const a = new Array(3) // [undefined, undefined, undefined]
Object.getOwnPropertyNames(a) // ["length"]
- ES5
Array.apply(null, { length: 10 }).map((v, k) => k)
- ES6
Array.from(new Array(10), (k, v) => v)
2,基本类型
1,number
在JavaScript中整数和浮点数都属于number类型,所有数组都是以64位浮点数的形式存储。
其他
1, .. 问题
在解析语句时,允许数字后面跟一个小数点。
//true
1. === 1
解释器无法解析1.toString()这样的语句,会抛出Uncaught SyntaxError,此时表达式中的.并没有解释为属性访问器,所以程序报错。
2,布尔值
1,转换为布尔值
- !! 使用 !! 将其他类型的值转化为布尔值
3,NaN
not a number
1,判断NaN 的方法
1,Object.is(NaN, value)
2, isNaN() 与 Number.isNaN()
两者区别:如果当前值为NaN或者将其强制转为数字后是NaN,前者将返回true;后者只有当前值为NaN是才返回true。
isNaN('hello world'); // true
Number.isNaN('hello world'); // false
2,出现NaN的情况
- 1,计算失败时,作为Math的某个方法的返回值,例如
Math.sqrt(-1)) - 2,将一个字符串解析为数字失败,例如
parseInt("blabla")
4,string
1, 常用方法
- includes es6
其他
1,undefined与null的区别
undefined表明变量未声明,当前变量不存在。
null表明变量存在,但是变量的值是null。
3,标准内置对象(JavaScript标准库)
1,值属性
这些全局属性返回一个简单值,这些值没有自己的属性和方法。
1,Infinity
2, NaN
3,undefined
4,globalThis
2,函数属性
全局函数可以直接调用,不需要在调用时指定所属对象,执行结束会会将结果直接返回给调用者。
1, eval()
2, uneval()
3, isFinite()
这个全局函数用来判断被传入的参数值是否为一个有限数值(finite number),
4, isNaN()
5, parseFloat()
6, parseInt()
7, decodeURI()
7, decodeURIComponent()
7, encodeURI()
7, encodeURIComponent()
3,基本对象
基本对象是定义和使用其他对象的基础。基本对象包括一般对象,函数对象和错误对象。
1,Object
1,静态方法
1,Object.assign()
通过复制一个或者多个对象来创建一个新的对象。
会改变原对象。
2,Object.create()
创建一个新对象,使用现有的对象来作为新对象的__proto__。
ES5之后,可以使用Object.create(null)来创建没有原型的对象。
3,Object.defineProperty()
直接在一个对象上定义一个新属性,或者修改一个对象的现有属性。
const object1 = {};
Object.defineProperty(object1, 'property1', {
value: 42,
writable: false
});
object1.property1 = 77;
// throws an error in strict mode
console.log(object1.property1);
// expected output: 42
对象里目前存在的属性描述符有两种:数据描述符和存取描述符。
- configurable 当且仅当某个属性的configurable的键值为true时,该属性的属性描述符才能改变,同时该属性也能从对应的对象上删除。默认值为false。
- enumerable 当属性的enumerable键值为true时,该属性才会出现在对象的枚举属性中。默认值为false.
- value 属性的的值,默认为undefined。
- writable 当属性的writable键值为true时,该属性的值才能被赋值运算符改变。默认值为false。
- 如果一个描述符同时拥有value,writable,get, set键,会产生一个异常。
4, Object.is
用于比较两个值是否相等。
2, Object.prototype
原型对象。
1,Object.prototype.toString
每个对象都有一个toString(),用于将对象以字符串方式引用时自动调用,如果此方法未被覆盖,toString()会返回[object type], 因此Object.prototype.toString.call只是为了调用原生对象上未被覆盖的方法。
2, Function
3, Boolean
Truthy与Falsy。
对每一个类型的值来说,都可以转为boolean类型的值,Falsy表示在Boolean对象中表现为false的值。
在条件判断和循环中,JavaScript会将任意类型强制转换为Boolean对象。
如下都表示为falsy
if (false)
if (null)
if (undefined)
if (0)
if (NaN)
if ('')
if ("")
if (document.all)
除此之外都表示为Truthy.
4, Symbol
4,错误对象
1, Error
5,数字和日期对象
1, Number
Number对象的作用:
- 1,如果参数无法被转换为数字,则返回NaN.
- 2, 在非构造器上下文中(没有使用new操作符),Number能被用来执行类型转换。
new Number(value);
var a = new Number('123'); // a === 123 is false
var b = Number('123'); // b === 123 is true
a instanceof Number; // is true
b instanceof Number; // is false
2, BigInt
3, Math
4, Date
6,字符串
7,可索引的集合对象
8,使用键的集合对象
1,Map
Map对象保存键值对,并且能够记住键值对的原始插入顺序。任何值(对象或者原始值)都可以作为一个键或者一个值。
1,Map与Object的对比
- 在Object中,key值必须是简单数据类型,而在Map中可以是JavaScript的任意数据类型。
- Map元素的顺序遵循插入的顺序,而Object没有这一特性。
- Map继承自Object。
- 新建
var map = new Map([[1, 2], [2, 3]]); // map = {1 => 2, 2 => 3}
- 数据访问
map.get(1)
2,Map与Object的适用场景
- 当要存储的是简单数据类型
- JSON直接支持Object,但不支持Map。
- Map在存储大量元素的时候性能更好。
2, Set
3, WeakMap
4, WeakSet
9,结构化数据
这些对象用来表示和操作结构化的缓冲区数据,或使用JSON编码的数据
1, JSON
1, JSON.parse(JSON.stringify(Obj))
常用来做深拷贝,将对象进行JSON字符串格式化再进行解析,即可获得一个新的对象,但是性能不好,无法处理闭环问题。
const obj = {a: 1};
obj.b = obj;
JSON.parse(JSON.stringify(obj)) // Uncaught TypeError: Converting circular structure to JSON
能使用浅拷贝尽量使用浅拷贝。例如:
{...obj}
Object.assign({}, obj)
也可以使用npm:clone这个第三方的包。
10,控制抽象对象
1,Promise
继承自Object。
Promise对象用于表示一个异步操作的最终完成及其结果值。
1,静态方法
1,Promise.all(iterable)
这个方法返回一个新的promise,在iterable参数对象里所有的promise都成功的时候才会触发成功,一旦iterable中有任何一个失败都会触发该promise的失败。这个新的promise在触发成功状态之后,会把一个包含iterable里所有promise返回值的数组作为成功回调的返回值,顺序与iterable的顺序一致;如果触发了失败状态,会把iterable里第一个触发失败的promise的错误信息作为它的失败信息。
2,Promise.allSettled(iterable)
在所有iterable都出结果之后,返回一个数组,这个数组是所有iterable项的返回值。
3,Promise.any(iterable)
当其中一个promise成功,就返回成功的promise的值。
4,Promise.race(iterable)
当iterable中的任一项出结果之后,就返回该promise的值。
5,Promise.reject(reason)
返回一个状态为失败的promise对象
6,Promise.resolve(value)
返回一个状态由value决定的promise
2,promise还存在的问题
- 无法取消promise,一旦新建就会立即执行,无法中途取消。
- 如果不设置回调函数,promise内部抛出的错误,不会反映到外部。
- 当处于padding状态时,无法知道当前的阶段。
3,手动实现promise
class Promise {
constructor(params) {
//初始化state为pending
this.state = 'pending';
//成功的值,返回一般都是undefined
this.value = undefined;
//失败的原因,返回一般都是undefined
this.reason = undefined;
//成功执行函数队列
this.onResolvedCallbacks = [];
//失败执行函数队列
this.onRejectedCallbacks = [];
//success
let resolve = value => {
if (this.state === 'pending') {
//state change
this.state = 'fulfilled';
//储存成功的值
this.value = value;
//一旦成功,调用函数队列
this.onResolvedCallbacks.forEach(fn => fn());
}
};
//error
let reject = reason => {
if (this.state === 'pending') {
//state change
this.state = 'rejected';
//储存成功的原因
this.reason = reason;
//一旦失败,调用函数队列
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
params(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : err => {
throw err
};
let promise2 = new Promise((resolve, reject) => {
//当状态是fulfilled时执行onFulfilled函数
if (this.state === 'fulfilled') {
//异步实现
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
//当状态是rejected时执行onRejected函数
if (this.state === 'rejected') {
//异步实现
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
//当状态是pending时,往onFulfilledCacks、onRejectedCacks里加入函数
if (this.state === 'pending') {
this.onResolvedCallbacks.push(() => {
//异步实现
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
//异步实现
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
});
};
});
return promise2;
}
catch(fn) {
return this.then(null, fn);
}
}
function resolvePromise(promise2, x, resolve, reject) {
//循环引用报错
if (x === promise2) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
//防止多次调用
let called;
//判断x
if (x != null && (typeof x === 'object' || typeof x === 'function')) {
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, y => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
}, err => {
if (called) return;
called = true;
reject(err);
})
} else {
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
//resolve方法
Promise.resolve = function (val) {
return new Promise((resolve, reject) => {
resolve(val)
});
}
//reject方法
Promise.reject = function (val) {
return new Promise((resolve, reject) => {
reject(val);
});
}
const test = new Promise((res, rej) => {
setTimeout(() => {
res('resolve after 2000ms');
}, 2000)
})
test.then(res => {
console.error('res: ', res); // res: resolve after 2000ms
})
2,Generator
生成器。
3,async/await
async函数时Generator的语法糖,返回的是一个promise,可以使用then,catch方法。
11,反射
12,国际化
13,WebAssembly
14, 其他
1,Arguments
arguments是一个对应于传递给函数的参数的类数组对象。
function func1(a, b, c) {
console.log(arguments[0]);
// expected output: 1
console.log(arguments[1]);
// expected output: 2
console.log(arguments[2]);
// expected output: 3
}
func1(1, 2, 3);
arguments对象不是一个数组,是类数组对象,除了length属性和索引元素之外没有任何数组属性,但是它可以被转变为真正的数组。
1,arguments.callee
引用该函数体内当前正在执行的函数。在函数名称是未知时很有用,例如在匿名函数表达式内。
在严格模式下,ES5禁止使用arguments.callee,
4, 函数
1,定义函数
1,函数表达式
在函数表达式中可以省略函数名称
函数表达式没有提升,在定义函数表达式之前,不能使用它。
1,命名函数表达式
如果需要在函数体中使用当前函数,则需要创建一个命名函数表达式。函数名称只会作为函数体内的本地变量。
2,函数声明
5,异步代码
1,promise
2,async/await
在定义函数时使用async关键字,await关键字只能在使用async关键字定义的函数内部使用。
await不能脱离async使用。async是一个通过异步执行并隐式返回Promise作为结果的函数。
async function foo () {
return '浪里行舟'
}
foo().then(val => {
console.log(val) // 浪里行舟
})
6,闭包
1,什么是闭包
当声明一个函数时,同时还会创建一个闭包,包括这个函数的执行上下文所有变量的词法作用域。
函数创建的时候,闭包会将当前执行上下文中引用的变量进行打包Closure,并存放到[[Scopes]]中(V8实现)。
闭包是函数和声明该函数的词法环境的组合。
更通俗一点,内层函数引用外层函数上的变量,就形成一个闭包。
2,闭包的作用
闭包的最大作用就是用来变量私有,通过闭包来存储一些状态数据。例如react hooks。
- 示例1
function Person() {
// 以 let 声明一个局部变量,而不是 this.name
// this.name = 'zs' => p.name
let name = 'hm_programmer' // 数据私有
this.getName = function(){
return name
}
this.setName = function(value){
name = value
}
}
// new:
// 1. 创建一个新的对象
// 2. 让构造函数的this指向这个新对象
// 3. 执行构造函数
// 4. 返回实例
const p = new Person()
console.log(p.getName()) // hm_programmer
p.setName('Tom')
console.log(p.getName()) // Tom
p.name // 访问不到 name 变量:undefined
,闭包的问题
引用的对象过大时,会导致内存泄漏
3,执行上下文
执行上下文分为全局执行上下文和函数执行上下文。
执行环境的建立分为两个阶段:进入执行上下文(创建阶段)和执行阶段。
- 创建阶段 发生在函数调用时,但在执行具体代码之前。具体完成创建作用域链,创建变量、函数和参数以及求this的值。
- 执行阶段
主要完成变量赋值、函数引用和解释执行其他代码。
可以将执行上下文看做一个对象:
EC = {
VO:{/*函数中的arguments对象、参数、内部变量以及函数声明*/}
this:{},
Scope:{/*VO以及所有父执行上下文中的VO*/}
}
1,变量对象
每个执行环境都对应一个变量对象,在该执行环境中定义的所有变量和函数都存放在对应的变量对象中。
2,活动对象
3,作用域链
当代码在一个环境中执行时,都会创建一个作用域链。保证对执行环境有权访问的所有变量和函数的有序访问。整个作用域链本质上是一个指向变量对象的指针列表。作用域链的最前端,始终是当前正在执行代码所在环境的变量对象。
变量查找从当前执行上下文到上级执行上下文,直到全局执行上下文。
4,词法作用域
深入理解javascript作用域系列第二篇——词法作用域和动态作用域
也称为静态作用域,它的作用域在词法分析阶段就确定了。
动态作用域是在运行时根据程序的流程信息来动态确定的,而不是在写代码时静态确定的。
JavaScript使用的是词法作用域,this采用的是动态作用域。
var a = 2;
function foo() {
console.log(a); // 会输出2还是3?
}
function bar() {
var a = 3;
foo();
}
bar();
大多数时候,我们对作用域产生混乱的原因
7,DOM
1,事件委托
1,事件委托的优点
1,减少内存消耗
<ul id="list">
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
......
<li>item n</li>
</ul>
// ...... 代表中间还有未知数个 li
//如果给每个列表项都绑定一个函数,那么将消耗很多内存。
//比较好的方法就是将事件绑定到父组件上,在事件执行的时候再去匹配判断目标元素。
2,动态绑定事件
动态表单每一次增加或者去除列表项元素,这时就需要给这些变动的元素绑定事件,或者接触事件绑定。
使用事件委托可以减少这种操作,因为事件是绑定再父层的,与目标元素的增减没有关系。
2,事件传播
当事件发生在DOM元素上时,该事件并不完全发生在那个元素上。
事件传播有三个阶段:
- 捕获阶段:
事件从window开始,然后向下到每个元素,直到到达目标元素事件或event.target.
window -》document -》HTML -》 body -》 目标元素 - 目标阶段:事件已到达目标元素。
- 冒泡阶段:事件从目标元素冒泡,然后上升到每个元素,直到到达window。
3,DOM操作
1,创建新节点
createDocumentFragment() //创建一个DOM片段
createElement() //创建一个具体的元素
createTextNode() //创建一个文本节点
2,添加,移除,替换,插入
appendChild(node)
removeChild(node)
replaceChild(new,old)
insertBefore(new,old)
3,查找
getElementById();
getElementsByName();
getElementsByTagName();
getElementsByClassName();
querySelector();
querySelectorAll();
4,属性操作
getAttribute(key);
setAttribute(key, value);
hasAttribute(key);
removeAttribute(key);
其他
1,DOM插入操作优化:DocumentFragment
下面将元素插入到文档中的操作,对性能会有很大的影响。
var ul = document.getElementById("ul");
for (var i = 0; i < 20; i++) {
var li = document.createElement("li");
li.innerHTML = "index: " + i;
ul.appendChild(li);
}
通过减少页面的渲染,来提高DOM操作的效率。
DocumentFragment是没有父节点的最小的文档对象,用来存储HTML和XML片段。DocumentFragment继承自Node,所以它有Node的所有属性和方法。
因为文档片段存在于内存中,所以将子元素插入到文档片段中不会引起页面回流。
var ul = document.getElementById("ul");
var fragment = document.createDocumentFragment();
for (var i = 0; i < 20; i++) {
var li = document.createElement("li");
li.innerHTML = "index: " + i;
fragment.appendChild(li);
}
ul.appendChild(fragment);
1,创建DocumentFragment方法
- document.CreateDocumentFragment() 所有浏览器都支持
- new Fragment() 兼容性差。
3,其他概念
1,window
2,window.location
window.location.hash 使用说明
location是JavaScript里面管理地址栏的内置对象。
比如location.href就管理页面的url,用location.href=url就可以直接将页面重定向url。而location.hash则可以用来获取或设置页面的标签值。比如http://domain/#admin的location.hash="#admin"。
利用这个属性值可以做一个非常有意义的事情。
#代表网页中的一个位置,其右面的字符,就是该位置的标识符。例如:
http://www.example.com/index.html#print
就代表网页index.html的print位置,浏览器读取这个网页后,会自动将print位置滚动到网页可视区域。
为网页位置指定标识符,有两个方法。
- 1,使用锚点
<a name="print"></a> - 2, 使用id属性
<div id="print" >
1, # 的其他特性
- 1,HTTP请求不包括# #是用来知道浏览器动作的,对服务器完全无效。
- 2,改变#不触发网页重载 单单改变#后面的部分,浏览器只会滚动到相应位置,不会重新加载网页。
- 3,改变#会改变浏览器的访问历史 每一次改变#后的部分,都会在浏览器的访问历史中增加一个记录,使用后退按钮,可以回到上一个位置。
- 4,window.location.hash读取#值 hash值可读可写,读取是可以用来判断网页状态是否改变,写入时,则会在不重载网页的情况下,创造一条访问历史记录。
- 5,onhashchange事件
这是HTML5新增的事件,当#值发生改变时,就会触发这个事件。
它的使用方法有三种:
window.onhashchange = func;
<body onhashchange="func();">
window.addEventListener("hashchange", func, false);
8,表达式和运算符
1,一元运算符
1,delete
delete操作符用于删除对象的某个属性。
1,返回值
对于所有的情况都是true,除非属性是一个自身的,不可配置的属性,在这种情况下,非严格模式返回false。
严格模式下,会会返回TypeError。
2,特性
- 如果待删除的属性不存在,delete不会起任何作用,但仍会返回true。
- 如果对象的原型链上有与待删除的属性同名的属性,那么删除属性之后,对象会使用原型链上的属性,delete只会在自身的属性上起作用。
- 任何使用var声明的属性不能从全局作用域或者函数的作用域删除(任何使用var声明的变量都会被标记为不可设置的)。
这样的话(这两句话有什么因果关系呀?),delete不能删除任何在全局作用域中的函数。
在对象中的函数是可以用delete删除的。 - 任何使用let或者const声明的属性不能从它被声明的作用域删除。
- 不可设置(Non-configurable)的属性不能被删除。 这意味着像 Math,Array, Object内置对象的属性以及使用Object.defineProperty()方法设置的为不可设置的属性不可删除。
- 使用delete删除数组元素,数组的长度不受影响。
2,void
表示表达式放弃返回值
3,typeof
typeof运算符用来判断给定对象的类型。
4,+
一元+运算符将操作转换为number类型。
5. -
一元-运算符将操作转换为number类型并取反。
6, ~
位运算符
按位取反运算符。
~~ 代表双非按位取反运算符,作用类似于Math.floor(),但是速度更快。需要注意的是:对于正数,乡下取整;对于负数,向上取整;非数字取值为0;
7, !
逻辑非运算符。
返回的是布尔值。
可以应用于ES的任何值,先强制转为布尔值变量,再对值取反。
!!单纯的将操作数执行两次逻辑非,可以将任意类型的值转为相应的布尔值。
2,主要表达式
1,this
2,function
3,yield
暂停和恢复生成器函数。
4,yield*
委派给另一个生成器函数或可迭代对象。
5,await
暂停或者恢复执行异步函数,并等待promise的resolve/reject回调。
6,[]
数组初始化/字面量语法
7,{}
对象初始化/字面量语法
8,/ab+c/i
正则表达式字面量语法。
9,()
分组操作符。
3,左表达式
左边的值是赋值的目标。
1,属性访问符
分别是点号和方括号
2,new
new运算符创建了构造函数实例
3,new.target
在构造器中,new.target指向new调用的构造器
4,super
super关键字调用父类的构造器
5,...obj
展开运算符可以将一个可迭代的对象在函数调用的位置展开成为多个参数,或者在数组字面量中展开成多个数组元素。
4,自增和自减
5,算术运算符
算术运算符以二个数值作为操作数,并返回单个数值。
1,+
加法运算符。
2,-
减法运算符
6,关系运算符
关系(比较)运算符比较二个操作数并返回基于比较结果的boolean值。
1,in
用来判断对象是否拥有给定属性。
2,instanceof
3,<
小于运算符。
7,相等运算符
8,位移运算符
在二进制的基础上对数字进行移动操作。
9,二进制位运算符
1,& 按位与
此运算符需要两个数字并返回一个数字。
1,& 与 && 的区别
&&表示逻辑与,通常用于if条件判断。&&并不是单纯的返回true或者false,而是依据:
- 若第一个表达式为false,则返回第一个表达式。
- 若第一个表达式为true,则返回第二个表达式。
0 && false 0 (both are false-y, but 0 is the first)
true && false false (second one is false-y)
true && true true (both are true-y)
true && 20 20 (both are true-y)
&&还可以用于连接多个操作符,如a && b && c && d,返回值的规则和上面一样。除此之外,它还经常被作为短路逻辑使用:若前面的表达式不是truthy,则不会执行之后的表达式。
//下面的false作为默认值
const value = obj && obj.value || false
2,| 按位或
1,| 与 ||的区别
|| 表示逻辑或,会返回第一个Truthy值或者最后一个值。
可以使用 || 进行默认值赋值,可以省略很多不必要的if语句。
// before
let res;
if (a) {
res = a;
} else if (b) {
res = b;
} else if (c) {
res = c;
} else {
res = 1;
}
// after
const res = a || b || c || 1;
3,^ 按位异或运算符
对比每一个比特位,当比特位不同时返回1,否则返回0。
10,二元逻辑运算符
用于布尔值运算,返回布尔值。
用于非布尔型值时,返回非布尔值。如果要显示的将他们的返回值转换为布尔值,可以使用双重非运算符( !! )或者Boolean构造函数。
如果一个值可以转换为true,这个值就是truthy;如果这个值可以被转换为false,那么这个值就是falsy。
会被转换为false的表达式:
- null
- NaN
- 0
- 空字符串
- undefined
1,&&
2,||
3,??
空值合并运算符,如果??前面是null或者undefined,取后面的默认值。
11,条件(三元)运算符
12,赋值运算符
13,逗号操作符
允许在一个判断状态中有多个表达式去进行运算并且最后返回最后一个表达式的值。
其他
1,表达式和语句的区别
表达式是一个单纯的运算过程,总是由返回值。语句是执行某种操作,没有返回值。
表达式是可以被求值的代码,而语句是一段可执行代码。表达式本身可以作为表达式语句,也能作为赋值语句的右值或者if语句的条件等。
通常一个孤立的表达式放在代码中没有任何含义,go语言不允许无意义的表达式存在。
而语句就是一条完整的指令,可以包含关键词、运算符、变量、常量以及表达式。语句一般可以分为:声明式语句、赋值语句、执行式语句。
9,语句和声明
JavaScript应用程序是由许多语法正确的语句组成的。
1,控制流程
1,block 块语句
用于组合0个或者多个语句,该块由一对大括号界定。
2,break
3,continue
4,Empty
空语句
5,if...else
6, switch
7, throw
8, try...catch
2,声明
1,var
2,let
3,const
在JavaScript中的常量和一般语言中的常量不同,JavaScript中的常量的含义是:只能才初始化时赋值一次,之后都不允许再次赋值的变量
3, 函数和类
1,function
声明一个指定参数的函数。
2,function*
生成器函数使得迭代器更容易实现。
3,async function
使用指定的参数声明一个异步函数。
4,return
指定函数的返回值。
5,class
声明一个类。
6,箭头函数
箭头函数表达式的语法比函数表达式更简洁,并且没有自己的this,arguments, super或者new.target。箭头函数表达式更适合用于那些需要匿名函数的地方,并且不能作为构造函数使用。
引入箭头函数的作用:更简短的函数并且不绑定this。
- 当箭头函数的函数体只有一个return语句时,可以省略return关键字和方法体的花括号。
4,迭代器
1,do ... while
2,for
3,for...in
4, for...of
5, for await...of
在异步可迭代对象,类数组对象,迭代器和生成器上迭代,调用自定义迭代钩子,其中包含要为每个不同属性的值执行的语句。
6,while
5,其他
1,debugger
2, export
3, import
4, import.meta
5, label
,其他
1,继承与组合的优缺点
先说结论,组合优于继承。
1,继承的缺点
继承的优点是可以重写父类的方法,来实现对父类的拓展。
- 父类的内部细节对子类是可见的
- 子类继承父类的方法在编译时就确定了,无法在运行期间改变从父类继承的方法。
- 如果对父类的方法做了修改的话,则子类的方法必须作出相应的修改,子类和父类是高耦合的,违背了面对对象的思想。
2,组合的优点
组合就是在设计类的时候把要组合的类的对象加入到该类中作为自己的成员变量。
- 当前对象只能通过包含的对象去调用其方法,包含对象的内部细节对当前对象来说是不可见的。
- 当前对象与包含对象是一个低耦合关系,如果修改包含对象的类的代码,不需要修改当前对象类的代码。
- 当前对象可以在运行时动态绑定所包含的对象。
3,组合的缺点
- 容易产生过多的对象
- 为了能组合多个对象,必须仔细对接口进行定义。
2,es7,es8,es9,es10新特性
1,变量提升与函数提升
一个JS文件的运行分为编译阶段和执行阶段,而变量提升发生在编译阶段,当JS文件被编译时,会把变量的声明提升到代码的开头,并赋值为undefined。变量提升 不包括赋值。
函数提升和变量提升还是有区别的,函数提升会把整个函数声明提升到代码开头。
let声明的变量没有变量提升。
2,let与var的区别
在ES6之前只有函数作用域和全局作用域,没有块级作用域。
在代码块中使用const和let,在使用const和let声明变量之前,就使用变量,会报Uncaught ReferenceError错误,这个称为暂时性死区。
let声明的变量不能重复声明。
面试题
在for of循环中,var与let的表现不一样
for(var i=0;i<3;i++){
document.body.addEventListener('click', function(){
console.log(i) // 点击之后输出3 3 3
})
}
问题出现的原因:
forof循环时同步代码,而点击事件是异步事件,在点击之前,所有的for循环已经运行完毕,而var只有函数作用域,在上述代码中所有的i都引用的是同一个变量,值为3.
使用let声明的变量在块级作用域内能强制执行更新变量。
解决方法 :
问题的核心在于固定i的值。
- 使用es6的let let具有块级作用域,每次循环的块作用域i值都不一样,相当于引用了不同的变量。
- 使用自执行函数
var btn=document.getElementsByTagName('button');
for (var i = 0; i < btn.length; i++) {
(function(i){
btn[i].onclick=function(){
alert(i);
}
})(i)
}
每次循环,函数都会运行一次,声明一个变量i,i的值为当次循环传入的值。
2,工程化
1,模块化
参考文档:
0.9 不使用模块化
// util.js getFormatDate 函数里 (基础函数库)
// a-util.js aGetFormatDate函数 使用getFormatDate(a业务在util.js上二次封装)
// a.js aGetFormatDate 函数(a.js 引用a-util.js)
// util.js
function getFormatDate(date,type){
//type === 1 返回 2017-06-15 格式的日期
//type === 2 返回 2017年6月15日 格式日期
//...
}
//a-util.js
function aGetFormatDate(date){
//要求返回2017年6月15日格式
return getFormatDate(date,2)
}
// a.js
var dt = new Date()
console.log(aGetFormatDate(dt))
使用
<script src="util.js"></script>
<script src="a-util.js"></script>
<script src="a.js"></script>
// 1. 这些代码中的函数必须是全局变量,才能暴露给使用方,全局变量污染,
//因为全局变量,可能被其他人覆盖掉
//强依赖,顺序不能乱
// 2. a.js 知道要引用a-util.js,但a.js不一定知道需要依赖util.js
1, commonjs
//定义一个module.js文件
var A = function() {
console.log('我是定义的模块');
}
//导出这个模块
//1.第一种返回方式 module.exports = A;
//2.第二种返回方式 module.exports.test = A,不能写为exports = A,因为这样会切断module与exports之间的关系。
//3.第三种返回方式 exports.test = A;
exports.test = A;
//再写一个test.js文件,去调用刚才定义好的模块,这两个文件在同一个目录下
var module = require("./module"); //加载这个模块
//调用这个模块,不同的返回方式用不同的方式调用
//1.第一种调用方式 module();
//2.第二种调用方式 module.test();
//3.第三种调用方式 module.test();
module.test();
//接下来我们去执行这个文件,前提是你本地要安装node.js,不多说了,自己百度安装。
//首先打开cmd, cd到这两个文件所在的目录下,执行: node test.js
node test.js
//输出结果:我是定义的模块
common.js模块缓存
在第一次加载模块之后,后续的require都直接从缓存中加载模块,而不会再次运行模块代码。
//number.js
console.log('run number.js')
module.exports = {
num: 1
}
//main.js
let number1 = require("./number");
let number2 = require("./number");
number2.num = 2
let number3 = require("./number");
console.log(number3)
// run number.js
// { num: 2 }
加载机制
commonJS的加载机制是,模块输出的是拷贝,如果是基本类型,属于赋值;如果是引用类型,属于浅拷贝。
require加载模块时,会执行模块中的代码,然后将模块中的module.exports属性作为返回值返回,这个加载过程发生在程序的运行阶段,而在主程序运行前无法确定模块的加载顺序(依赖关系),这种加载方式称为运行时加载。由于commonjs是运行时加载,可以通过判断语句,动态的选择加载哪个模块。
let num = 10;
if (num > 2) {
var a = require("./a");
} else {
var b = require("./b");
}
也是由于这种动态加载方式,导致commonJS无法在编译时做静态优化。
循环加载
由于缓存机制的存在,commonJS的模块可以循环加载,而不会出现死循环。
//a.js
exports.a = 1;
var b = require("./b");
console.log(b, "a.js");
exports.a = 2;
//b.js
exports.b = 11;
var a = require("./a");
console.log(a, "b.js");
exports.b = 22;
//main.js
const a = require("./a");
const b = require("./b");
console.log(a, "main a");
console.log(b, "main b");
2,AMD
全称异步模块定义。
- 使用AMD需要引入require.js,会有define,require这两个全局函数。
- 使用define定义模块文件,require引用模块。
1,第一个例子
// dependence.js
define(function() {
var minus = (x, y) => x - y;
return {
minus,
}
})
模块间相互引用
// utils.js,和dependence.js在同一个文件夹底下
define(['./dependence'], function(dependence) {
var sum = (x, y) => x + y;
return {
sum,
minus: dependence.minus,
}
})
- 使用require引入模块
// index.js
require.config({
paths: {
utils: './utils',
dependence: './dependence',
}
})
require(["utils", "dependence"], function (utils, dependence) {
console.log(utils.sum(10, 20), utils.minus(20, 10), dependence.minus(30, 10));
});
// 控制台输出30,10,20
2,第二个例子
//util.js
define(function(){
var util = {
getFormatDate: function(date, type){
if(type === 1){
return '2017-06-15'
}
if(type === 2){
return '2017年6月15日'
}
}
}
return util
})
//a-util.js
define(['./util.js'],function(util){
//参数util就是util.js 返回出的对象
var aUtil = {
aGetFormatDate: function(date){
return util.getFormatDate(date,2)
}
}
return aUtil
})
//a.js
define(['./a-util.js'],function(aUtil){
//参数aUtil就是a-util.js 返回出的对象
return {
printDate: function(date){
console.log(aUtil.aGetFormatDate(date))
}
}
})
//main.js //main.js作为入口文件
require(['./a.js'],function(a){
var date = new Date()
a.printDate(date)
})
//每个异步模块都会异步加载
使用
<!--加载使用main.js-->
<script data-main='./main.js' src='./requrie.js'></script>
<!--
data-main用来嵌入自定义数据,当script标签指定data-main属性时,JS会默认的 将data-main指定的JS作为根路径。
data-main是require的use的简化方式,上面的语句也等同于下面的
-->
<script type="text/javascript" src="js/libs/requrie.js"></script>
<script>
requrie.use('/Office/js/common');
</script>
3,ES6中的module
ES6的模块化的设计思想是尽可能的静态化,使得在编译阶段就能确定模块之间的依赖关系。利用ES6的模块静态化的加载方案,就能实现摇树优化。
和commonJS相同,es6也是定义了一个文件就是一个模块。模块内部的变量都是私有的,其他模块无法访问。
- export
//1,export + 变量声明
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
//export命令规定的是对外的接口,必须与模块内部的变量建立一一对应的关系
// 报错
export 1;
// 报错,需要放到大括号中
var m = 1;
export m;
//2,export + {变量名组合}
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export { firstName, lastName, year };
//3,export default + 变量
// export default 其实是个语法糖,本质上是将后面的值赋值给default变量,但是正是由于它是输出一个default变量,所以default后面不能再跟变量声明语句。这个命令只能在模块中使用一次。
// 正确
export var a = 1;
// 正确
var a = 1;
export default a;
// 错误
export default var a = 1
//函数和类的输出写法可以是两种不同的写法。
// 第一组
export default function crc32() { // 输出
// ...
}
import crc32 from 'crc32'; // 输入
// 第二组
export function crc32() { // 输出
// ...
};
import {crc32} from 'crc32'; // 输入
//第三组
//export 和 export default可以同时存在
//num.js
export let num1 = 1
export let num2 = 2
let defaultNum = 3
export default defaultNum
//main.js
import defaultNum, {
num1,
num2
} from './num'
- import
// 1,import输入的变量是只读的
import {a} from './xxx.js'
a = {}; // Syntax Error : 'a' is read-only;
// 2,import命令具有提升效果
foo(); // correct
import { foo } from 'my_module';
//原因是因为import命令是在编译阶段执行的,在代码运行之前。
//3,import是在编译阶段执行,不能使用表达式和变量
// 报错
import { 'f' + 'oo' } from 'my_module';
// 报错
let module = 'my_module';
import { foo } from module;
// 报错
if (x === 1) {
import { foo } from 'module1';
} else {
import { foo } from 'module2';
}
//4,导入的类型
//加载指定变量接口
import {xxx, yyy} from 'my_module'
// 整体加载
import * as number from 'my_module'
// default导出模块的导入
//my_module.js
export default function add1(){
//code
}
import add2 form 'my_module'
- import()
因为export和import只能在编译阶段执行,所以export和import只能在模块的顶层,不能在代码块中。导致无法在运行中加载模块。
commonjs的运行时加载:
const path = './' + fileName;
const myModual = require(path); // require到底加载哪一个模块,只有运行时才知道。import命令做不到这一点。
为了解决这个问题,ES2020引入了import()函数,并返回一个promise对象。
用法:
setTimeout(() => {
import('./components/test/import').then( module => {
console.log(module.judge);
})
}, 3000);
加载机制
ES模块只是输出了一个对外的接口,可以将这个接口理解为一个引用,实际的值还是在模块中,而且这个引用还是一个只读引用,不论是基本数据类型还是复杂数据类型。
//obj.js
let num = 1
let list = [1,2]
export { num, list }
//main.js
import { num, list } from './obj.js'
//Error: "num" is read-only.
num = 3
//Error: "list" is read-only.
list = [3, 4]
import也会对导入的模块进行缓存,重复import同一个模块,只会执行一次。
4,commonjs VS ES6 module
- 两者输出的值不同,commonJS输出的是值的拷贝,一旦输出一个值,模块内部的变化就影响不到这个值;ESModule输出的是值的引用。
- 两者的加载时机不同,commonJS是运行时加载,ESModule时编译时加载。
- commonjs加载的是整个模块,es6可以单独加载某个模块。
- commonjs默认非严格模式,es6默认严格模式。
3,内部原理
1,面向对象编程
1,this
参考文档:
1,this的绑定规则
1,默认绑定
非严格模式下,this指向全局对象,严格模式下,this指向undefined。
var a = 1;
function foo() {
console.log(this.a);
};
function bar() {
"use strict";
console.log(this.a);
};
foo(); // 1,非严格模式下,this 指向全局对象 Window,这里相当于 Window.a
bar(); // Uncaught TypeError: Cannot read property 'a' of undefined,严格模式下,this 会绑定到 undefined,尝试从 undefined
读取属性会报错
2,隐式绑定
如果函数在调用位置上有上下文对象,this就会隐式的绑定到这个对象上。
var a = 1;
function foo() {
console.log(this.a);
};
var obj = {
a: 2,
foo: foo, // <-- foo 的调用位置
};
obj.foo(); // 2,foo 在调用位置有上下文对象 obj,this 会隐式地绑定到 obj,this.a 相当于 obj.a
隐式绑定在某些情况下会导致绑定丢失。
第一种是使用函数别名调用时:
var a = 1;
function foo() {
console.log(this.a);
};
var obj = {
a: 2,
foo: foo,
};
var bar = obj.foo;
bar(); // 1,赋值并不会改变引用本身,使用函数别名调用时,bar 虽然是
//obj.foo 的一个引用,但是实际上引用的还是 foo 函数本身,所以这里隐式绑定并没有生效, this 应用的是默认绑定
第二种时函数作为参数传递的:
function foo() {
console.log(this.a);
};
function bar(fn) {
fn(); // <-- 调用位置
};
var a = 1;
var obj = {
a: 2,
foo: foo,
};
bar(obj.foo); // 1, 参数传递也是一种隐式赋值,即使传入的是函数,这里相当于 fn = obj.foo,所以 fn 实际上引用的还是 foo 函数本身,this 应用默认绑定
3, 显示绑定
call,apply,bind通过传入参数就可以指定this的绑定值,称为显示绑定。
function foo() {
console.log(this.a);
};
var a = 1;
var obj = { a: 2 };
foo.call(obj); // 2,调用时显式地将 foo 的 this 绑定为 obj 对象,所以这里的 this.a 相当于 obj.a
foo.apply(obj); // 2,同理
通过显示绑定可以解决隐式绑定中出现的绑定丢失的问题
function foo() {
console.log(this.a);
};
var obj = {
a: 2,
foo: foo,
};
function bar(fn) {
fn.call(obj);
};
var a = 1;
bar(obj.foo); // 2,
与call,apply不同的是,bind调用后不会立即执行,而是会返回一个硬绑定对象的函数
function foo() {
console.log(this.a);
};
var obj = { a: 2 };
var a = 1;
var bar = foo.bind(obj);
bar(); // 2,bar 是通过 bind 返回后的一个硬绑定函数,其内部应用了显式绑定
将null或者undefined作为第一个参数传入call,apply,bind,调用时会被忽略,实际应用的是默认绑定规则。
4,new绑定
new的实现原理
function _new() {
let obj = new Object(); // 1. 创建一个空对象
let Con = [].shift.call(arguments); // 2. 获得构造函数
obj.__proto__ = Con.prototype; // 3. 链接到原型
let result = Con.apply(obj, arguments); // 4. 绑定 this,执行构造函数
return typeof result === 'object' ? result : obj; // 5. 返回 new 出来的对象
}
5,箭头函数的this
箭头函数的this不会应用上述规则,而是根据最外层的词法作用域来确定this。
6,绑定的优先级
this绑定的优先级: new绑定》显示绑定》隐式绑定》默认绑定。
2,原型和原型链
在JavaScript中,除了一部分内建函数,绝大多数函数都会包含一个叫做prototype的属性,指向原型对象。
再JavaScript中,每个对象都有一个__proto__属性,指向当前构造函数的原型对象。
3,对象
1,新建对象
2,给对象动态添加属性
在项目中给对象添加属性,虽然比较方便,但是其实不利于后续的维护,考虑使用typescript来做限制。
- 给引用类型动态添加属性
//创建obj对象
var obj = new Object();
//为对象添加动态属性
obj.userName = "admin";
obj.passWord = "123456";
//输出
console.log(obj);
//创建固定对象
var dt = {userName:"admin",password:"123456"};
console.log(dt);
- 给基本类型添加属性 虽然给基本类型添加属性,不会报错;但是使用属性的时候会报undefined。
其他
1,垃圾回收
1,内存管理
栈内存中的基本类型有操作系统管理,而堆内存中的引用类型,由于经常变化,大小不固定,需要通过JavaScript引擎通过垃圾回收来管理。
2,垃圾回收方法
1,新生代
Scavenge算法
2,老年代
标记清除,标记整理
3,内存泄漏的原因
- 过多的缓存未释放
- 闭包未释放
- 定时器未清除
- 全局变量太多
2,变量提升
变量提升被认为是,JavaScript中执行上下文(特别是创建和执行阶段)工作方式的一种认识。
变量可以在声明之前进行初始化和使用。
函数和变量相比,会被优先提升,这意味着函数会被提升到更靠前的位置。
4,其他概念
编译型语言与解释型语言
1,编译型语言
需要通过编译器将源代码编译为机器码,之后才能执行的语言。一般需要经过编译、链接这两个步骤。编译是将源代码编译为机器码,链接是将各个模块的机器码和依赖库串联起来生成可执行文件。
优点:运行时不需要编译,执行效率高;可以不依赖语言环境独立运行。
缺点:修改之后需要重新编译,耗时比较长;编译的时候是根据所处的操作系统环境编译得到的执行文件,不同的操作系统间不能移植。
代表语言:C, C++
2,解释型语言
不需要编译,在运行时逐行翻译。
优点:只要安装了解释器,在任何环境中都可以运行;可以快速部署。
缺点:性能不如编译型语言。
代表语言:JavaScript,python。
3,混合型语言
半编译型语言,编译的时候,不是直接编译为机器码而是编译为字节码。由语言平台来运行字节码。例如:Java
动态语言和静态语言
动态语言和静态语言最大的不同,就是类和函数的定义,不是编译时决定的,而是运行时动态创建的。
1,动态语言
运行时代码可以根据某些条件改变自身结构。
主要的动态语言:JavaScript、python
2,静态语言
与动态语言相对应的,运行时结构不可变的就是静态语言。
例如:Java、C、C++
动态类型语言和静态类型语言
1,动态类型语言
运行期间才去做类型检查。动态类型语言的数据类型不是在编译期间决定的,而把类型绑定延迟到了运行阶段。
例如:python、JavaScript
2,静态类型语言
静态类型语言的数据类型是在编译期间确定的,编写代码的时候要明确变量的数据类型。
例如:Java、C、C++
强类型语言和弱类型语言
1,强类型语言
一旦一个变量被指定为某个类型,如果不经过强制类型转换,就永远是这个类型。例如:不能把一个整形变量当做一个字符串来处理。
主要语言:Java,c#,python
2,弱类型语言
数据类型可以被忽略,一个变量可以赋值为不同类型的值。
主要语言:JavaScript,C, C++
3,注意
一个语言是不是动态语言和是不是强类型语言没有必然联系。python是动态类型语言,强类型语言;JavaScript是动态类型语言,弱类型语言;Java是静态类型语言,强类型语言。