JavaScript基础

228 阅读4分钟

基础

常用浏览器及对应内核

浏览器内核
IEtrident
Chromewebkit/blink
FirefoxGecko
Operapresto
Safariwebkit

JS特点

  • 解释性语言,单线程,脚本语言。
  1. 动态语言基本都是解释性语言,解释性语言基本都是脚本语言。
  2. 解释性语言特点:解释一行执行一行。
  3. 解释之前会通篇扫描一遍看有没有语法错误(低级错误),如果此时有错,则程序一行都不会执行。
  4. 执行到逻辑错误时,终止。注释该错误则程序继续往下走。

语法错误

  1. 低级错误(语法分析错误) Uncaught SyntaxError:
  2. 逻辑错误(标准错误) Uncaught ReferenceError:

数据类型

  1. 基本类型:numberbooleanstringundefinednullSymbol(ES6引入的)
  2. 引用类型:arrayobjectfunction
// 原始值不能有属性
var num = 1;
num.a = 2; // 讲道理是会报错的,但是返回值是undefined。
// 原因是包装类! 所以会做这样的隐式转换:new Number(1).a = 2;

// 引用值可自由设置属性
var obj = {};
obj.a = 3;
  • 两种值唯一的不同是:赋值形式不同。
// 1. 原始值放在 栈(stack) 里面; 栈属性:先进的后出
var a = 1;
b = a;
a = 2;
console.log(b) // 1

// 2. 引用值放在 堆(heap) 里面;引用的是地址
var arr = [1]; 
var arr1 = arr; 
arr.push(2); 
// 此时 arr1 == [1,2] 

var arr = [1]; 
var arr1 = arr; 
arr = [2]; 
// 此时 arr1 == [1]。因为arr新开了一个房间

typeof 返回六种值

  • 返回的值均为字符串类型 numberbooleanstringobjectfunctionundefined

六种情况下返回值为 false

false0undefinednull""NaN

=====

==会先试图类型转换,然后再比较,而===不会类型转换。

1 == '1' // true
1 === '1' // false
0 == false // true
0 === false // false
null == undefined // true
null === undefined // false
  • 何时使用=== 何时使用==? 根据 jQuery 源码中的写法,只推荐在一个地方用==,其他地方都必须用===
// 这里相当于 obj.a === null || obj.a === undefined ,简写形式
if (obj.a == null) {}
  • 运算规则
  1. undefined == null,结果是true。且它俩与所有其他值比较的结果都是false。
  2. String == Boolean,需要两个操作数同时转为Number。
  3. String/Boolean == Number,需要String/Boolean转为Number。
  4. Object == Primitive,需要Object转为Primitive(具体通过valueOf和toString方法)。

JS中有哪些本地、内置、宿主对象?

  1. 本地对象:Object Array Boolean Number String Function 等可以new实例化。
  2. 内置对象:gload Math 等不可用实例化的。
  3. 宿主对象:浏览器自带的 document window等。

类型转换

Number(null) == 0
Number(undefined) == NaN
Number('a') == NaN
Number(a) // 报错

+undefined == NaN

onload

  • window.onload是在 DOM 树加载完和所有文件加载完之后执行的一个函数,后执行,只能执行一次。
  • document.ready是在 DOM 树加载完之后执行的函数(不需要全部文件加载完),先执行,可出现多次。

iframe

  • 优点
  1. 能够原封不动的把嵌入网页展现出来。
  2. 遇到的加载缓慢的第三方内容如图标、广告,可以由 iframe 来解决。
  3. 重载页面时不需要重载整个页面,只需要重载页面中的一个框架页。
  • 缺点
  1. 页面样式调试麻烦,出现多个滚动条。
  2. 浏览器后退按钮失效。
  3. 过多会增加服务器的HTTP请求。
  4. 产生多个页面,不易管理。
  5. 代码复杂,无法被一些搜索引擎解读。
  • 父子通信
// 父页面,接收子页面传值
window.onmessage = function(e) {
    console.log(e.origin, e.data);
}
// 子页面,子页面给父页面传值
parent.postMessage(theObj, '*');

事件委托

利用冒泡的原理,把事件加到父级上,触发执行效果。

// 例如有一个无限滚动列表,对每个li执行事件
<ul id="list">
    <li class="li-1">1</li>
    <li class="li-2">2</li>
    <li class="li-3">3</li>
    ...
</ul>

$('#list').click((e) => {
    console.log(e.target.getAttribute('class'))
})

JS运行逻辑

  • JS运行三部曲:
  1. 语法分析
  2. 预编译
  3. 解释执行
  • 语法分析 通篇执行一遍,看有没有问题,有问题直接报错。
  • 预编译 预编译发生在函数执行的前一刻:
  1. 创建 AO 对象;(AO:Activation Object 执行期上下文)
  2. 找形参和变量声明,将变量和形参名作为 AO 属性名,值为 undefined。
  3. 将实参值和形参统一。
  4. 在函数体里面找函数声明,值赋予函数体。

注意:全局预编译:window === GO。
先执行 GO 再执行 AO。

案例

// 预编译 实例,执行过程
function fn(a) { // 开始从预编译中找值
    console.log(a); // function a(){}
    var a = 123; // var a 可忽略,因为已经在预编译中执行过, a = 123; 赋值。
    console.log(a); // 123
    function a() {}; // 忽略
    console.log(a); // 123
    var b = function() {}; // var b 可忽略,因为已经在预编译中执行过, b = funciton(){}); 赋值。
    console.log(b); // function(){}
    function d() {};
};
fn(1);

// 执行逻辑:
// 第一步、第二步:
AO: {
    a: undefined,
    b: undefined,
}
// 第三步:
AO: {
    a: 1,
    b: undefined
}
// 第四步:
AO: {
a: function a() {},
b: function() {},
d: function d() {}
}

递归

递归就是 return 公式;只是让代码变得简洁;

  1. 找规律
  2. 找出口
// 阶乘 
// 3! = 3 * 2! 
// 2! = 2 * 1! 
// 规律:n! = n * (n - 1)!
function mul(n) { 
    if(n == 1 || n == 0) { 
        return 1; // 出口 
    } 
    return n * mul(n - 1) 
} 

// 斐波那契 
function mulFei(n) { 
    if(n == 1 || n == 0) { 
        return 1; 
    } 
    return mulFei(n - 1) + mulFei(n - 2) 
}

立即执行函数

针对初始化功能的函数,执行完立即销毁,不占用内存。js中唯一一个可以手动立即销毁的函数。

// 写法一
(function (){}()) // 官方推荐

// 写法二
(function (){})()

(function(a,b,c) { 
    console.log(a + b + c * 2); 
}(1,2,3)); 

// 可以有返回值 
var num = (function(a,b,c) { 
    var d = a + b + c; 
    return d; 
}(1,2,3));
  • 只有表达式才能被执行符号执行
// 错误
function test() { 
    var a = 1; 
}()

// 下方则是正确的写法,加个 + 或者 -,变为了可执行的表达式 
+function test() { 
    console.log('1') 
}() // 1
// 表达式被执行后会忽略前面的名字 
var test = function() { 
    console.log('a') 
}() // a

注意:

// 函数声明不会执行,所以此刻是没用的,返回a是因为('a')
function test(a) {
    console.log(a)
}('a') // a

如何理解JSON

JSON 是一个js对象,是一种数据格式。

// 将对象转为字符串
JSON.stringify({a:10, b:20})
// 将字符串转为对象
JOSN.parse('{"a":10,"b":20}')

数组

常用方法

let arr = [1,2,3];

// push:尾部添加;可添加一个也可添加多个
arr.push(4);

// pop:尾部删除
arr.pop();

// unshift:头部添加
arr.unshift(0);

// shift:头部删除
arr.shift();

// reverse:翻转
arr.reverse();

// splice:截取(从第几位,截取几个,添加新数据)
arr.splice(0, 1, 100);

// concat: 组合
arr.concat(arr1,arr2)

// sort:排序
arr.sort(function(a,b) { 
    return a - b; // 升序 
    return b - a; // 降序 
    return Math.random() - 0.5; // 乱序
})

去重

let arr = [1, 2, 2, 3, 3, 4, 5, 6, 6];
// 方法一
let arr1 = [...new Set(arr)];
// 方法二
let arr2 = [];
arr.forEach(function(item, index) {
    // if (arr2.includes(item)) {
    if (arr2.indexOf(item) != -1) {
        arr2.splice(index, 1)
    } else {
        arr2.push(item)
    }
})

最大值

let zuidArr = [1, 2, 3, 44, 5, 6];
console.log(Math.max(...zuidArr))
console.log(Math.max.apply(null, zuidArr))

字符串转数组

let str = 'goodmorening';
let strArr1 = Array.from(str);
let strArr2 = str.split('');
let strArr3 = [...str];
let strArr4 = Array.prototype.slice.call(str);
let strArr5 = [].slice.call(str);

对象

  • 特点:有属性,有方法
var mrFu = {
    name: 'fj', // 属性名属性值,或者叫key值value值
    age: 27,
    health: 100,
    smoke() {
        this.health--;
    },
    drink() {
        this.health++
    }
}
// 1、 增:
mrFu.wife = 'xiaoyang';
// 2、 查:
mrFu.name;
// 3、 改:
 mrFu.age = 28;
// 4、 删:
delete mrFu.age;
  • 对象的枚举
var obj = {
    name: 'fj',
    age: 27,
    __proto__: {
        lastName: 'xx'
    }
}
Object.prototype.protoName = 'yjx'

for (let key in obj) {
    console.log(key) // name age lastName protoName
    // 不从原型链上寻找key值
    if (obj.hasOwnProperty(key)) {
        console.log(key) // name age
    }
}
console.log('name' in obj) // true

闭包

  • 闭包是指有权访问另一个函数作用域中的变量的函数。
  • 但凡是内部的函数,被保存到了外部,一定产生闭包。
  • 自由变量将从作用域链中去寻找,但是依据的是函数定义时的作用域链,而不是函数执行时
  1. 函数作为返回值。
function F1() {
    var a = 100
    return function () {
        console.log(a)
    }
}
var f1 = F1()
var a = 200
f1()
  1. 函数作为参数传递。
function F1() {
    var a = 100
    return function () {
        console.log(a)
    }
}
function F2(f1) {
    var a = 200
    console.log(f1())
}
var f1 = F1()
F2(f1)

作用

  • 闭包实际应用中主要用于封装变量、收敛权限。防止污染全局变量。
function isFirstLoad() {
    var _list = []

    return function (id) {
        if (_list.indexOf(id) >= 0) {
            return false
        } else {
            _list.push(id)
            return true
        }
    }
}

// 使用
var firstLoad = isFirstLoad()
firstLoad(10) // true
firstLoad(10) // false
firstLoad(20) // true
  • 模拟块级作用域。
  • 访问函数定义时所在的作用域。

缺点

  • 闭包会导致原有的作用域链不释放,造成内存泄漏。 内存泄漏理解:其实是一个反向理解的概念,比方说内存是一捧沙子,闭包会将一些变量一直占用而不得销毁,由此可理解为 内存少了一部分,用不了,泄漏的多,就剩的少。

作用域

  • [[scope]]:每个 JavaScript 函数都是一个对象,对象中有些属性我们可以访问,但有些不可以,这些不可以的属性仅供 JavaScript引擎存取,[[scope]] 就是其中一个。它就是我们所说的作用域,其中存储了执行期上下文集合。
// 执行一个函数,先生成一个执行期上下文,挂在自己(作用域链)的最顶端。
function test(){}

test.[[scope]]
// scope 就是 test 的一个属性,隐式的,表示作用域。

作用域链

[[scope]] 中所存储的执行期上下文对象的集合,这个集合呈链式链接,我们把这种链式链接叫做 作用域链
通俗点说:就是当前作用域中没有寻找的变量,则向父级作用域寻找,没有的话一层一层向上直到全局作用域。这种一层一层的关系,就是作用域链

function a () {
    function b () {}
    b()
}
a()

a defined a.[[scope]] --> 0: GO
a doing   a.[[scope]] --> 0: aAO
                          1: GO

b defined b.[[scope]] --> 0: aAO
                          1: GO
b doing   b.[[scope]] --> 0: bAO
                          1: aAO
                          2: GO

自由变量

当前作用域没有声明的变量。

var a = 100
function fn() {
    var b = 200
    console.log(a) // 自由变量
    console.log(b)
}
fn()

this

this 的值是在执行的时候才能确认,定义的时候不能确认!

var a = {
    name: 'A',
    fn: function () {
        console.log(this.name)
    }
}
a.fn()  // this === a
a.fn.call({name: 'B'})  // this === {name: 'B'}
var fn1 = a.fn
fn1()  // this === window

this执行会有不同,主要集中在这几个场景中

  • 作为构造函数执行。
  • 作为对象属性执行。
  • 作为普通函数执行。
  • 用于call apply bind
function fn1 (name, age) {
    console.log(name);
    console.log(this);
}
fn1.call({x: 100}, 'fj', 22); // fj {x: 100}
fn1.apply({y: 200}, ['yjx', 22]); // yjx {y: 200}

var fn2 = function (name, age) {
    console.log(name);
    console.log(this);
}.bind({z: 300})
fn2('gx',24); // gx {z: 300}

原型和原型链

定义

原型:是对象,它提供了一套属性和方法可供后代继承。 原型是 function 对象的一个属性,它定义了构造函数制造出的对象的公共祖先。通过该构造函数产生的对象,可以继承该原型的属性和方法。

  1. 利用原型的特点和概念,可以提取共有属性。
  2. 对象如何查看原型 :隐式属性 _proto_。
  3. 对象如何查看对象的构造函数:constructor

原型规则

  • 所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展属性(除了null以外)
var obj = {}; obj.a = 100;
var arr = []; arr.a = 100;
function fn () {}
fn.a = 100;
  • 所有的引用类型(数组、对象、函数),都有一个__proto__属性(隐式原型),属性值是一个普通的对象。
obj.__proto__
arr.__proto__
fn.__proto__
  • 所有的函数,都有一个prototype属性(显式原型),属性值也是一个普通的对象。
fn.prototype
  • 所有的引用类型(数组、对象、函数),__proto__属性值指向它的构造函数的prototype属性值。
obj.__proto__ === Object.prototype
  • 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的__proto__(即它的构造函数的prototype)中寻找

构造函数

构造函数内部原理:三段论

  1. 在函数体最前面隐式的加上 this = {};
  2. 执行 this.xxx = xxx;
  3. 隐式的返回 this;
function Foo(name, age) {
    // var this = {}
    this.name = name
    this.age = age
    this.class = 'class-1'
    // return this  // 默认有这一行
}
var f = new Foo('zhangsan', 20)
// var f1 = new Foo('lisi', 22)  // 创建多个对象

// Foo 是 f 的 构造函数
//  f 是 Foo 的 实例
  • var a = {}其实是var a = new Object()的语法糖。
  • var a = []其实是var a = new Array()的语法糖。
  • function Foo(){...}其实是var Foo = new Function(...)的语法糖。

原型链

// 构造函数
function Foo(name, age) {
    this.name = name
}
Foo.prototype.alertName = function () {
    alert(this.name)
}
// 创建示例
var f = new Foo('zhangsan')
f.printName = function () {
    console.log(this.name)
}
f.printName() // 自身寻找
f.alertName() // 原型寻找

f.toString() // 原型链上寻找 f.__proto__.__proto__

yxl.png

写一个原型链继承的例子

  • 案例一
// 动物
function Animal() {
    this.eat = function () {}
}
// 狗
function Dog() {
    this.bark = function () {}
}
Dog.prototype = new Animal()
// 哈士奇
var hashiqi = new Dog()
  • 案例二
function Elem (id) {
    this.elem = document.getElementById(id)
}

Elem.prototype.html = function (val) {
    let elem = this.elem;
    if (val) {
        elem.innerHTML = val
        return this // 链式操作
    } else {
        return elem.innerHTML
    }
}
Elem.prototype.on = function(type,fn) {
    let elem = this.elem;
    elem.addEventListener(type,fn)
}

let div1 = new Elem('container')
div1.html('点击').on('click',function() {
    alert('点击了')
})

异步单线程

同步和异步的区别是什么?

  • 同步会阻塞代码执行,而异步不会。(比如 alert是同步,setTimeout是异步)。
// 以下代码执行后,打印出来的结果是什么
console.log(1)
setTimeout(function () {
    console.log(2)
}, 0)
console.log(3)
setTimeout(function () {
    console.log(4)
}, 1000)
console.log(5)

答案:1、3、5、2、4
因为:js 是单线程语言。当遇到异步代码时,会将其暂存在一边,不能阻塞正常代码执行。等正常代码走完,再根据异步代码执行顺序依次执行。

前端使用异步的场景有哪些?

  • 定时任务:setTimeoutsetInterval
  • 网络请求:ajax 请求,动态 <img> 加载。
  • 事件绑定。事件绑定原理也是将代码暂存,等用户操作后再执行暂存的代码。

通用事件绑定

// 源代码
var btn = document.getElementById('btn');
btn.addEventListener('click', function() {
    // to do
})

// 优化后
function bindEvent (elem, type , fn) {
    elem.addEventListener(type, fn)
}
bindEvent(btn, 'click', function(e) {
    e.preventDefault();
    // to do
})

存储

  1. 本地存储:Web Storage
  2. 本地存储:IndexedDB

存储需求

  • 移动端网络环境因素:
  1. 数据响应慢,体验下降。
  2. 节省流量 = 节省金钱。
  • 追求原生体验:
  1. 离线使用应用。
  2. 存储应用相关资源,数据。

cookie 劣势

  1. 存储大小限制,仅4kb左右。
  2. 单个域名下的数量限制,50个左右。
  3. 污染请求头,浪费流量。

localStorage 和 sessionStorage

  • 方法(两者方法相同):
  1. localStorage.setItem(key,value):设置存储内容。
  2. localStorage.getItem(key):获取存储内容。
  3. localStorage.removeItem(key):删除存储内容。
  4. localstorage.clear():清除所有内容。
  5. localstorage.length:获取存储内容的个数。
  • 区别
  1. 不同的存储时效: localstorage:存储会持久化没储存时间限制。
    sessionStorage:网页会话结束时失效(关闭网页会失效,刷新没问题。大小没限制)

  2. 不同的存储容量: localStorage容量一般在2-5Mb左右
    sessionStorage存储容量不一,部分浏览器不设限

注意事项

  • 存储容量超出限制
  1. 抛出QutotaExceededError异常(存储值时应使用try catch避免异常未捕获)
  • 存储类型的限制
  1. 只能存储字符串。
  2. 注意类型转换
  • sessionStorage 失效机制
  1. 刷新页面并不能使sessionStorage失效
  2. 相同url不同标签页不能共享sessionStorage数据(只能在自己的标签页:唯一性)

优化

性能与存储容量大小无关,与读取次数有关。

  1. 减少读取item次数。
  2. 单个item中尽可能多的存储数据。

indexedDB

HTML 的数据库

创建数据库

// name 数据库名字,verson 版本号
indexedDB.open(name, verson)
  1. 如果有这个数据库就打开,没有就创建。
  2. 版本号只能递增,否则会走 onerror。
let request = indexedDB.open('testDB', 3);
request.onsuccess = () => {
    console.log('创建数据库成功')
}
// 当版本号降低时
request.onerror = () => {
    console.log('读取数据库失败')
}
// 版本升级时触发
request.onupgradeneeded = () =>{
    console.log('版本号升级成功了')
}

创建表

// 创建一个命为 test1 的表
let request = indexedDB.open('testDB', 8);
let db = request.result;
db.createObjectStore('test1')

设置主键

  1. 设置自增主键:{autoIncrement: true}
// 1 2 3...递增
db.createObjectStore('test1', {autoIncrement: true})
  1. 取数据中字段作为主键:keyPath: 字段名
db.createObjectStore('test1', {keyPath: 'id'})
  • 添加内容
let json = {
    id: '001',
    name: 'xx',
    age: 24
}

// db.createObjectStore('test1', {autoIncrement: true})
db.createObjectStore('test2', {keyPath: 'id'})

setTimeout(()=>{
    let db = request.result;
    // 读取表,允许读和写 readwrite 读和写,readonly 只读
    let transaction = db.transaction('test2', 'readwrite')
    // let transaction = db.transaction(['test1','test2'], 'readwrite')
    // 读取哪个表
    let store = transaction.objectStore('test2');
    store.add(json)
}, 100)

表的增删改查

  • 添加数据:IDBObjectStore.add()
  • 读取数据:IDBObjectStore.get()
// 根据 key 值寻找
let requestNode = store.get('002');
// 读取所有数据
let requestNode = store.getAll();
// 执行完成后的回调,同样适用于修改、删除
requestNode.onsuccess = () => {
    console.log(requestNode.result)
}
  • 修改数据:IDBObjectStore.put()
let obj = {
    id: '002',
    name: 'yy',
    age: 27
}
store.put(obj)
  • 删除数据:IDBObjectStore.delete()
store.delete(key)
// 删除所有
store.clear()

索引

比 get 方式更方便,精准。

  • 创建索引:IDBObjectStore.createIndex
  1. indexName: 索引名称
  2. keyPath:索引字段,可以为空或者数组
  3. optionParameters:索引配置参数
let store1 = db.createObjectStore('test2', {
    keyPath: 'id'
})
// 表名称,key值,unique 唯一性
store1.createIndex('test2', 'name', {
    unique: true
})
  • 查看索引
// 读取哪个表
let store = transaction.objectStore('test2');

let index = store.index('test2');
index.get('xxx').onsuccess = (e) => {
    console.log(e.target.result)
}
  • 好处
  1. 可以使用存储记录中的值进行检索
  2. 索引自动更新
  3. 索引数据自动排序

游标

  • 创建游标 IDBObjectStore/IDBIndex.openCursor
  1. range: 指定游标范围 | Range | Code | | --- | --- | | All keys <= x | upperBound(x) | | All keys < x | upperBound(x, true) | | All keys >= y | lowerBound(y) | | All keys > y | lowerBound(y, true) | | The keys = z | only(z) | | All keys >=x && <= y | bound(x, y) | | All keys >x && < y | bound(x, y, true, true) | | All keys >x && <= y | bound(x, y, true, false) | | All keys >=x && < y | bound(x, y, false, true) |

  2. direction: 游标方向 next: 顺序查询、 prev: 逆序查询、 nextunique: 顺序唯一查询、 prevunique: 逆序唯一查询

let requestNode = store.openCursor(IDBKeyRange.only('001'));
requestNode.onsuccess = () => {
    console.log(requestNode.result.value)
}

// let requestNode = store.openCursor(IDBKeyRange.bound(100, 101, true, false));
let requestNode = store.openCursor(IDBKeyRange.upperBound(101),'prev');
requestNode.onsuccess = () => {
    let cursor = requestNode.result;
    if (cursor) {
        console.log(cursor.value)
        cursor.continue();
    }
}
  • 好处
  1. 可以查询指定数据集范围
  2. 拥有逆序遍历能力

优劣

  • 好处
  1. 存储类型更加丰富
  2. 可以在Worker中使用
  3. 条件搜索优势明显
  4. 存储容量更大
  • 劣处
  1. 学习曲线陡峭
  2. 兼容问题较多
  3. 跨域存储限制

Ajax

XMLHttpReques

var xhr = new XMLHttpRequest()
xhr.open('GET', '/api', true) // 允许异步
xhr.onreadystatechange = function () {
    if (xhr.readyState == 4) {
        if (xhr.status == 200) {
            // to do
        }
    }
}
xhr.send(null)

send(null):规定send方法是将请求发送到服务器, 但是里面的参数只在post请求下有效;而get方法下直接忽略send里面的值,默认为null。

  • readyState(4 最常用)
  1. 0 - (未初始化) 还没有调用send()方法
  2. 1 - (载入) 已调用send(方法,正在发送请求
  3. 2 - (载入完成) send()方法执行完成,已经接收到全部响应内容
  4. 3 - (交互) 正在解析响应内容
  5. 4 - (完成) 响应内容解析完成,可以在客户端调用了
  • status(200 最常用)
  1. 2xx - 表示成功处理请求。如200。
  2. 3xx - 需要重定向,浏览器直接跳转。
  3. 4xx - 客户端请求错误,如404。
  4. 5xx - 服务器端错误。

JQuery 中 ajax的调用

$.ajax({
    type: 'POST', // 请求方式
    async: false, // 同步
    url: '...', // 请求地址
    dataType: 'json',
    contentType: 'application/json',
    data: { // 入参
        name: 'fj',password: 'xxx'
    },
    beforeSend(request) { // 设置头部header入参 非必须
        request.setRequestHeader('appId','xxx')
        request.setRequestHeader('token','xxx')
    },
    success(res) {
        // to do
    },
    error(err) {
        // to do
    }
})

跨域

  • 什么是跨域?
  1. 浏览器有同源策略,不允许ajax访问其他域接口。
  2. 跨域条件:协议、域名、端口,有一个不同就算跨域。
  • 如何解决跨域?
  1. 针对前端,跨域的实现方式就是JSONP
  2. 针对后端,跨域的实现方式就是设置 http header
  • 跨域注意事项。
  1. 所有的跨域请求都必须经过信息提供方允许。
  2. 如果未经允许即可获取,那是浏览器同源策略出现漏洞。
  • 可以跨域的三个标签。
  1. <img src=xxx>
  2. <link href=xxxx>
  3. <script src=xxx>
  • 三个标签的场景。
  1. <img> 用于打点统计,统计网站可能是其他域(img没有跨域限制,可以兼容各种浏览器)。
  2. <link> <script> 可以使用CDN,CDN的也是其他域。
  3. <script>可以用于JSONP。

JSONP

  • 原理 script 标签可以逃避浏览器的同源策略。首先定义一个函数,然后请求外域的一个API,API返回的是一个JS片段,JS片段正好执行这个函数,然后就把参数传过来,如此就可请求到跨域数据。
<script>
window.callback = function(data) {
    // 跨域得到的信息
    console.log(data)
}
</script>

<script src="http://www.xxx.com/api.js"></script>
// 以上将返回 callback({x: 100, y: 200})
  • JQuery 中 JSONP
$.ajax({
    url: 'http://www.xxx.com/xx.json', // 跨域接口
    dataType: 'jsonp',
    jsonpCallback: 'myCallback', // 商议好的内容
    success: function (result) {
        console.log(result)
    }
})
// myCallback ({x: 100, y: 200})
// result ({x: 100, y: 200})