JavaScript
JavaScript 是 web 开发者必学的三种语言之一: HTML 定义网页的内容 CSS 规定网页的布局 JavaScript 对网页行为进行编程 JavaScript 和 Java 是完全不同的语言,不论是概念还是设计。 JavaScript 在 1995 年由 Brendan Eich 发明,并于 1997 年成为一部 ECMA 标准。 ECMA-262 是其官方名称。ECMAScript 6 (发布于 2015 年)是最新的 JavaScript 版本。 1. 如果您想学习 JavaScript,您可以学习本教程: 了解 JavaScript 是如何与 HTML 和 CSS 一起工作的。 2. 如果在此之前您已经使用过 JavaScript,您也可以阅读本教程: JavaScript 一直在升级,所以我们需要时刻了解 JavaScript 的新技术。
JavaScript 基础
JavaScript 函数和事件 JavaScript 函数是一种 JavaScript 代码块,它可以在调用时被执行。 例如,当发生事件时调用函数,比如当用户点击按钮时。 提示:您将在稍后的章节学到更多有关函数和事件的知识。 ECMAScript 版本 JavaScript 已经由 ECMA(欧洲电脑制造商协会)通过 ECMAScript 实现语言的标准化。 年份 名称 描述 1997 ECMAScript 1 第一个版本 1998 ECMAScript 2 版本变更 1999 ECMAScript 3 添加正则表达式 添加 try/catch ECMAScript 4 没有发布 2009 ECMAScript 5 添加 "strict mode",严格模式 添加 JSON 支持 2011 ECMAScript 5.1 版本变更 2015 ECMAScript 6 添加类和模块 2016 ECMAScript 7 增加指数运算符 (**) 增加 Array.prototype.includes
JS中call apply bind使用指南及原理
JS中call apply bind使用指南及原理
为什么需要这些?主要是因为this,来看看this干的好事。
box.onclick = function(){
function fn(){
alert(this);
}
fn();
};
我们原本以为这里面的this指向的是box,然而却是Window。一般我们这样解决:
box.onclick = function(){
var _this = this;
function fn(){
alert(_this);
}
fn();
};
将this保存下来。
还有一些情况,有时我们想让伪数组也能够调用数组的一些方法,这时call、apply、bind就派上用场了。
我们先来解决第一个问题修复this指向。
box.onclick = function(){
function fn(){
alert(this);
}
fn();
};
改成如下:
box.onclick = function(){
function fn(){
console.log(this);
}
fn.call(this);
};
很神奇吧,call的作用就是改变this的指向的,第一个传的是一个对象,就是你要借用的那个对象。
fn.call(this);
这里的意思是让this去调用fn这个函数,这里的this是box,这个没有意见吧?如果这个你不清楚,很可能你是javscript的新朋友。box调用fn,这句话非常重要,我们知道this它始终指向一个对象,刚好box就是一个对象。那么fn里面的this就是box。
box.onclick = function(){
function fn(){
console.log(this);
}
fn.call(this);
};
这句话在某些情况下是可以简写的,比如:
box.onclick = function(){
var fn = function(){
console.log(this); //box
}.call(this);
};
或者这样:
box.onclick = function(){
(function(){
console.log(this);
}.call(this)); //box
};
又或者这样:
var objName = {name:'JS2016'};
var obj = {
name:'0 _ 0',
sayHello:function(){
console.log(this.name);
}.bind(objName)
};
obj.sayHello();//JS2016
call和apply、bind但是用来改变this的指向的,但也有一些小小的差别。下面我们来看看它们的差别在哪。
function fn(a,b,c,d){
console.log(a,b,c,d);
}
//call
fn.call(null,1,2,3);
//apply
fn.apply(null,[1,2,3]);
//bind
var f = fn.bind(null,1,2,3);
f(4);
结果如下:
1 2 3 undefined
1 2 3 undefined
1 2 3 4
前面说过第一个参数传的是一个你要借用的对象,但这么我们不需要,所有就传了一个null,当然你也可以传其他的,反正在这里没有用到,除了第一个参数后面的参数将作为实际参数传入到函数中。
call就是挨个传值,apply传一个数组,bind也是挨个传值,但和call和apply还有多少不同,使用call和apply会直接执行这个函数,而bind并不会而是将绑定好的this重新返回一个新函数,什么时候调用由你自己决定。
var objName = {name:'JS2016'};
var obj = {
name:'0 _ 0',
sayHello:function(){
console.log(this.name);
}.bind(objName)
};
obj.sayHello();//JS2016
这里也就是为什么我要用bind的原因,如果用call的话就会报错了。自己想想这个sayHello在obj都已经执行完了,就根本没有sayHello这个函数了。
这几个方法使用的好的话可以帮你解决不少问题比如:
正常情况下Math.max只能这样用
Math.max(10,6)
但如果你想传一个数组的话你可以用apply
var arr = [1,2,30,4,5];
console.log(Math.max.apply(null,arr));
又或者你想让伪数组调用数组的方法
function fn(){
[].push.call(arguments,3);
console.log(arguments); //[1, 2, 3]
}
fn(1,2);
再者:
var arr = ['aaabc'];
console.log(''.indexOf.call(arr,'b')); //3
牛逼不,简直偷梁换柱,当然还有很多可以利用的,自己尽情花辉去吧。
简单说一下这种偷梁换柱的原理吧,实际上浏览器内部根本就不在乎你是谁,它只关心你传给我的是不是我能够运行的,如下:
正常情况
var str = 'aaabc';
console.log(str.indexOf('b'));
而这种情况其实做的事情和上面一模一样,看我来拆解。
var arr = ['aaabc'];
''.indexOf.call(arr);
这句话就是说让arr调用字符串的indexOf方法,前面说过了浏览器内部不在乎你是谁,所以谁都可以来调用,但不是100%成功,具体看如下。
''.indexOf.call(arr,'b')
这里的arr就是['aaabc'],内部很可能拆成了'aaabc',因此就成了下面的这段代码。
'aaabc'.indexOf('b');
这就是它们的秘密。
这里得说一下bind在某些浏览器下不兼容。我们来模拟一个玩玩。
Function.prototype.$bind = function(obj){
//保存当前this
var _this = this;
//截取除了第一个以外的所有实际参数
var a = [].slice.call(arguments,1);
//返回一个新函数
return function(){
//让当前那个调用的函数的this指向obj,并且把实参传给它,这里用了concat是因为,我们可能在绑定以后还传递参数,所以才把他们合并起来。如f(4)这个是在绑定以后传的参数,a这个argument是绑定时的。
_this.apply(obj,a.concat([].slice.call(arguments)));
};
};
这个方法和实际上的bind还是差别很大的,如
var arr = ['JSS'];
var index = ''.indexOf.$bind(arr,'S');
console.log(index())
------------------
function fff(){
[].push.$bind(arguments,1);
console.log(arguments);
}
fff();
这些都没法使用,因为技术有限就没办法带大家封装一个完美的bind了,如果有需要网上搜索一下吧。
JavaScript 基础问题
JavaScript 基础问题
数据类型
在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。
JavaScript包含七种数据类型,分别为number
、string
、boolean
、undefined
、null
、object
和symbol
(ES6新增的)。
number
、string
、boolean
、symbol
合称为原始类型(primitive type)数据。
object
是由原始类型数据组成,所以称为复合类型(complex type)数据。
undefined
和null
,一般将它们看成两个特殊值。
object
又可以分成三个子类型:
- 狭义的对象(object)
- 数组(array)
- 函数(function)
判断数据类型
常用的三种方法:
-
typeof
运算符 -
instanceof
运算符 -
Object.prototype.toString
方法
typeof
typeof 123 // "number"
typeof NaN // "number"
typeof "abc" // "string"
typeof true // "boolean"
typeof function foo() {} // "function"
typeof undefined // "undefined"
typeof Symbol('abc') // "symbol"
基本类型与函数都能直接返回数据类型的字符串。
typeof {a: 1} // "object"
typeof [1, 2] // "object"
typeof null // "object"
js 在底层存储变量的时候,会在变量的机器码的低位1-3位存储其类型信息
- 000:对象
- 010:浮点数
- 100:字符串
- 110:布尔
- 1:整数
null
所有机器码均为0,所以用typeof
判断返回object
。
undefined
用 −2^30 整数来表示
instanceof
instanceof
的原理是检查右边构造函数的prototype属性,是否在左边对象的原型链上。也就是判断一个实例是否是其父类型或者祖先类型的实例。
实现原理大概如下:
function instance_of(L, R) {//L 表示左表达式,R 表示右表达式
var O = R.prototype;// 取 R 的显示原型
L = L.__proto__;// 取 L 的隐式原型
while (true) {
if (L === null)
return false;
if (O === L)// 这里重点:当 O 严格等于 L 时,返回 true
return true;
L = L.__proto__;
}
}
参考:https://www.ibm.com/developerworks/cn/web/1306_jiangjj_jsinstanceof/
示例:
'abc' instanceof String; // false, 字符串不是一个实例
123 instanceof Number; // false
true instanceof Boolean; // false
// 数字、布尔值同样效果
(new String('abc')) instanceof String; // true
(new String('abc')) instanceof Object; // true
String instanceof Object; // true
let person = function () {};
let Amy = new person();
Amy instanceof person; // true
[1,2] instanceof Array; // true
[1,2] instanceof Object; // true
简单来说,instanceof
的作用是检测一个对象是否是另个对象 new
出来的实例。牵涉更深一层原理,可参考 js中__proto__和prototype的区别和关系?
参考: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/instanceof
Object.prototype.toString
调用Object.prototype.toString.call(arguments)
会以[object xxx]
返回参数的数据类型,xxx
为类型名称。
Object.prototype.toString.call(123) // "[object Number]"
Object.prototype.toString.call('1bc') // "[object String]"
Object.prototype.toString.call({a:'a'}) // "[object Object]"
Object.prototype.toString.call([1,'a', true]) // "[object Array]"
Object.prototype.toString.call(true) // "[object Boolean]"
Object.prototype.toString.call(() => {}) // "[object Function]"
Object.prototype.toString.call(null) // "[object Null]"
Object.prototype.toString.call(undefined) // "[object Undefined]"
Object.prototype.toString.call(Symbol(1)) // "[object Symbol]"
Object.prototype.toString.call(new Date) // "[object Date]"
Object.prototype.toString.call(/\w+/ig) // "[object RegExp]"
Object.prototype.toString.call(new Error) // "[object Error]"
作用域
JavaScript
的作用域有三个:
- 全局作用域
- 局部作用域
- 块级作用域
全局作用域
在函数定义之外声明的变量是全局变量
,它的值可在整个程序中访问和修改。
// 全局变量
var name = "John";
// 全局函数
function sayhi () {
return console.log("Hi!");
}
局部作用域
在函数定义内声明的变量是局部变量
。
function sayhi () {
var saying = "Hi, John.";
console.log(saying);
}
console.log(saying); // 将抛出异常
块级作用域
ES6新增let
和const
命令来声明变量。它们所声明的变量,只在命令所在的代码块内有效,这个代码块称为块级作用域
。
let x = 1;
{
let x = 2, y=3;
}
console.log(x); // 1
console.log(y); // 报错,在外层没有定义变量y
const
同理。
参考:
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/block
- https://msdn.microsoft.com/zh-cn/library/bzt2dkta(v=vs.94).aspx
模块作用域
在Node.js
中,一个文件可以称为一个模块。每个模块都有各自的作用域,跨模块无法访问。可以通过require
方法引入这个文件去调用当中作用域的值。或者通过global
标志变量为全局作用域。
// 引入文件
const file = require('./1.js');
// 全局变量
global.web = "Nodejs";
参考: http://nodejs.cn/api/globals.html
引用传递
堆和栈的概念
内存中存储的区域分为堆
和栈
。
栈(stack)为自动分配的内存空间,大小固定,它由系统自动释放; 堆(heap)则是动态分配的内存,大小不定也不会自动释放。
var word = 'hello';
str[0] = 'y';
console.log(str); // hello,基本数据类型值不可变。
存储在堆
中的一般是简单的数据段,它们位置存储的就是变量的值。JavaScript中的原始类型数据就是存储在堆
中。
复合类型数据存储在栈
中,但会在在堆
中存储一个指向栈
的指针。
传值与传址
原始类型数据的赋值运算在底层的实现是,新开一段栈内存
,然后赋值到新栈中。
var num1 = 10;
var num2 = num1;
num1 += 5;
console.log(num1); // 15
console.log(num2); // 20
赋值后变量的运算不会影响到原变量。
复合类型数据的赋值运算,是堆
地址的赋值。新变量的栈
内存获取的是原变量的堆
地址。
var a = {age: 12};
var b = a;
a.name = 'John'
a.age // 12
b.age // 12
b.name // 'John'
参考:
- https://juejin.im/post/59ac1c4ef265da248e75892b
- https://segmentfault.com/a/1190000008637489
内存释放
不管什么程序语言,内存生命周期基本是一致的:
- 分配你所需要的内存
- 使用分配到的内存(读、写)
- 不需要时将其释放\归还
在C语言中,可以使用malloc
方法用来申请内存,使用完后,必须自己用free
方法释放内存。
但大多数语言提供自动内存管理,这被称为"垃圾回收机制"(garbage collector)。
var n = 123; // 给数值变量分配内存
var s = "azerty"; // 给字符串分配内存
// 两个对象被创建,一个作为另一个的属性被引用,另一个被分配给变量o
// 很显然,没有一个可以被垃圾收集
var o = {
a: {
b:2
}
};
var o2 = o; // o2变量是第二个对“这个对象”的引用
o = 1; // 现在,“这个对象”的原始引用o被o2替换了
var oa = o2.a; // 引用“这个对象”的a属性
// 现在,“这个对象”有两个引用了,一个是o2,一个是oa
o2 = "yo"; // 最初的对象现在已经是零引用了
// 他可以被垃圾回收了
// 然而它的属性a的对象还在被oa引用,所以还不能回收
oa = null; // a属性的那个对象现在也是零引用了
// 它可以被垃圾回收了
参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Memory_Management
ES6新特性
推荐阅读阮一峰的《ECMAScript 6 入门》
ES6新特性与浏览器兼容性可视化数据 http://kangax.github.io/compat-table/es6/
ES6特性相关推荐阅读文章目录 https://www.css88.com/archives/9922