ailingangel / front-end-knowledge-note Goto Github PK
View Code? Open in Web Editor NEW前端知识学习笔记
前端知识学习笔记
var str = `
aaaaa
<%=a%>
hhhhh
<% for(var i=0;i<list.length;i++){ %>
<%=JSON.stringify(list[i])%>
<% }%>
bbbbb
`
var obj = {
a: 'aaaaaaaaaa',
list: [
{ item: '1' },
{ item: '2' },
{ item: '3' },
{ item: '4' },
{ item: '5' }
]
}
var s = tmpl(str, obj)
console.log(s)
// aaaaa aaaaaaaaaa {"item":"1"} {"item":"2"} {"item":"3"} {"item":"4"} {"item":"5"} bbbbb
function tmpl(str, obj) {
let func = "let ans; \nwith(data){\n"
func += "ans = `";
str = str.replace(/<%=(.*)%>/g, (match, p) => {
return "${" + p + "}";
})
str = str.replace(/<%(.*)%>/g, (match, p) => {
return "`\n" + p + "\nans+=`"
})
func += str + "`\n}\nreturn ans;"
let fn = new Function("data", func);
return fn(obj);
}
name | padding | border | content | vertical-scroll-bar width |
---|---|---|---|---|
scrollWidth | ✔️ | ❌ | ✔️ | ❌ |
offsetWidth | ✔️ | ✔️ | ✔️ | ✔️ |
clientWidth | ✔️ | ❌ | ✔️ | ❌ |
上面的三个属性与box-sizing属性的设置无关。即使是在border-box下,clientWidth拿到的还是padding和内容相加得到的值。
单纯使用scrollWidth>clientWidth不能准确得到的溢出的结果,因为scrollWIdth和clientWIdth拿到的都是整数的结果,有的文本实际需要的长度,比如为20.3px,但是通过scrollWidth拿到的为20px,可能与clientWidth20px相等。其实文本是溢出的。
可以通过range来准确获取实际需要的宽度,然后与最终渲染的宽度做对比
const range = document.createRange();
range.setStart(cellChild, 0);
range.setEnd(cellChild, cellChild.childNodes.length);
const rangeWidth = range.getBoundingClientRect().width;
const padding = (parseInt(getStyle(cellChild, 'paddingLeft'), 10) || 0) +
(parseInt(getStyle(cellChild, 'paddingRight'), 10) || 0);
(rangeWidth + padding > cellChild.clientWidth
function findItem(arr, pos) {
let parrentArray = arr;
let i = 0;
while (i < pos.length - 1) {
parrentArray = parrentArray[pos[i]];
i++;
}
// 当前数组已经访问完了
if (pos[i] === parrentArray.length) {
pos.splice(pos.length - 1, 1);
if (pos.length === 0) {
return {
done: true,
value: undefined
}
} else {
pos[pos.length - 1]++;
return findItem(arr, pos);
}
}
let item = parrentArray[pos[i]];
if (Array.isArray(item)) {
if (item.length > 0) {
pos.push(0);
return findItem(arr, pos);
} else {
pos[pos.length - 1]++;
return findItem(arr, pos);
}
} else {
pos[pos.length - 1]++;
return {
value: item,
done: false,
};
}
}
function createFlatArrayIterator(arr) {
let pos = [0];
return {
next() {
return findItem(arr, pos);
}
}
}
let iterator = createFlatArrayIterator([
[],
['a'], 'b', 'c', ['d', ['e', 'f', 'g'], 'h']
]);
let flatedNumber = [];
let nextValue = iterator.next();
while (!nextValue.done) {
flatedNumber.push(nextValue.value);
nextValue = iterator.next();
}
console.log('ans=>', flatedNumber)
var ifm = document.createElement('iframe')
document.body.appendChild(ifm)
var img = document.createElement('img')
img.src = 'https://web.500px.com/static/media/[email protected]'
ifm.contentWindow.document.body.appendChild(img)
setTimeout(function () {
console.log('stop')
ifm.contentWindow.stop()
}, 100)
优点:
缺点:
new Promise(function(resolve, reject) {
console.log('start excutor');
resolve(100);
console.log('after resolve');
}).then(function(data) {
console.log('success', data);
}, function(error) {
console.log('error', error);
});
console.log('start before then');
// start excutor
// after resolve
// start before then
// success 100
new Promise(function(resolve, reject) {
setTimeout(function() {
console.log('before resolve');
resolve(4, 3); // 首次决议,then的resolved回调接收结果
console.log('after resolve'); // 决议不影响后面的代码执行
reject(5); // 已经决议过不会被reject的回调接受
console.log('after reject');//依然会执行
return 'test'; // return的结果并不会被then的回调函数接收
console.log('after return'); // 不会执行
}, 500);
}).then(function(data) {
console.log(data);// 只会打印4,传递给resolve的第二个参数3被忽略
}, function(error) {
console.log(error);
});
// before resolve
// after resolve
// after reject
// 4
let p1 = new Promise(function(resolve, reject) {
resolve(100);
});
let p2 = p1.then(function(data) {
return data;
});
console.log(p1 === p2)// false
new Promise(function(resolve, reject) {
resolve(100);
}).then(function(data) {
return data;
}).then(function(data) {
console.log('success', data); // 走到这里, success 100
}, function(error) {
console.log('error', error);
});
new Promise(function(resolve, reject) {
resolve(100);
}).then(function(data) {
// return data;
}).then(function(data) {
console.log('success', data); // 走到这里, success undefined
}, function(error) {
console.log('error', error);
});
new Promise(function(resolve, reject) {
resolve(100);
}).then(function(data) {
return new Promise(function(resolve, reject) {
resolve(data * 2);
});
}).then(function(data) {
console.log('success', data);// 走到这里 success 200
}, function(error) {
console.log('error', error);
});
new Promise(function(resolve, reject) {
resolve(100);
}).then(function(data) {
return new Promise(function(resolve, reject) {
reject(data * 2);
});
}).then(function(data) {
console.log('success', data);
}, function(error) {
console.log('error', error); // 走到这里 error 200
});
new Promise(function(resolve, reject) {
reject(100);
}).then(function(data) {
throw new Promise(function(resolve, reject) {
resolve(data * 2);
});
}, function(error) {
console.log('error', error) // 走到这里来
return error * 2; // return 返回这个基本值表示走下一个then的成功回调
}).then(function(data) {
console.log('success', data); // 走到这里 success 200
}, function(error) {
console.log('error', error);
});
new Promise(function(resolve, reject) {
resolve(100);
}).then(function(data) {
throw 2
}).then(function(data) {
console.log('success', data);
}, function(error) {
console.log('error', error); // 走到这里 error 2
});
new Promise(function(resolve, reject) {
resolve(100);
}).then(function(data) {
throw new Promise(function(resolve, reject) {
resolve(data * 2);
});
}).then(function(data) {
console.log('success', data);
}, function(error) {
console.log('error', error); // 走到这里 error Promise {200}; 这里error就是被上层throw的Promise对象
});
new Promise(function(resolve, reject) {
throw TypeError('type is not support')
}).then(function(data) {
console.log(data);
}, function(error) {
console.log('error:', error)// 走到这里 error, TypeError: type is not support
}).then(function(data) {
console.log('here', data) // 走到这里 here undefined, 因为上一个rejected回调没有拒绝,默认走resolved回调; 没有显示return一个值,采用默认的undefined
})
new Promise(function(resolve, reject) {
reject('type is not support')
}).catch(function(data) {
console.log('catch', data) // 走到这里 catch type is not support
})
new Promise(function(resolve, reject) {
reject('type is not support')
}).then(function(data) {
console.log('success', data);
}, function(error) {
console.log('error', error); // 走到这里不走catch; error type is not support
}).catch(function(data) {
console.log('catch', data);
})
new Promise(function(resolve, reject) {
reject('type is not support')
}).catch(function(data) {
console.log('catch', data); // 走到这里 catch type is not support
}).then(function(data) {
console.log('success', data); // 走到这了 success undefined 前一个catch默认使用return undefined
}, function(error) {
console.log('error', error);
})
Promise.resolve(value)是下列代码的简写
new Promise(function(resolve){
resolve(value)
});
Promise.resolve({ a: 1 }).then(function(data) {
console.log(data)
});
// {a:1}
var thenable = {
then: function(resolve) {
resolve("Resolving");
throw new TypeError("Throwing");
}
};
var p3 = Promise.resolve(thenable);
p3.then(function(v) {
console.log(v); // 输出"Resolving"
}, function(e) {
// 不会被调用
});
var p1 = Promise.resolve('p1');
var p2 = Promise.resolve(p1);
var p = Promise.resolve(p2);
console.log(p === p1); // true
p.then(function(data) {
console.log(data); // 'p1'
})
var p1 = Promise.reject('p1');
var p2 = Promise.resolve(p1);
var p = Promise.resolve(p2);
p.then(function(data) {
console.log(data);
}, function(error) {
console.log('error', error);// 走到这里 error p1
})
new Promise(function(resolve, reject) {
reject(reason);
});
var p1 = Promise.reject('p1');
var p2 = Promise.reject(p1);
var p = Promise.resolve(p2);
console.log(p === p1); // false
console.log(p === p2); // true
console.log(p1 === p2);//false
p.then(function(data) {
console.log(data);
}, function(error) {
console.log('error', error); // 走到这里 error 是Promise p1 不是字符串"p1"
})
下面列出了Promise.all的源代码实现:
Promise.all = function(iterable) {
return new Promise(function(resolve, reject) {
if (typeof iterable !== 'object' && typeof iterable[Symbol.iterator] !== 'function') {
reject('type error');
} else if (iterable.length === 0) {
resolve([]);
} else {
let ans = [];
let len = 0;
for (let i = 0; i < iterable.length; i++) { // 注意使用let来声明块作用域,闭包!!!
Promise.resolve(iterable[i]).then(function(data) {
len++; // 使用len来追踪获取到了多少个结果; 不能使用ans.length来判断 因为ans[i]的方式会创建稀疏数组导致ans.length不能用来决议的promise的个数
ans[i] = data; // 必须使用下标复制 因为不知道谁先到 如果使用ans.push(data)不能保证结果的顺序
if (len === iterable.length) {
resolve(ans);
}
}).catch(function(error) {
reject(error);
});
}
}
});
}
var p1 = Promise.resolve(3);
var p2 = 1337;
var p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 300, 'foo');
});
var p4 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'bar');
});
Promise.all([p1, p2, p3, p4]).then(values => {
console.log(values); // [3, 1337, "foo", "bar"]
});
下面是Promise.race的实现
Promise.race = function(iterable) {
return new Promise(function(resolve, reject) {
if (typeof iterable !== 'object' && typeof iterable[Symbol.iterator] !== 'function') {
reject('type error');
} else if (iterable.length !== 0) {
for (let i = 0; i < iterable.length; i++) {
Promise.resolve(iterable[i]).then(function(data) {
resolve(data);
}).catch(function(error) {
reject(error);
});
}
}
});
}
Promise.prototype.finally = function(cb) {
return this.then(function(data) {
return Promise.resolve(cb()).then(function() {
return data;
});
}, function(error) {
return Promise.resolve(cb()).then(function() {
throw error;
});
});
};
var会进行变量提升,let和const不会进行提升
因为var会进行变量提升,所以可以在声明之前访问,不会形成暂存死区。let 和const 不会进行变量提升,在声明之前不能使用,形成暂存死区
var可以进行重复声明,但是let和const不能进行重复声明
var不会形成块作用域,let和const可以形成块作用域
var和let声明的变量可以重新赋值,const不可以。如果const 声明的变量存储的是引用地址, 是可以修改这个引用对应的对象的值的,但是这个变量不能被赋予其他值
情况1: 假设有一个文件a.js里面的代码如下
const random = Math.random();
export const loginUrl = `http://test.com?number=${random}`;
在b.js中使用loginUrl
import {loginUrl} from './a.js';
function test(){
const test = Math.random();
console.log(loginUrl);
}
test();
test();
两次test调用的结果是?
情况2: 如果将a.js文件中改成
const random = Math.random();
export const loginUrl = ()=>`http://test.com?number=${random}`;
然后将b.js文件的loginUrl改成函数调用
import {loginUrl} from './a.js';
function test(){
const test = Math.random();
console.log(loginUrl());
}
test();
test();
两次运行的结果?
情况3: 在情况2的基础上修改a.js代码
export const loginUrl = ()=>{
const random = Math.random();
return `http://test.com?number=${random}`;
}
+'10' // 10
'' + 1 -> '1'
!![] // true
// new Number是将一个值封装成数字对象类型
var test = new Number(1)
typeof test === 'object'
// Number()是将一个值强制类型转换成数字
typeof Number('1') === 'number'
a + b
'1' + 1 // '11'
true + 1 // 2
!![] + 1 // 2
{} + 1 // '[object Object]1'
var obj = {
valueOf(){
return 1
}
}
console.log(obj + 1) // 2
因为这几个操作符都是针对数字的运算,因此会将变量强制类型转换成数字
true // 1
false // 1
'' // 0
null // 0
isNaN(undefined) // true
// 引用类型就采用toPrimitive操作, 引用类型的valueOf方法默认返回自己,除非自己显示定义, 不能返回基本类型就继续调用toString方法, 数组的toString默认返回逗号分隔的字符串, 对象默认返回'[object Object]'
// 数组
[] -> '' -> 0
{} -> [object Object] -> NaN
== 和===最大的区别在于==允许强制类型转换,而===不允许进行强制类型转换
转换规则
console.log(2.0 == “2” == new Boolean(true) == “1”) // true
// 2.0 == '2' 返回true
// true == new Boolean(true) true变成数字类型1,然后对new Boolean(true)进行拆封, 返回 true
// true == '1' 首先true转变成数字类型1,然后字符串'1'转变成数字类型1 所以最终返回true
![] == [] //true
// ![]是布尔值返回false
// fasle == [] 将布尔值转变成数字类型0
// 0 == [] 将[]执行toPrimitive操作返回字符串''
// 0 == '' 字符串''转变成数字类型 0 所以最终结果返回true
!{} == {} // false
// !{}转变成boolean类型 false
// false == {} false转变成数字类型0
// 0 == {} 对{}执行toPrimitive操作返回'[object Object]'
// 0 == '[object Object]' 字符串转变成数字类型NaN
// NaN和任何值都不相等包括他自己
注意事项
抽象关系比较会进行字符串比较,如果出现非字符串的值就转换成数字再进行比较
{a:1} < {b:1} // false
[0, 1, 2] < [1, 0] // true
[0] < 2 // true
JS中的基本数据类型氛围基本类型和引用类型
基本类型: null undefined number string boolean symbol(es6新增)
引用类型: object
检测数据的基本类型有如下几种方法
1. typeof 操作符
缺点: 不能区分内置对象的类型(function 除外)以及自定义的类型
// 不能区分 null 以及内置对象类型, function 除外
typeof null === 'object'
typeof function(){} === 'function'
typeof [] === 'object'
typeof new Boolean(true) === 'object'
// 不能区分自定义数据类型
function myValue() {}
var test = new myValue();
typeof test === 'object';
2. Object.prototype.toString 方法
可以区分内置对象的类型,但是不能区分自定义对象的类型; 返回类属性
Object.prototype.toString.call(null) // '[object Null]'
Object.prototype.toString.call(undefined) // '[object Undefined]'
Object.prototype.toString.call(1)// '[object Number]'
// 可以区分内置对象的类型
Object.prototype.toString.call([])// '[object Array]'
// 不能区分自定义类型
function myVal() {}
var test = new myVal();
Object.prototype.toString.call(test) // '[object Object]'
3..instanceof 操作符
可以区分自定义类型,内置对象类型,但是无法检测基本数据类型;因为instanceof操作检测是一个对象的原型是否出现在另一个对象的原型链上,因此要求这两个对象必须属于同一个全局环境,也就是说无法检测一个窗口内的对象是否是另一个窗口(iframe)中类型的对象
// 无法检测基本数据类型
null instanceof Object // false
1 instanceof Number // false
// 可以检测内置对象的类型
[] instanceof Array // true
// 可以检测自定义数据类型
function myVal() {}
var test = new myVal();
test instanceof myVal // true
// 必须要在同一个环境中
a instanceof A // 构造函数A必须和a处于同一个执行环境中
function A(){}
function test(){
function A(){}
var a = new window.A();
console.log(a instanceof A); // false
console.log(a instanceof window.A); // true
}
test();
4.利用constructor
只能用来判断引用类型,无法判断基础数据类型; 而且对象的原型链可能被整个替换,拿到的constructor 不一定准确
[].constructor === Array // true
function myVal() {}
var test = new myVal();
myVal.prototype = {}
test.constructor === myVal // true
var test2 = new MyVal()
test2.constructor === myVal // false, test2.constructor 是Object, 继承自新的原型对象的constructor属性
myVal.prototype.constructor = myVal; // 修正constructor的指向
var test3 = new myVal()
test3.constructor === myVal;
JS中函数的this是动态绑定的,并不采用词法作用域规则。this和arguments都不会沿着作用域向上查找
function test(){
alert(this); // [object Window]
}
test();
function test(){
alert(this.a); // test
}
var obj = {
a:'test',
fn: test
}
obj.fn();
function test(){
alert(this.a); // test
}
var obj1 = {
a:'test',
fn: test
}
var obj2 = {
a: 'obj2',
obj1: obj1
}
obj2.obj1.fn() //以obj1为准
绑定丢失: 在将函数作为参数传递给另一个函数或者将函数赋值给另一个变量的时候就容易导致绑定丢失的问题
function test(){
alert(this); // [object Window]
}
var obj1 = {
a:'test',
fn: test
}
var foo = obj1.fn;
foo();
call的实现
Function.prototype.call = function(context) {
if (!context) {
context = typeof window === 'undefined' ? global : window;
}
context.fn = this;
let args = [...arguments].slice(1);
let res = context.fn(...args);
delete context.fn;
return res;
}
apply的实现
Function.prototype.apply = function(context, args = []) {
if (!context) {
context = typeof window === 'undefined' ? global : window;
}
context.fn = this;
let res = context.fn(...args);
delete context.fn;
return res;
}
```js
bind实现
```js
Function.prototype.bind = function(context) {
if (!context) {
context = typeof window === 'undefined' ? global : window;
}
let oldArgs = Array.prototype.slice(arguments, 1);
let self = this;
return function() {
let args = oldArgs.concat([...arguments]);
let res = self.apply(context, args);
return res;
}
}
function newFn(fn) {
return function() {
// let obj = {};
// obj.__proto__ = fn.prototype;
let obj = Object.create(fn.prototype);
let res = fn.apply(obj, arguments);
//注意判断条件
if ((typeof res === 'object' && res !== null) || typeof res === 'function') {
return res;
}
return obj;
}
}
对象的属性分为数据属性和访问器属性
数据属性具有的特性有
configurable: 是否可以配置,典型的能否应用delete操作符删除;改成false的操作是不可逆的
enumerable: 是否可以枚举,能否通过for...in循环便利到
writable: 是否可写,是否可以修改属性对应的值
value: 属性对应的值
访问器属性具有的特性有
set: 属性的set函数,只要具有set函数就说明数据可写
get: 属性是否可被访问
configurable:属性可以配置吗
enumerable: 属性可以枚举吗
备注: 通过字面量创建的对象,默认是可写的课枚举的可配置的。如果是通过Object.defineProperty那么属性默认是不可配置不可枚举的;
function deepClone(obj) {
if (typeof obj !== 'object') return obj;
if (obj === null) return obj;
let copy = new obj.constructor();
for (let key in obj) {
if (typeof obj[key] === 'object') {
copy[key] = deepClone(obj[key]);
} else {
copy[key] = obj[key];
}
}
return copy;
}
将数组扁平化去并除其中重复部分数据,得到一个升序且不重复的数组
例 [
[1, 2, 2],
[3, 4, 5, 5],
[6, 7, 8, 9, [11, 12, [12, 13, [14]]]],
10
] => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
function flatAndSortArray(arr) {
const flatedArr = flatArray(arr);
flatedArr.sort((a,b)=>a-b);
let i = 0;
let j = 0;
while(j < flatedArr.length) {
if(flatedArr[i] !== flatedArr[j]){
flatedArr[++i] = flatedArr[j];
}
j++;
}
flatedArr.splice(i+1);
return flatedArr;
}
function flatArray(arr) {
return arr.reduce((acc, item)=> {
if(Array.isArray(item)){
return acc.concat(flatArray(item))
}
return acc.concat(item);
},[]);
}
function styleHyphenFormat(propertyName) {
function upperToHyphenLower(match) {
return '-' + match.toLowerCase();
}
return propertyName.replace(/[A-Z]/g, upperToHyphenLower);
}
console.log(styleHyphenFormat('borderTop'))
只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数
function curry(fn) {
let oldArgs = Array.prototype.slice.call(arguments, 1);
return function() {
let newArgs = Array.prototype.slice.call(arguments);
let args = newArgs.concat(oldArgs);
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return curry.call(this, fn, ...args);
}
}
}
function sum(a, b, c) {
return a + b + c;
}
console.log(curry(sum)(1, 2, 3))
console.log(curry(sum, 1)(2, 3))
console.log(curry(sum, 1, 2)(3))
// 第一题:
var foo = 'hello';
(function() {
var foo = foo || 'word'; // foo会进行提升覆盖了全局作用域中的foo
console.log(foo); // word
})();
// 第二题:
console.log(parseInt(' 01')) //1
console.log(parseInt('0918abc')) // 918
// 第三题:
var ninjia = function myNinJia() {
console.log(ninjia === myNinJia); // 在函数表达式中如果给函数了一个名字,那么这个名字只能在函数内部作用域中访问
}
ninjia() // true;
myNinJia() // reference error]
// 第四题:
var f = function() {
var a = b = 1; // b没有进行生命,泄漏到了全局作用域中
}
f();
console.log(b); // 1
console.log(a); // a is not defined , reference error
// 第五题:
var f = function() {
var a = b = 1;
}
setTimeout(f, 0);
console.log(b); // reference error b is not defined
// 第六题:
var a, b = 0;
fn = function() {
var a = b = 1;
}
fn();
console.log(a) // undefined;
console.log(b) // 1
// 第七题: 循环执行完之后tc最后的值为最后一个定时器,因此setTimeout的回调中取消的是最后一个定时器,前面的定时器不受影响
function f() {
for (var i = 0; i < 4; i++) {
var tc = setTimeout(function(i) {
console.log(i);
clearTimeout(tc);
}, 10, i);
}
}
f(); // 0, 1, 2
// 第八题: tc作为参数传给setInterval回调,tc是上一次的定时器, 最后一个定时器无法被取消
function fn() {
for (var i = 0; i < 4; i++) {
var tc = setInterval(function(i, tc) {
console.log(i);
clearInterval(tc);
}, 10, i, tc);
}
}
fn(); // 0, 1, 2, 3,3,3,3,3
// 第九题:
function foo(a) {
var a; //重复声明被忽略
return a; // hello
}
console.log(foo('hello'));
// 第十题:
function foo(a) {
var a = 'bye'; //赋值被覆盖
return a; // bye
}
console.log(foo('hello'));
// 第十一题: 立即函数表达式中if中的name因为用var进行了声明,最终被提升了
var name = 'word';
(function() {
if (typeof name === 'undefined') {
var name = 'Jack'; //提升
console.log('Goodbye' + name);
} else {
console.log('hello' + name);
}
})(); // 'Goodbye Jack';
// 第十二题:
var number = 5;
var obj = {
number: 3,
fn1: (function() {
var number;
this.number *= 2;
number = number * 2;
number = 3;
return function() {
var num = this.number;
this.number *= 2;
console.log(num);
number *= 3;
console.log(number);
}
})()
}
var fn1 = obj.fn1;
fn1.call(null);
obj.fn1();
console.log(number);
// 10 9 3 27 20
// 第十三题: 数字的每一个值是一个基本类型的变量传递给forEach的回调函数中,使用的是值传递,不会影响数组本身的元素
let arry = [1, 2, 3, 4];
arry.forEach(item => {
item *= 10;
});
console.log(arry); //[1, 2, 3, 4]
// 第十四题:
function countFunc() {
let funcs = [];
for (var i = 0; i < 10; i++) {
funcs[i] = function() {
return i;
}
}
return funcs;
}
var funcs = countFunc();
console.log(funcs[5]()) // 10 多个闭包共享作用域
高阶函数: 函数作为值进行传递或者作为值被返回
function checkType(type) {
return function(x) {
return Object.prototype.toString.call(x) === `[object ${type}]`
}
}
var isString = checkType('String');
alert(isString(1))
commonJs:
es6:
因此,相比于commonJs, es6有一下几点优势
commonJs: 导出的是一份值的拷贝
es6 Module: 导出的是值的动态映射,并且这个映射是只读的
// calculator.js
var count = 0;
module.exports = {
count: count,
add: function(a, b) {
count++;
return a + b;
}
}
// index.js
var count = require('./calculator.js').count;
var add = require('./calculator.js').add;
console.log('initial count=>', count);
add(2, 3);
console.log('after add=>',count);
count+=2;
console.log('add count=>', count);
let count = 0;
const add = function(a, b) {
count++;
return a + b;
}
export {count, add};
import {count, add} from './calculator';
// eslint-disable-next-line no-console
console.log('initial count=>', count);
add(2, 3);
console.log('after add=>', count);
count +=1;
api 参看这里
class EventEmitter {
constructor(){
// 存每一个event对应的cb, 结构如下
// connection -> {function: function(){}, once: true}
this.eventCbMap = new Map();
}
on(event, cb){
this.pushCbIntoQueue(event, cb, false);
}
off(event, cb){
const queue = this.eventCbMap.get(event);
if(queue) {
const index = queue.findIndex(item=>item.cb === cb)
queue.splice(index, 1);
}
}
once(event, cb){
this.pushCbIntoQueue(event, cb, true);
}
trigger(event, ...args){
const queue = this.eventCbMap.get(event);
if(queue && queue.length > 0) {
let i = 0;
while(i < queue.length) {
const {cb, once} = queue[i];
cb(...args);
if(once) {
// 将once的侦听移除;
queue.splice(i, 1);
} else {
i++;
}
}
}
}
pushCbIntoQueue(event, cb, isOnce=false) {
const queue = this.eventCbMap.get(event);
if(queue) {
queue.push({cb, once: isOnce});
} else {
this.eventCbMap.set(event, [{cb, once: isOnce}]);
}
}
}
const event = new EventEmitter();
let cb1 = function(...args) {
console.log('cb1', args);
};
let cb2 = function(...args) {
console.log('cb2', args);
};
let cb3 = function(...args) {
console.log('cb3', args);
};
let cb4 = function(...args) {
console.log('cb4', args);
};
event.on('event-one', cb1);
event.on('event-one', cb2);
event.once('event-one', cb3);
event.on('event-two', cb4)
console.log('-------')
event.trigger('event-one', 'a', 'b');
event.trigger('event-two', 'a', 'b');
console.log('-------')
event.trigger('event-one', 'a', 'b');
event.trigger('event-two', 'a', 'b');
event.off('event-one', cb1);
console.log('-------')
event.trigger('event-one', 'a', 'b');
event.trigger('event-two', 'a', 'b');
原型
原型链
继承
只有在查找某个对象的属性时才会体现出JS的继承特性, 查找规则如下先从对象自身开始查找,如果有就返回这个属性的值,如果没有就查找原型对象,直到原型对象为null为止;对象自身的同名属性可以屏蔽原型链上的同名属性
函数可以通过new 操作符来调用,该调用叫做函数的构造调用,函数就叫做构造器(却别与其他语言的类, JS中不存在类);当函数通过new 来调用时会发生以下的操作:
每个函数都有一个prototype属性,当函数通过new 来调用时新创建的对象的[[prototype]]会自动指向这个函数的prototype属性;这个原型对象自带一个constructor属性指向这个构造器
// new操作符的原生js实现
function _new(fn) {
return function() {
let target = Object.create(fn.prototype);
let res = fn.apply(target, arguments);
if ((typeof res === 'object' && res) || typeof res === 'function') {
return res;
}
return target;
}
}
function SuperType(name) {
this.name = name;
}
SuperType.prototype.getName = function() {
return this.name;
};
function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
}
// 使用Object.create来创建一个以SuperType.prototype为原型对象的对象
SubType.prototype = Object.create(SuperType.prototype);
SubType.prototype.getAge = function() {
return this.age;
}
SubType.prototype.constructor = SubType; // 修正constructor的指向
let test = new SubType('a1', 12);
console.log(test.getAge()); // 12
console.log(test.getName()); // a1
注意上述代码中SubType继承SuperType的时候使用的是Object.create(SuperType.prototype)新创建了一个以SuperType.prototype为原型对象的对象,并没有使用new SuperType()的方式,是因为SuperType构造器可能导致某些副作用或者里面新创建的属性并不期望被放在原型对象上被其他对象继承,而是希望维护单独的一份数据,所以在SubType的构造器中通过调用SuperType.call(this, name)的方式保证SubType的实例对象拥有一份独立的数据而不是共享的数据
instanceof操作符用来判断一个函数的原型对象是否出现在另一个对象的原型链中,这个操作符比较的是对象和构造器之间的关系
function intanceof(a, A) {
let proto = A.prototype;
let _proto_ = a.__proto__;
while (_proto_) {
if (_proto_ === proto) {
return true;
}
_proto_ = _proto_.__proto__;
}
return false;
}
isPrototypeOf 用来判断一个对象是否出现在另一个对象的原型链上
function isPrototypeOf(a, b) {
var _proto = b.__proto__;
while (_proto) {
if (_proto === a) {
return true;
}
_proto = _proto.__proto__;
}
return false;
}
let a = {};
let b = Object.create(a);
console.log(isPrototypeOf(a, b)) // true
获取某个对象的原型对象
let a = {};
let b = Object.create(a);
console.log(isPrototypeOf(a, b)) // true
function getPrototypeOf(obj) {
if ('__proto__' in obj) {
return obj.__proto__;
}
return obj.constructor.prototype;
}
console.log(getPrototypeOf(b)); // {}
创建一个以指定对象为原型对象的对象
function objectCreate(obj) {
function F() {}
F.prototype = obj;
return new F();
}
let c = { a: 1 };
let d = objectCreate(c);
console.log(d); // {}
console.log(c.isPrototypeOf(d)) //true
function MyClass(a, b) {
if(!(this instanceof MyClass)) {
let obj = Object.create(MyClass.prototype);
return MyClass.apply(obj, arguments);
}
this.a = a;
this.b = b;
return this;
}
let foo = new MyClass('a0', 'b0');
let bar = MyClass('a1', 'b1');
console.log(foo, bar);
console.log(foo instanceof MyClass, bar instanceof MyClass);
<script src="../your_file.js" defer></script>
<script src="../your_file.js" async></script>
let script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'your_file.js';
// 只有添加到html文件中才会开始下载
document.body.append(script);
var xhr = new XMLHttpRequest();
xhr.open("get", "your_file.js", true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
let script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'your_file.js';
script.text = xhr.responseText;
// 只有添加到html文件中才会开始下载
document.body.append(script);
}
}
}
xhr.send(null);
如果一个事件连续触发,那么只在delay间隔内回调函数顶多调用一次, 函数节流可以减少连续事件触发时回调函数的执行次数
应用场景:
function throttle(fn, delay) {
let pre = 0;
return function() {
let now = Date.now();
if (now - pre >= delay) {
fn.apply(this, arguments);
pre = now;
}
}
}
function throttle(fn, delay) {
let timer = null;
return function() {
let args = arguments;
let context = this;
if (!timer) {
fn.apply(context, args);
timer = setTimeout(function() {
timer = null;
}, delay);
}
}
}
和函数节流一样,函数防抖也是用来控制函数执行的次数的;不一样的是在连续事件触发的过程中,回调函数要么立即执行在之后不执行,只有在事件停止之后的delay时间后才可以再次执行。 要么在事件停止之后的delay时间之后再执行,开始不执行。
应用场景:
function debounce(fn, delay) {
let timer = null;
return function() {
clearTimeout(timer);
let args = arguments;
timer = setTimeout(()=>{
fn.apply(this, args);
}, delay);
}
}
var count = 1;
document.body.onresize = debounce(function(){
console.log(count++);
}, 5000);
function debounce(fn, delay) {
let timer = null;
return function() {
clearTimeout(timer);
if(!timer) {
fn.apply(this, arguments);
}
timer = setTimeout(function(){
timer = null;
}, delay);
}
}
var count = 1;
document.body.onresize = debounce(function(){
console.log(count++);
}, 1000);
function debounce(fn, delay, immediate) {
let timer = null;
return function() {
clearTimeout(timer);
let context = this;
let args = arguments;
if (!timer && immediate) {
fn.apply(context, args);
}
timer = setTimeout(function() {
timer = null;
if (!immediate) {
fn.apply(context, args);
}
}, delay);
}
}
ASCII和unicode都是字符集,ASCII用8位二进制(开头第一个位置为0)来标识英文字母和常用的字符,这种标识方式对英文字符是够用的,但对其他的文字比如法语,中文,韩文等不够用。于是128之后的数字(将八位的第一位设成1)各个国家用来标识自己的字符,这样会导致同样的数字标识的字符并不一样,就出现了Unicode。
Unicode用数字对应每个字符,它本身只规定了每个字符对应的数字是多少,但不规定这些字符如何存储。
utf8和utf-16就是用来将这些字符对应的数字转换成二进制存储在计算机上,utf8和uft-16使用的编码方式不一样。
对最后一个flex-item设置margin-left:auto
https://stackoverflow.com/questions/33924655/position-last-flex-item-at-the-end-of-container
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.