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

JavaScript 基础

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 中的值是由一个表示类型的标签和实际数据值表示的。 JavaScript包含七种数据类型,分别为numberstringbooleanundefinednullobjectsymbol(ES6新增的)。

numberstringbooleansymbol合称为原始类型(primitive type)数据。 object是由原始类型数据组成,所以称为复合类型(complex type)数据。 undefinednull,一般将它们看成两个特殊值。

object又可以分成三个子类型:

判断数据类型

常用的三种方法:

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位存储其类型信息

null所有机器码均为0,所以用typeof判断返回objectundefined用 −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新增letconst命令来声明变量。它们所声明的变量,只在命令所在的代码块内有效,这个代码块称为块级作用域

let x = 1;
{
  let x = 2, y=3;
}
console.log(x); // 1
console.log(y); // 报错,在外层没有定义变量y

const同理。

参考:

  1. https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/block
  2. 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'

参考:

  1. https://juejin.im/post/59ac1c4ef265da248e75892b
  2. https://segmentfault.com/a/1190000008637489

内存释放

不管什么程序语言,内存生命周期基本是一致的:

  1. 分配你所需要的内存
  2. 使用分配到的内存(读、写)
  3. 不需要时将其释放\归还

在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