bind函数补充

bind返回函数被当成构造函数的情况

在MDN中有这么一句话

1
2
bind()中的第一个参数:调用绑定函数时作为 this 参数传递给目标函数的值。
如果使用new运算符构造绑定函数,则忽略该值

那么这句话啥意思呢??

首先,我们都知道bind()会返回一个新的函数,如果这个返回的新的函数作为构造函数创建一个新的对象,那么此时this不再指向传入给bind的第一个参数,而是指向用new创建的实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function func (name) {
console.log(this);
this.name = name
}
func.prototype.hello = function () {
console.log(this.name);
}
let obj = {
a: 1,
say: function () {
console.log('say');
}
}
let newObj = func.bind(obj)
newObj()
//{a:1, say:f} 此时this指向obj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function func (name) {
console.log(this);
// func{}
this.name = name
}
func.prototype.hello = function () {
console.log(this.name);
}
let obj = {
a: 1,
say: function () {
console.log('say');
}
}
let newObj = func.bind(obj)
// 若将返回的函数作为构造函数
let o = new newObj('seven')
//this的指向发生了改变,指向原函数,并且可以访问原函数的原型
console.log('o', o);
// func{name:'seven'}
o.hello() // seven
阅读全文
JS中的bind

什么是bind

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用

语法

1
function.bind(thisArg[, arg1[, arg2[, ...]]])

参数

  1. thisArg
  • 调用绑定函数时作为 this 参数传递给目标函数的值。 如果使用new运算符构造绑定函数,则忽略该值。当使用 bindsetTimeout 中创建一个函数(作为回调提供)时,作为 thisArg 传递的任何原始值都将转换为 object。如果 bind 函数的参数列表为空,执行作用域的 this 将被视为新函数的 thisArg
  1. arg1, arg2, ...
  • 当目标函数被调用时,被预置入绑定函数的参数列表中的参数。

返回值

返回一个原函数的拷贝,并拥有指定的 this 值和初始参数

bind特点

  1. bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this(js的this是动态的,改变this有三种方式call,apply,bind),之后的一序列参数将会在传递的实参前传入作为它的参数。
  2. 一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被略,同时调用时的参数被提供给模拟函数。

mdn的Polyfill

Polyfill 是一块代码(通常是 Web 上的 JavaScript),用来为旧浏览器提供它没有原生支持的较新的功能。

比如说 polyfill 可以让 IE7 使用 Silverlight 插件来模拟 HTML Canvas 元素的功能,或模拟 CSS 实现 rem 单位的支持,或 text-shadow,或其他任何你想要的功能

Poly表示可以使用多种技术来解决它-它不仅限于使用JavaScript完成,而且fill会填补浏览器中需要该技术的空白。它也不意味着“旧的浏览器”(因为我们也需要填充新的浏览器)

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
if (!Function.prototype.bind) {
//防止Function.prototype.bind.call(obj,param)这种调用改变this
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() {
// this instanceof fBound === true时,说明返回的fBound被当做new的构造函数调用
return fToBind.apply(this instanceof fBound
? this
: oThis,
// 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
aArgs.concat(Array.prototype.slice.call(arguments)));
};
// 维护原型关系
if (this.prototype) {
// 当执行Function.prototype.bind()时, this为Function.prototype
// this.prototype(即Function.prototype.prototype)为undefined
fNOP.prototype = this.prototype;
}
// 下行的代码使fBound.prototype是fNOP的实例,因此
// 返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的 实例
fBound.prototype = new fNOP();
return fBound;
};}

polyfill相关文章

词法作用域

==JavaScript 采用的是词法作用域(静态的作用域),函数的作用域在函数定义的时候就决定了==(一个函数先定义后执行,定义的时候什么都不干,但是已经锁定了其词法作用域,执行的时候有执行机上下文,会用到已经定义的词法作用域)

而与词法作用域相对的是动态作用域,函数的作用域是在函数调用的时候才决定的

1
2
3
4
5
6
7
8
9
var value = 1;
function foo() {
console.log(value);
}
function bar() {
var value = 2;
foo();
}
bar();
  • 假设JavaScript采用静态作用域,让我们分析下执行过程:
    • 执行 foo 函数,先从 foo 函数内部查找是否有局部变量 value,如果没有,就根据书写的位置,查找上面一层的代码,也就是 value 等于 1,所以结果会打印 1。
  • 假设JavaScript采用动态作用域,让我们分析下执行过程:
    • 执行 foo 函数,依然是从 foo 函数内部查找是否有局部变量 value。如果没有,就从调用函数的作用域,也就是 bar 函数内部查找 value 变量,所以结果会打印 2。

前面我们已经说了,JavaScript采用的是静态作用域,所以这个例子的结果是 1

执行顺序

1
2
3
4
5
6
7
8
9
// 赋值式函数
var foo = function () {
console.log('foo1');
}
foo(); // foo1
var foo = function () {
console.log('foo2');
}
foo(); // foo2

然而去看这段代码:

1
2
3
4
5
6
7
8
9
// 声明式函数
function foo() {
console.log('foo1');
}
foo(); // foo2
function foo() {
console.log('foo2');
}
foo(); // foo2

可执行代码(executable code)的类型:全局代码、函数代码、eval代码。

举个例子,当执行到一个函数的时候,就会进行准备工作,这里的“准备工作”,让我们用个更专业一点的说法,就叫做”执行上下文(execution context)”。

补充

事实上,JS的解析过程分为两个阶段:预编译期(预处理)与执行期

预编译期JS会对本代码块中的所有声明的变量和函数进行处理(类似与C语言的编译),但需要注意的是此时处理函数的只是声明式函数,而且变量也只是进行了声明但未进行初始化以及赋值

1
2
3
4
5
6
7
//例子1
<script type="text/javascript">
Fn(); //执行了定义式函数
function Fn(){
alert("执行了定义式函数");
}
</script>
1
2
3
4
5
6
7
// 例子2
<script type="text/javascript">
Fn(); //报错,提示函数未定义
var Fun= function(){
alert("执行了赋值式函数");
}
</script>
1
2
3
4
5
6
7
8
9
10
11
// 例子3
//因为在js中重名的函数,后定义的会覆盖前面定义的函数,这种策略和js的顺序执行也是有关系的
<script type="text/javascript">
Fn(); //它执行的结果是:弹出"执行了函数2"
function Fn(){
alert("执行了函数1");
}
function Fn(){
alert("执行了函数2");
}
</script>

执行上下文栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//单个函数执行的时候有执行机上下文,多个函数执行的时候有执行上下文栈 
function fun3() {
console.log('fun3')
}
function fun2() {
fun3();
}
function fun1() {
fun2();
}
fun1();
// 伪代码
// fun1()ECStack.push(<fun1> functionContext); -- fun1进栈
// fun1中竟然调用了fun2,还要创建fun2的执行上下文ECStack.push(<fun2> functionContext); -- fun2进栈
// 擦,fun2还调用了fun3!ECStack.push(<fun3> functionContext); -- fun3进栈
// fun3执行完毕ECStack.pop(); -- fun3出栈
// fun2执行完毕ECStack.pop(); -- 。。
// fun1执行完毕ECStack.pop(); -- 。。
// javascript接着执行下面的代码,但是ECStack底层永远有个globalContext

变量对象

对于每个执行上下文,都有三个重要属性:

  • 变量对象(Variable object,VO)
  • 作用域链(Scope chain)
  • this

变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明

全局上下文

​ 全局上下文中的变量对象就是全局对象!

函数上下文

  • 在函数上下文中,我们用活动对象(activation object, AO)来表示变量对象。
  • 活动对象和变量对象其实是一个东西,只是变量对象是规范上的或者说是引擎实现上的,不可在 JavaScript 环境中访问,只有到当进入一个执行上下文中,这个执行上下文的变量对象才会被激活,所以才叫 activation object 呐,而只有被激活的变量对象,也就是活动对象上的各种属性才能被访问。
  • 活动对象是在进入函数上下文时刻被创建的,它通过函数的 arguments 属性初始化。arguments 属性值是 Arguments 对象。(只有函数才具有arguments )

执行过程

  1. 进入执行上下文
  2. 代码执行

进入执行上下文

当进入执行上下文时,这时候还没有执行代码,

变量对象会包括:

  1. 函数的所有形参 (如果是函数上下文)
    • 由名称和对应值组成的一个变量对象的属性被创建
    • 没有实参,属性值设为 undefined
  2. 函数声明
    • 由名称和对应值(函数对象(function-object))组成一个变量对象的属性被创建
    • 如果变量对象已经存在相同名称的属性,则完全替换这个属性
  3. 变量声明
    • 由名称和对应值(undefined)组成一个变量对象的属性被创建;
    • 如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性
1
2
3
4
5
6
7
8
9
function foo(a) {
var b = 2;
function c() {}
var d = function() {};

b = 3;

}
foo(1);

在进入执行上下文后,这时候的 AO 是

1
2
3
4
5
6
7
8
9
10
AO = {
arguments: {
0: 1,
length: 1
},
a: 1,
b: undefined,
c: reference to function c(){}, // 函数提升
d: undefined
}

代码执行

在代码执行阶段,会顺序执行代码,根据代码,修改变量对象的值

还是上面的例子,当代码执行完后,这时候的 AO:

1
2
3
4
5
6
7
8
9
10
AO = {
arguments: {
0: 1,
length: 1
},
a: 1,
b: 3,
c: reference to function c(){},
d: reference to FunctionExpression "d"
}

作用域链

  1. 查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链
  2. 函数的作用域在函数定义的时候就决定了!!!!!!
  3. 这是因为函数有一个内部属性 [[scope]] (双括号表示引擎内部的变量),当函数创建的时候,就会保存所有父变量对象到其中,你可以理解 [[scope]] 就是所有父变量对象的层级链,但是注意:[[scope]] 并不代表完整的作用域链!
1
2
3
4
5
function foo() {
function bar() {
...
}
}

函数创建时,各自的[[scope]]为:

1
2
3
4
5
6
7
8
9
//scope可以理解为函数在创建时的一个属性
foo.[[scope]] = [
globalContext.VO // -- globalContext位于栈底,Vo里存放变量,这就是所有函数为什么能够拿到window变量的原因
];

bar.[[scope]] = [
fooContext.AO,// bar套在foo中,所以能拿到foo的AO,(定义bar的时候,foo已经执行了)
globalContext.VO // window的AO与VO相同,不需要激活
];

详见下面的例子 – 图片中只展示出来了层级关系

1
2
3
4
5
6
7
8
9
10
function foo() {
var a = 10
function bar() {
var b = 20
console.dir(bar)
}
bar()
}
foo()
console.dir(foo)

函数激活

当函数激活时(激活通俗的理解就是执行),进入函数上下文,创建 VO/AO 后,就会将活动对象添加到作用链的前端。

这时候执行上下文的作用域链,我们命名为 ScopeChain

1
ScopeChain = [AO].concat([[Scope]]);

至此,作用域链创建完毕

this

其实很复杂,简单来说是谁调用指向谁

具体执行分析

1
2
3
4
5
6
7
8
9
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f();
}
checkscope();
  1. 执行全局代码,创建全局执行上下文,全局上下文被压入执行上下文栈
1
2
3
ECStack = [
globalContext
];
  1. 全局上下文初始化
1
2
3
4
5
globalContext = {
VO: [global],
Scope: [globalContext.VO],
this: globalContext.VO
}
  1. 初始化的同时,checkscope函数被创建,保存作用域链到函数的内部属性[[scope]]
1
2
3
checkscope.[[scope]] = [
globalContext.VO
];
  1. 执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 函数执行上下文被压入执行上下文栈
1
2
3
4
ECStack = [
checkscopeContext,
globalContext
];
  1. checkscope 函数执行上下文初始化:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
复制函数 [[scope]] 属性创建作用域链,
arguments 创建活动对象,
初始化活动对象,即加入形参、函数声明、变量声明,
将活动对象压入 checkscope 作用域链顶端。
同时 f 函数被创建,保存作用域链到 f 函数的内部属性[[scope]]

checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope: undefined,
f: reference to function f(){}
},
Scope: [AO, globalContext.VO], //ScopeChan
this: undefined
}
  1. 执行 f 函数,创建 f 函数执行上下文,f 函数执行上下文被压入执行上下文栈
1
2
3
4
5
ECStack = [
fContext,
checkscopeContext,
globalContext
];
  1. f 函数执行上下文初始化, 以下跟第 4 步相同:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制函数 [[scope]] 属性创建作用域链
arguments 创建活动对象
初始化活动对象,即加入形参、函数声明、变量声明
将活动对象压入 f 作用域链顶端

fContext = {
AO: {
arguments: {
length: 0
}
},
Scope: [AO, checkscopeContext.AO, globalContext.VO],
this: undefined
}
  1. f 函数执行,沿着作用域链查找 scope 值,返回 scope 值
  2. f 函数执行完毕,f 函数上下文从执行上下文栈中弹出
  3. checkscope 函数执行完毕,checkscope 执行上下文从执行上下文栈中弹出
1
2
3
ECStack = [
globalContext
];

闭包

定义

闭包是指那些能够访问自由变量的函数(MDN)、闭包是指有权访问另外一个函数作用域中的变量的函数(红宝书p178)、《JavaScript权威指南》中就讲到:从技术的角度讲,所有的JavaScript函数都是闭包。

从实践角度:以下函数才算是闭包:

  1. 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
  2. 在代码中引用了自由变量

例子

1
2
3
4
5
6
7
8
9
10
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
var foo = checkscope();
foo();

这里直接给出简要的执行过程:

  • 进入全局代码,创建全局执行上下文,全局执行上下文压入执行上下文栈
  • 全局执行上下文初始化
  • 执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 执行上下文被压入执行上下文栈
  • checkscope 执行上下文初始化,创建变量对象、作用域链、this等
  • checkscope 函数执行完毕,checkscope 执行上下文从执行上下文栈中弹出
  • 执行 f 函数,创建 f 函数执行上下文,f 执行上下文被压入执行上下文栈
  • f 执行上下文初始化,创建变量对象、作用域链、this等
  • f 函数执行完毕,f 函数上下文从执行上下文栈中弹出

了解到这个过程,我们应该思考一个问题,那就是:

当 f 函数执行的时候,checkscope 函数上下文已经被销毁了啊(即从执行上下文栈中被弹出),怎么还会读取到 checkscope 作用域下的 scope 值呢?

我们知道 f 执行上下文维护了一个作用域链:

1
2
3
fContext = {
Scope: [AO, checkscopeContext.AO, globalContext.VO],
}

call和apply的模拟实现

call() 方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Function.prototype.call2 = function (context) {
var context = context || window;
context.fn = this;
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
var result = eval('context.fn(' + args +')');
delete context.fn
return result;
}

Function.prototype.apply = function (context, arr) {
var context = Object(context) || window;
context.fn = this;
var result;
if (!arr) {
result = context.fn();
}
else {
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push('arr[' + i + ']');
}
result = eval('context.fn(' + args + ')')
}
delete context.fn
return result;
}

new

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型之一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function objectFactory() {
var obj = new Object(),
Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
var ret = Constructor.apply(obj, arguments);
return typeof ret === 'object' ? ret : obj;
};
function Otaku (name, age) {
this.name = name;
this.age = age;

this.habit = 'Games';
}
Otaku.prototype.strength = 60;
Otaku.prototype.sayYourName = function () {
console.log('I am ' + this.name);
}
var person = objectFactory(Otaku, 'Kevin', '18')
console.log(person.name) // Kevin
console.log(person.habit) // Games
console.log(person.strength) // 60
console.log(person.age) // 18
console.log(person.sayYourName())//I am Kevin

柯里化

在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。

1
2
3
4
5
6
7
8
9
10
11
12
13
//乞丐版:完整版很深
var curry = function (fn) {
var args = [].slice.call(arguments, 1);
return function() {
var newArgs = args.concat([].slice.call(arguments));
return fn.apply(this, newArgs);
};
};
function add(a, b) {
return a + b;
}
var addCurry = curry(add, 1, 2);
addCurry() // 3//或者var addCurry = curry(add, 1);addCurry(2) // 3//或者var addCurry = curry(add);addCurry(1, 2) // 3

继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Rectangle(length,width){
this.l = length
this.w = width
}
Rectangle.prototype.getArea = function(){
return this.l*this.w
}
function Square(length){
Rectangle.call(this,length,length)
}
Square.prototype = Object.create(Rectangle.prototype,{
constructor:{
value:Square
}
}) // 相当于Rectangle.prototype.constructor = Square
var square = new Square(3)
console.log(square.getArea())
console.log(square instanceof Square)
console.log(square instanceof Rectangle)

原型链

爸爸的父亲等于妈妈的老公???

后续

bind()创建的function上没有prototype/箭头函数上也没有prototype

阅读全文
Js防抖与节流

JS防抖和节流

防抖

原理

事件响应函数在一段时间后才执行,如果在这段时间内再次调用,则重新计算执行时间;当预定的时间内没有再次调用该函数,则执行响应逻辑

underscore中的debounce函数可以防抖

应用场景

  1. scroll事件滚动触发的时候
  2. 搜索框输入查询的时候
  3. 表单验证
  4. 按钮的提交事件
  5. 浏览器的窗口缩放,resize事件

自定义防抖函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* 防抖函数的自定义实现
* @param func 执行的函数
* @param wait 等待的时间
* @param immediate 是否立即执行
*/
function debounce (func, wait, immediate) {
// result -- 返回值
let timeout, result;
let debounced = function () {
let self = this
let args = arguments
if(timeout) clearTimeout(timeout)
if (immediate) {
// callNow是立即执行的变量
let callNow = !timeout
timeout = setTimeout(() => {
timeout = null
}, wait)
// 由于最开始timeout为undefined,取反为true,故立即执行
if (callNow) result = func.apply(self, args)
} else {
// 不会立即执行
timeout = setTimeout(function() {
//解决执行函数内部this指向问题以及event指向问题
result = func.apply(self, args)
}, wait)
}
return result
}
// 取消
debounced.cancel = function () {
clearTimeout(timeout)
timeout = null // 防止内存泄露
}
return debounced
}

节流

原理

如果你持续的触发事件,每隔一段时间,只执行一次事件

应用场景

  1. DOM元素的拖拽功能的实现
  2. 计算鼠标移动的距离
  3. 监听scroll滚动事件

初步实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 节流函数 -- 初步实现 -- 第一次会触发,最后一次不会触发
* @param func 执行的函数
* @param wait 等待的时间
*/
function trottle (func, wait) {
let context, args
// 之前的时间戳
let old = 0 // 默认为0
return function () {
context = this
args = arguments
// 获取当前的时间戳
let now = new Date().valueOf()
if (now-old > wait) {
// 立即执行
func.apply(context, args)
old = now
}
}
}

改进版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 节流函数 -- 第一次不会触发,最后一次会触发
*/
function trottle (func, wait) {
let context, args, timeout
return function () {
context = this
args = arguments
if (!timeout) {
timeout = setTimeout(() => {
timeout = null
func.apply(context, args)
}, wait)
}
}
}

再次改进版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* 节流函数
* 第一次会触发,最后一次会触发
*/
function trottle (func, wait) {
let context, args, timeout
let old = 0 // 时间戳
let later = function () {
old = new Date().valueOf()
timeout = null
func.apply(context, args)
}
return function () {
context = this
args = arguments
let now = new Date().valueOf()
if (now - old > wait) {
if (timeout) {
clearTimeout(timeout)
timeout = null
}
func.apply(context, args)
old = now
}

if (!timeout) {
timeout = setTimeout(later, wait)
}
}
}

最终版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* 节流函数
* 最终版本
* 第一次不会触发,最后一次会调用 leading:false, trailing:true
* 第一次会触发,最后一次不会触发 leading:true, trailing:false
*/
function trottle (func, wait, options) {
let context, args, timeout, result
let old = 0 // 时间戳
if (!options) options = {}
let later = function () {
old = new Date().valueOf()
timeout = null
result = func.apply(context, args)
}
let trottled = function () {
context = this
args = arguments
let now = new Date().valueOf()
if (options.leading === false && !old) {
old = now;
}
if (now - old > wait) {
if (timeout) {
clearTimeout(timeout)
timeout = null
}
result = func.apply(context, args)
old = now
}

if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, wait)
}
return result
}
trottled.cancel = function () {
clearTimeout(timeout)
timeout = null
}
return trottled
}
阅读全文
JS深拷贝

浅拷贝

创建了一个新的对象,这个对象有着原始对象属性值得精确拷贝,如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。

1
2
3
4
Object.assign(target, ...sources)
let target = {}
Object.assign(target, source)
target.a === source.a // true

判断类型的方式

typeof

typeof可以用于判断以下js的8种类型:

js的8中数据类型:

  • Boolean
  • Null
  • Undefined
  • Number
  • BigInt – 浏览器已经支持了
  • String
  • Symbol
  • Object

但也有缺点 — 暂时性死区(TDZ)

我们知道let和const 具有暂时性死区(即在代码块内,使用let/const命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”)

typeof也具有暂时性死区

1
2
typeof x; // ReferenceError -- 声明在后导致出错
let x;
1
typeof undeclared_variable // "undefined"  -- 若一个变量根本没有声明,倒不会报错
1
2
// 考烂了的面试题
typeof null === 'object' // true

instanceof

原理: 右边变量的prototype在左边变量的原型链上即可(右边构造函数的原型在不在左边实例的原型链上)

1
2
3
4
String instanceof String // false
Function instanceof Function // true
Object instanceof Object // true
Function instanceof Object // true

JS原型链神图

Object.prototype.toString()

深拷贝

将一个对象从内存中完整的拷贝出来一份,从内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象

最常用的一种方式 – 业务中用(不适合于面试)

1
let d = JSON.parse(JSON.stringify(x)) // 将x深拷贝给d

基本版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function deepCopy( obj ){
if (typeof obj === 'object') {
if (obj.constructor === Array) {
var newArr = []
for (var i = 0; i < obj.length; i++) {
newArr.push(obj[i])
}
} else {
var newObj = {}
for (var key in obj) {
newObj[key] = this.deepCopy(obj[key])
}
return newObj
}
} else {
return obj
}
}

面试版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 面试版本
// 判断是不是一个复杂类型
const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null)
const deepCopy = function (obj, hash= new WeakMap()) {
if (hash.has(obj)) return hash.get(obj);
let type = [Date, RegExp, Set, Map, WeakMap, WeakSet];
if (type.includes(obj.constructor)) return new obj.constructor(obj);
// 如果成环了,参数obj = obj.loop = 最初的obj 会在WeakMap中找到第一次放入的obj提前返回第一次放入 WeakMap的cloneObj
let allDesc = Object.getOwnPropertyDescriptors(obj) // 遍历传入参数所有键的特性
let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc) // 继承原型
hash.set(obj, cloneObj)
for (let key of Reflect.ownKeys(obj)) { //Reflect.ownKeys可以拷贝不可枚举属性和符号类型
// 如果值是引用类型则递归调用deepClone
cloneObj[key] =
(isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ? deepCopy(obj[key], hash) : obj[key];
}
return cloneObj
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 测试用例
let obj = {
bigInt: BigInt(12312),
set: new Set([2]),
map: new Map([['a', 222], ['b', 33]]),
num: 0,
str: '',
boolean: true,
unf: undefined,
nul: null,
obj: {
name: '我是一个对象',
id: 1
},
arr: [1,2,3],
func: function() {
console.log('我是一个函数');
},
date: new Date(0),
reg: new RegExp('/我是一个正则/ig'),
[Symbol('1')] : 1
}
Object.defineProperty(obj, 'innumerable', {
enumerable: false,
value: '不可枚举属性'
})
obj = Object.create(obj, Object.getOwnPropertyDescriptors(obj))
obj.loop = obj
console.log(obj);
let cloneObj = deepCopy(obj)
console.log('cloneObj', cloneObj);
for (let key of Object.keys(cloneObj)) {
if (typeof cloneObj[key] === 'object' || typeof cloneObj[key] === 'function') {
console.log(`${key}相同吗?`, cloneObj[key] === obj[key]);
}
}

学习自京程一灯董老师课程,如有侵权,联系删除

阅读全文
CSS专业术语

CSS常用专业术语

  1. 属性:属性对应的是平常我们书面或交谈时对应的CSS中文称谓,例如height,color等

  2. 值:

    • 整数值
    • 数值:如line-height:1.5中的1.5
    • 百分比值
    • 长度值:例如99px
    • 颜色值:例如#99
    • 字符串值等等
  3. 关键字:如soild,inherit等

  4. 泛关键字:可以理解为公交车关键字,就是“所有CSS属性都可以使用的关键字”的意思。

  5. 变量:如CSS3中的currentColor等

  6. 长度单位:如px,em等(值 + 长度单位 = 长度)

    • 相对长度单位:

      • 相对字体长度单位
        • em:其相对于当前对象内文本的font-size(如果当前对象内文本的font-size计量单位也是em,则当前对象内文本的font-size的参考对象为父元素文本font-size)。使用em可以较好的相应设备屏幕尺寸的变化,但是在进行元素设置时都需要知道父元素文本的font-size及当前对象内文本的font-size,如有遗漏可能会导致错误.
        • ex:字符x高度的一半,一般是字体高度一半。
        • rem:使用rem为元素设定字体大小时,仍然是相对大小,但相对的只是HTML根元素。这个单位可谓集相对大小和绝对大小的优点于一身,通过它既可以做到只修改根元素就成比例地调整所有字体大小,又可以避免字体大小逐层复合的连锁反应。
      • 相对视区长度单位:vh,vw,vmin,vmax等
        • 视区所指为浏览器内部的可视区域大小,即window.innerWidth/window.innerHeight大小,不包含任务栏标题栏以及底部工具栏的浏览器区域大小)
        • vh:相对于视框的高度:视框高度是100vh
        • vw:相对于视框的宽度,视框宽度为100vw
        • vmin:相对于视口的宽度或高度中较小的那个。其中最小的那个被均分为100单位的vmin
          1
          2
          3
          4
          5
          6
          h1 {
          font-size: 8vm;
          font-size: 8vmin;
          }
          /*
          如果视口的宽度是300mm,高度是200mm,那么上述代码中h1元素的字号将为16mm,即(8x200)/100,因为高度比宽度要小,所以计算的时候相对于高度。*/
    • 绝对长度单位:

      • px:像素
      • cm
      • mm
      • pc : 皮卡Picas (1 皮卡 = 12 点)
      • pt : 点Points (1点 = 1/72英寸)
  7. 功能符:值以函数的形式指定起来的,主要用来表示颜色(rgba和hsla),背景图片地址(url),计算(calc)等,如rfb(0,0,0,.5)、url(‘css-World.png’)等

  8. 属性值:属性冒号后面的所有内容

  9. 声明:属性名加上属性值就是声明

  10. 声明块:用花括号{}包裹的一系列声明

  11. 规则或者规则集:出现了选择器,并且后面还跟着声明块

    1
    2
    3
    4
    .vo{
    height:99px;
    color:transparent;
    }
  12. 选择器:

  13. 关系选择器

  14. @规则:指的是以@字符开始的一些规则。

阅读全文
流、元素与基本尺寸(1)

流、元素与基本尺寸

  1. 流:实际上是CSS世界中的一种基本的定位和布局机制,可以理解为现实世界的一套物理规则,流跟现实世界的水流有异曲同工之妙
  2. CSS中的标签种类繁多,但通常我们把他们分为两类:块级元素(block-level element)和内联元素(inline element)

    块级元素

  3. 常见的块级元素有div,li,table等,但需要注意的是块级元素和display属性值为block的元素是不一样的,例如li元素默认display值时list-item,table元素默认的display值是table,但他们均是块级元素(块级元素的基本特征:一个水平流上只显示一个元素,多个块级元素换行显示)。
  4. 由于块级元素具有换行的特性,因此理论上它可以配合clear属性来清除浮动带来的影响
  5. 浮动:元素脱离文档流,使元素在页面中水平排列
    • float可选值如下:
      • none默认值,元素默认在文档流中排列
      • left:元素立即脱离文档流,向页面左侧浮动
      • right:元素立即脱离文档流,向页面右侧浮动
    • 内联元素脱离文档流后会变成块元素
    • 块元素脱离文档流后高度和宽度会被内容撑开
    • 浮动的元素不会盖住文字,文字会自动环绕在浮动元素周围,所以我们可以通过浮动来设置文字环绕图片的效果
    • 如果浮动元素上边是一个没有浮动的元素,则浮动元素不会超过块元素,浮动的元素不会超过他上边的兄弟元素
    • 当一个元素设置为浮动以后(非none值),元素会立即脱离文档流,脱离之后,它下面的元素会立即向上移动,元素浮动以后,会尽量向元素的左上或者是右上漂浮,直到遇到父元素的边框或者其他的浮动元素。
  6. 消除浮动:
    1
    2
    3
    4
    5
    .clear::after{
    content: "";
    display: block;
    clear: both;
    }
  7. 小案例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <title>清除浮动</title>
    <style>
    .box{
    padding: 10px;
    background-color:#cd0000;
    }
    .box > img {
    float:left;
    }
    .clear::after{
    content: "";
    display: block;
    clear: both;
    }
    </style>
    </head>
    <body>
    <div class="box clear">
    <img src="../img/1.jpg" alt="功夫熊猫">
    </div>
    </body>
    </html>
阅读全文
GitHub使用技巧(一)

GitHub使用小技巧(一)

在GitHub中搜索你所需要的知识得一些小窍门

在日常的学习中,我们很多时间需要去从GitHub上寻找一些开源项目
首先,我们需要了解GitHub上面开源项目的一些基本知识:项目名,项目源码,项目描述,Readme.md,star,fork数,更新日期等等

  1. in:name 搜索关键字 —-在GitHub项目名上精确搜索,如:in:name app开发,可找到所有项目名中包含“app开发”的项目
  2. stars: 整数 —–GitHub项目的star数目,例如你要找star数目大于3000的项目,stars:>3000
  3. forks: 整数 ——GitHub项目fork数目,与star数目用法相同
  4. in:readme 搜索关键字 ——-在Readme中搜索
  5. in:discription 搜索关键字 ——在描述里面搜索你所需要的内容
  6. language:语言名 —— 如需要搜索java项目;language:java即可
  7. pushed: 日期 ——-根据日期进行搜索,例如搜索2019年11月2日以后更新的项目, pushed:>2019-11-02
  8. 以上7点可以混合使用,以使你的搜索更加精确,找到你感兴趣的东西。
阅读全文
55-跳跃游戏

跳跃游戏

题目:给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个位置。

  1. 解法一:
    • 解题思路:最容易想到的方法,直接遍历每一种可能的情况,暴力破解,但是这个方法超时(哭了)
    • 代码如下
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      function  canJumpyue(position , nums) {
      //到达最后一个,直接返回true
      if(position == nums.length-1){
      return true;
      }
      var JumpLength = Math.min(position+nums[position],nums.length-1);
      //遍历所有的可能路径
      for(var nextposition = position + 1;nextposition <= JumpLength;nextposition++){
      if(canJumpyue(nextposition,nums)){
      return true;
      }
      }
      return false;
      }
      var canJump = function(nums) {
      return canJumpyue(0,nums);
      };
阅读全文
ubuntu系统重装

ubuntu系统重装(链接如下)

UEFI启动Windows10+Ubuntu双系统删除Ubuntu方法
安装win10+ubuntu18.04双系统
在Win10与Ubuntu双系统中删除Ubuntu

如有侵权,请联系删除

阅读全文
44--通配符匹配

通配符匹配

  1. 问题描述
    给定一个字符串 (s) 和一个字符模式 (p) ,实现一个支持 ‘ ? ‘ 和 ‘ * ‘ 的通配符匹配。
    ‘ ? ‘ 可以匹配任何单个字符。
    ‘ * ‘ 可以匹配任意字符串(包括空字符串)
    s 可能为空,且只包含从 a-z 的小写字母。
    p 可能为空,且只包含从 a-z 的小写字母,以及字符 ? 和 *

  2. 代码实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    var star=-1, match=0;
    var sp=0, pp=0;
    while(sp < s.length){
    if(pp<p.length && (s[sp] == p[pp] || p[pp] =="?")){
    sp++;
    pp++;
    }else if(pp<p.length && p[pp] == "*"){
    star = pp;
    match = sp;
    pp++;
    }else if(star != -1){
    pp = star + 1;
    match++;
    sp = match;
    }else return false;
    }
    while (pp < p.length && p[pp] == '*') pp++;
    return pp == p.length;
阅读全文